/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-6 by Raw Material Software ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified under the terms of the
   GNU General Public License, as published by the Free Software Foundation;
   either version 2 of the License, or (at your option) any later version.

   JUCE is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with JUCE; if not, visit www.gnu.org/licenses or write to the
   Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
   Boston, MA 02111-1307 USA

  ------------------------------------------------------------------------------

   If you'd like to release a closed-source product which uses JUCE, commercial
   licenses are also available: visit www.rawmaterialsoftware.com/juce for
   more information.

  ==============================================================================
*/

#include "juce_Config.h"
#if JUCE_BUILD_GUI_CLASSES

#include "linuxincludes.h"
#include <stdio.h>
#include <signal.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xresource.h>
#include <X11/Xutil.h>
#include "../../../src/juce_core/basics/juce_StandardHeader.h"

BEGIN_JUCE_NAMESPACE

#include "../../../src/juce_appframework/events/juce_MessageManager.h"
#include "../../../src/juce_core/threads/juce_WaitableEvent.h"
#include "../../../src/juce_core/threads/juce_Process.h"
#include "../../../src/juce_core/threads/juce_ScopedLock.h"
#include "../../../src/juce_core/basics/juce_Logger.h"

#ifdef JUCE_DEBUG
  #define JUCE_DEBUG_XERRORS 1
#endif

Display* display = 0;     // This is also referenced from WindowDriver.cpp
static Window juce_messageWindowHandle;

#define SpecialAtom         "JUCESpecialAtom"
#define BroadcastAtom       "JUCEBroadcastAtom"
#define SpecialCallbackAtom "JUCESpecialCallbackAtom"

static Atom specialId;
static Atom broadcastId;
static Atom specialCallbackId;

// This is referenced from WindowDriver.cpp
XContext improbableNumber;

// Defined in WindowDriver.cpp
extern void juce_windowMessageReceive (XEvent* event);

static int numPendingMessages = 0;
static int maxPendingMessages = 9000;

struct MessageThreadFuncCall
{
    MessageCallbackFunction* func;
    void* parameter;
    void* result;
    CriticalSection lock;
    WaitableEvent event;
};

static bool errorCondition = false;

// Usually happens when client-server connection is broken
static int ioErrorHandler (Display* display)
{
    DBG (T("ERROR: connection to X server broken.. terminating."));

    errorCondition = true;
    Process::terminate();
    return 0;
}

// A protocol error has occurred
static int errorHandler (Display* display, XErrorEvent* event)
{
#ifdef JUCE_DEBUG_XERRORS
    char errorStr[64] = { 0 };
    char requestStr[64] = { 0 };

    XGetErrorText (display, event->error_code, errorStr, 64);

    XGetErrorDatabaseText (display,
                           "XRequest",
                           (const char*) String (event->request_code),
                           "Unknown",
                           requestStr,
                           64);

    DBG (T("ERROR: X returned ") + String (errorStr) + T(" for operation ") + String (requestStr));
#endif

    return 0;
}

static bool breakIn = false;

// Breakin from keyboard
static void sig_handler (int sig)
{
    if (sig == SIGINT)
    {
        breakIn = true;
        return;
    }

    static bool reentrant = false;

    if (reentrant == false)
    {
        reentrant = true;

        // Illegal instruction
        fflush (stdout);
        Logger::outputDebugString ("ERROR: Program executed illegal instruction.. terminating");

        errorCondition = true;
        Process::terminate();
    }
    else
    {
        exit(0);
    }
}


//==============================================================================
void MessageManager::doPlatformSpecificInitialisation()
{
    // This is called if the client/server connection is broken
    XSetIOErrorHandler (ioErrorHandler);

    // This is called if a protocol error occurs
    XSetErrorHandler (errorHandler);

    // Install signal handler for break-in
    struct sigaction saction;
    sigset_t maskSet;
    sigemptyset (&maskSet);
    saction.sa_handler = sig_handler;
    saction.sa_mask = maskSet;
    saction.sa_flags = 0;
    sigaction (SIGINT, &saction, NULL);

#ifndef _DEBUG
    // Setup signal handlers for various fatal errors
    sigaction (SIGILL, &saction, NULL);
    sigaction (SIGBUS, &saction, NULL);
    sigaction (SIGFPE, &saction, NULL);
    sigaction (SIGSEGV, &saction, NULL);
    sigaction (SIGSYS, &saction, NULL);
#endif

    // Initialise xlib for multiple thread support
    if (! XInitThreads())
    {
        // This is fatal!  Print error and closedown
        Logger::outputDebugString (T("Failed to initialise xlib thread support."));
        Process::terminate();
    }

    String displayName (getenv ("DISPLAY"));
    if (displayName.isEmpty())
        displayName = T(":0.0");

    display = XOpenDisplay (displayName);

    if (display == 0)
    {
        // This is fatal!  Print error and closedown
        Logger::outputDebugString ("Failed to open the X display.");
        Process::terminate();
    }

    // Get defaults for various properties
    int screen = DefaultScreen (display);
    Window root = RootWindow (display, screen);
    Visual* visual = DefaultVisual (display, screen);

    // Create atoms for our ClientMessages (these cannot be deleted)
    specialId = XInternAtom (display, SpecialAtom, false);
    broadcastId = XInternAtom (display, BroadcastAtom, false);
    specialCallbackId = XInternAtom (display, SpecialCallbackAtom, false);

    // Create a context to store user data associated with Windows we
    // create in WindowDriver
    improbableNumber = XUniqueContext();

    // We're only interested in client messages for this window
    // which are always sent
    XSetWindowAttributes swa;
    swa.event_mask = NoEventMask;

    // Create our message window (this will never be mapped)
    juce_messageWindowHandle = XCreateWindow (display, root,
                                              0, 0, 1, 1, 0, 0, InputOnly,
                                              visual, CWEventMask, &swa);
}

