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

   This file is part of the JUCE library.
   Copyright (c) 2015 - ROLI Ltd.

   Permission is granted to use this software under the terms of either:
   a) the GPL v2 (or any later version)
   b) the Affero GPL v3

   Details of these licenses can be found at: www.gnu.org/licenses

   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.

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

   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.juce.com for more information.

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

#if JUCE_DEBUG && ! defined (JUCE_DEBUG_XERRORS)
 #define JUCE_DEBUG_XERRORS 1
#endif

Display* display = nullptr;
Window juce_messageWindowHandle = None;
XContext windowHandleXContext;   // This is referenced from Windowing.cpp

typedef bool (*WindowMessageReceiveCallback) (XEvent&);
WindowMessageReceiveCallback dispatchWindowMessage = nullptr;

typedef void (*SelectionRequestCallback) (XSelectionRequestEvent&);
SelectionRequestCallback handleSelectionRequest = nullptr;

//==============================================================================
ScopedXLock::ScopedXLock()       { if (display != nullptr) XLockDisplay (display); }
ScopedXLock::~ScopedXLock()      { if (display != nullptr) XUnlockDisplay (display); }

//==============================================================================
class InternalMessageQueue
{
public:
    InternalMessageQueue()
        : bytesInSocket (0),
          totalEventCount (0)
    {
        int ret = ::socketpair (AF_LOCAL, SOCK_STREAM, 0, fd);
        (void) ret; jassert (ret == 0);
    }

    ~InternalMessageQueue()
    {
        close (fd[0]);
        close (fd[1]);

        clearSingletonInstance();
    }

    //==============================================================================
    void postMessage (MessageManager::MessageBase* const msg)
    {
        const int maxBytesInSocketQueue = 128;

        ScopedLock sl (lock);
        queue.add (msg);

        if (bytesInSocket < maxBytesInSocketQueue)
        {
            ++bytesInSocket;

            ScopedUnlock ul (lock);
            const unsigned char x = 0xff;
            ssize_t bytesWritten = write (fd[0], &x, 1);
            (void) bytesWritten;
        }
    }

    bool isEmpty() const
    {
        ScopedLock sl (lock);
        return queue.size() == 0;
    }

    bool dispatchNextEvent()
    {
        // This alternates between giving priority to XEvents or internal messages,
        // to keep everything running smoothly..
        if ((++totalEventCount & 1) != 0)
            return dispatchNextXEvent() || dispatchNextInternalMessage();

        return dispatchNextInternalMessage() || dispatchNextXEvent();
    }

    // Wait for an event (either XEvent, or an internal Message)
    bool sleepUntilEvent (const int timeoutMs)
    {
        if (! isEmpty())
            return true;

        if (display != nullptr)
        {
            ScopedXLock xlock;
            if (XPending (display))
                return true;
        }

        struct timeval tv;
        tv.tv_sec = 0;
        tv.tv_usec = timeoutMs * 1000;
        int fd0 = getWaitHandle();
        int fdmax = fd0;

        fd_set readset;
        FD_ZERO (&readset);
        FD_SET (fd0, &readset);

        if (display != nullptr)
        {
            ScopedXLock xlock;
            int fd1 = XConnectionNumber (display);
            FD_SET (fd1, &readset);
            fdmax = jmax (fd0, fd1);
        }

        const int ret = select (fdmax + 1, &readset, 0, 0, &tv);
        return (ret > 0); // ret <= 0 if error or timeout
    }

    //==============================================================================
    juce_DeclareSingleton_SingleThreaded_Minimal (InternalMessageQueue)

private:
    CriticalSection lock;
    ReferenceCountedArray <MessageManager::MessageBase> queue;
    int fd[2];
    int bytesInSocket;
    int totalEventCount;

    int getWaitHandle() const noexcept      { return fd[1]; }

    static bool setNonBlocking (int handle)
    {
        int socketFlags = fcntl (handle, F_GETFL, 0);
        if (socketFlags == -1)
            return false;

        socketFlags |= O_NONBLOCK;
        return fcntl (handle, F_SETFL, socketFlags) == 0;
    }

    static bool dispatchNextXEvent()
    {
        if (display == nullptr)
            return false;

        XEvent evt;

        {
            ScopedXLock xlock;
            if (! XPending (display))
                return false;

            XNextEvent (display, &evt);
        }

        if (evt.type == SelectionRequest && evt.xany.window == juce_messageWindowHandle
              && handleSelectionRequest != nullptr)
            handleSelectionRequest (evt.xselectionrequest);
        else if (evt.xany.window != juce_messageWindowHandle && dispatchWindowMessage != nullptr)
            dispatchWindowMessage (evt);

        return true;
    }

    MessageManager::MessageBase::Ptr popNextMessage()
    {
        const ScopedLock sl (lock);

        if (bytesInSocket > 0)
        {
            --bytesInSocket;

            const ScopedUnlock ul (lock);
            unsigned char x;
            ssize_t numBytes = read (fd[1], &x, 1);
            (void) numBytes;
        }

        return queue.removeAndReturn (0);
    }

    bool dispatchNextInternalMessage()
    {
        if (const MessageManager::MessageBase::Ptr msg = popNextMessage())
        {
            JUCE_TRY
            {
                msg->messageCallback();
                return true;
            }
            JUCE_CATCH_EXCEPTION
        }

        return false;
    }
};

juce_ImplementSingleton_SingleThreaded (InternalMessageQueue)


//==============================================================================
namespace LinuxErrorHandling
{
    static bool errorOccurred = false;
    static bool keyboardBreakOccurred = false;
    static XErrorHandler oldErrorHandler = (XErrorHandler) 0;
    static XIOErrorHandler oldIOErrorHandler = (XIOErrorHandler) 0;

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

        if (JUCEApplicationBase::isStandaloneApp())
            MessageManager::getInstance()->stopDispatchLoop();

        errorOccurred = true;
        return 0;
    }

    int errorHandler (Display* display, XErrorEvent* event)
    {
        (void) display; (void) event;

       #if JUCE_DEBUG_XERRORS
        char errorStr[64] = { 0 };
        char requestStr[64] = { 0 };

        XGetErrorText (display, event->error_code, errorStr, 64);
        XGetErrorDatabaseText (display, "XRequest", String (event->request_code).toUTF8(), "Unknown", requestStr, 64);
        DBG ("ERROR: X returned " << errorStr << " for operation " << requestStr);
       #endif

        return 0;
    }

    void installXErrorHandlers()
    {
        oldIOErrorHandler = XSetIOErrorHandler (ioErrorHandler);
        oldErrorHandler = XSetErrorHandler (errorHandler);
    }

    void removeXErrorHandlers()
    {
        if (JUCEApplicationBase::isStandaloneApp())
        {
            XSetIOErrorHandler (oldIOErrorHandler);
            oldIOErrorHandler = 0;

            XSetErrorHandler (oldErrorHandler);
            oldErrorHandler = 0;
        }
    }

    //==============================================================================
    void keyboardBreakSignalHandler (int sig)
    {
        if (sig == SIGINT)
            keyboardBreakOccurred = true;
    }

    void installKeyboardBreakHandler()
    {
        struct sigaction saction;
        sigset_t maskSet;
        sigemptyset (&maskSet);
        saction.sa_handler = keyboardBreakSignalHandler;
        saction.sa_mask = maskSet;
        saction.sa_flags = 0;
        sigaction (SIGINT, &saction, 0);
    }
}

//==============================================================================
void MessageManager::doPlatformSpecificInitialisation()
{
    if (JUCEApplicationBase::isStandaloneApp())
    {
        // Initialise xlib for multiple thread support
        static bool initThreadCalled = false;

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

            initThreadCalled = true;
        }

        LinuxErrorHandling::installXErrorHandlers();
        LinuxErrorHandling::installKeyboardBreakHandler();
    }

    // Create the internal message queue
    InternalMessageQueue::getInstance();

    // Try to connect to a display
    String displayName (getenv ("DISPLAY"));
    if (displayName.isEmpty())
        displayName = ":0.0";

    display = XOpenDisplay (displayName.toUTF8());

    if (display != nullptr)  // This is not fatal! we can run headless.
    {
        // Create a context to store user data associated with Windows we create
        windowHandleXContext = 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)
        const int screen = DefaultScreen (display);
        juce_messageWindowHandle = XCreateWindow (display, RootWindow (display, screen),
                                                  0, 0, 1, 1, 0, 0, InputOnly,
                                                  DefaultVisual (display, screen),
                                                  CWEventMask, &swa);
    }
}

void MessageManager::doPlatformSpecificShutdown()
{
    InternalMessageQueue::deleteInstance();

    if (display != nullptr && ! LinuxErrorHandling::errorOccurred)
    {
        XDestroyWindow (display, juce_messageWindowHandle);
        XCloseDisplay (display);

        juce_messageWindowHandle = 0;
        display = nullptr;

        LinuxErrorHandling::removeXErrorHandlers();
    }
}

bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message)
{
    if (LinuxErrorHandling::errorOccurred)
        return false;

    InternalMessageQueue::getInstanceWithoutCreating()->postMessage (message);
    return true;
}

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

// this function expects that it will NEVER be called simultaneously for two concurrent threads
bool MessageManager::dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages)
{
    while (! LinuxErrorHandling::errorOccurred)
    {
        if (LinuxErrorHandling::keyboardBreakOccurred)
        {
            LinuxErrorHandling::errorOccurred = true;

            if (JUCEApplicationBase::isStandaloneApp())
                Process::terminate();

            break;
        }

        InternalMessageQueue* const queue = InternalMessageQueue::getInstanceWithoutCreating();
        jassert (queue != nullptr);

        if (queue->dispatchNextEvent())
            return true;

        if (returnIfNoPendingMessages)
            break;

        queue->sleepUntilEvent (2000);
    }

    return false;
}