void MessageManager::doPlatformSpecificShutdown()
{
    if (errorCondition == false)
    {
        XDestroyWindow (display, juce_messageWindowHandle);
        XCloseDisplay (display);
    }
}

bool juce_postMessageToSystemQueue (void* message, bool discardable)
{
    if (errorCondition)
        return false;

    if (numPendingMessages < maxPendingMessages || ! discardable)
    {
        ++numPendingMessages;

        XClientMessageEvent clientMsg;
        clientMsg.display = display;
        clientMsg.window = juce_messageWindowHandle;
        clientMsg.type = ClientMessage;
        clientMsg.format = 32;
        clientMsg.message_type = specialId;
#if JUCE_64BIT
        clientMsg.data.l[0] = (long) (0x00000000ffffffff & (((uint64) message) >> 32));
        clientMsg.data.l[1] = (long) (0x00000000ffffffff & (long) message);
#else
        clientMsg.data.l[0] = (long) message;
#endif

        XSendEvent (display, juce_messageWindowHandle, false,
                    NoEventMask, (XEvent*) &clientMsg);

        XFlush (display); // This is necessary to ensure the event is delivered

        return true;
    }
    else
    {
        DBG ("WARNING - Message queue too full! - message lost\n");
        return false;
    }
}

void MessageManager::broadcastMessage (const String& value)
{
}

void* MessageManager::callFunctionOnMessageThread (MessageCallbackFunction* func,
                                                   void* parameter)
{
    void* retVal = 0;

    if (! errorCondition)
    {
        if (! isThisTheMessageThread())
        {
            static MessageThreadFuncCall messageFuncCallContext;

            const ScopedLock sl (messageFuncCallContext.lock);

            XClientMessageEvent clientMsg;
            clientMsg.display = display;
            clientMsg.window = juce_messageWindowHandle;
            clientMsg.type = ClientMessage;
            clientMsg.format = 32;
            clientMsg.message_type = specialCallbackId;
#if JUCE_64BIT
            clientMsg.data.l[0] = (long) (0x00000000ffffffff & (((uint64) &messageFuncCallContext) >> 32));
            clientMsg.data.l[1] = (long) (0x00000000ffffffff & (uint64) &messageFuncCallContext);
#else
            clientMsg.data.l[0] = (long) &messageFuncCallContext;
#endif

            messageFuncCallContext.func = func;
            messageFuncCallContext.parameter = parameter;

            if (XSendEvent (display, juce_messageWindowHandle, false, NoEventMask, (XEvent*) &clientMsg) == 0)
                return 0;

            XFlush (display); // This is necessary to ensure the event is delivered

            // Wait for it to complete before continuing
            messageFuncCallContext.event.wait();

            retVal = messageFuncCallContext.result;
        }
        else
        {
            // Just call the function directly
            retVal = func (parameter);
        }
    }

    return retVal;
}

bool juce_dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages)
{
    XEvent evt;

    if (errorCondition)
        return false;

    if (breakIn)
    {
        errorCondition = true;
        Process::terminate();
    }

    if (returnIfNoPendingMessages && ! XPending (display))
        return false;

    XNextEvent (display, &evt);

    if (evt.type == ClientMessage && evt.xany.window == juce_messageWindowHandle)
    {
        XClientMessageEvent* const clientMsg = (XClientMessageEvent*) &evt;

        if (clientMsg->format != 32)
        {
            jassertfalse
            DBG ("Error: juce_dispatchNextMessageOnSystemQueue received malformed client message.");
        }
        else
        {
            JUCE_TRY
            {
#if JUCE_64BIT
                void* const messagePtr
                    = (void*) ((0xffffffff00000000 & (((uint64) clientMsg->data.l[0]) << 32))
                               | (clientMsg->data.l[1] & 0x00000000ffffffff));
#else
                void* const messagePtr = (void*) (clientMsg->data.l[0]);
#endif

                if (clientMsg->message_type == specialId)
                {
                    --numPendingMessages;
                    MessageManager::getInstance()->deliverMessage (messagePtr);
                }
                else if (clientMsg->message_type == specialCallbackId)
                {
                    MessageThreadFuncCall* const call = (MessageThreadFuncCall*) messagePtr;
                    MessageCallbackFunction* func = call->func;
                    call->result = (*func) (call->parameter);
                    call->event.signal();
                }
                else if (clientMsg->message_type == broadcastId)
                {
#if 0
                    TCHAR buffer[8192];
                    zeromem (buffer, sizeof (buffer));

                    if (GlobalGetAtomName ((ATOM) lParam, buffer, 8192) != 0)
                        mq->deliverBroadcastMessage (String (buffer));
#endif
                }
                else
                {
                    jassertfalse
                    DBG ("Error: juce_dispatchNextMessageOnSystemQueue received unknown client message.");
                }
            }
            JUCE_CATCH_ALL
        }
    }
    else if (evt.xany.window != juce_messageWindowHandle)
    {
        juce_windowMessageReceive (&evt);
    }

    return true;
}

END_JUCE_NAMESPACE

#endif
