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

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

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

   JUCE can be redistributed and/or modified under the terms of the GNU General
   Public License (Version 2), as published by the Free Software Foundation.
   A copy of the license is included in the JUCE distribution, or can be found
   online 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.rawmaterialsoftware.com/juce for more information.

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

#include "../jucedemo_headers.h"


//==============================================================================
class InterprocessCommsDemo  : public Component,
                               public ButtonListener,
                               public ComboBoxListener
{
public:
    //==============================================================================
    InterprocessCommsDemo()
    {
        server = new DemoInterprocessConnectionServer (*this);

        setName (T("Interprocess Communication"));

        // create all our UI bits and pieces..
        addAndMakeVisible (modeSelector = new ComboBox (T("mode:")));
        modeSelector->setBounds (100, 25, 200, 24);
        (new Label (modeSelector->getName(), modeSelector->getName()))->attachToComponent (modeSelector, true);

        modeSelector->addItem (T("(Disconnected)"), 8);
        modeSelector->addSeparator();
        modeSelector->addItem (T("Named pipe (listening)"), 1);
        modeSelector->addItem (T("Named pipe (connect to existing pipe)"), 5);
        modeSelector->addSeparator();
        modeSelector->addItem (T("Socket (listening)"), 2);
        modeSelector->addItem (T("Socket (connect to existing socket)"), 6);

        modeSelector->setSelectedId (8);
        modeSelector->addListener (this);

        addAndMakeVisible (pipeName = new TextEditor (T("pipe name:")));
        pipeName->setBounds (100, 60, 130, 24);
        pipeName->setMultiLine (false);
        pipeName->setText (T("juce demo pipe"));
        (new Label (pipeName->getName(), pipeName->getName()))->attachToComponent (pipeName, true);

        addAndMakeVisible (socketNumber = new TextEditor (T("socket port:")));
        socketNumber->setBounds (350, 60, 80, 24);
        socketNumber->setMultiLine (false);
        socketNumber->setText (T("12345"));
        socketNumber->setInputRestrictions (5, T("0123456789"));
        (new Label (socketNumber->getName(), socketNumber->getName()))->attachToComponent (socketNumber, true);

        addAndMakeVisible (socketHost = new TextEditor (T("socket host:")));
        socketHost->setBounds (530, 60, 130, 24);
        socketHost->setMultiLine (false);
        socketHost->setText (T("localhost"));
        socketNumber->setInputRestrictions (512);
        (new Label (socketHost->getName(), socketHost->getName()))->attachToComponent (socketHost, true);

        addChildComponent (sendText = new TextEditor (T("sendtext")));
        sendText->setBounds (30, 120, 200, 24);
        sendText->setMultiLine (false);
        sendText->setReadOnly (false);
        sendText->setText (T("testing 1234"));

        addChildComponent (sendButton = new TextButton (T("send"), T("Fires off the message")));
        sendButton->setBounds (240, 120, 200, 24);
        sendButton->changeWidthToFitText();
        sendButton->addButtonListener (this);

        addChildComponent (incomingMessages = new TextEditor (T("messages")));
        incomingMessages->setReadOnly (true);
        incomingMessages->setMultiLine (true);
        incomingMessages->setBounds (30, 150, 500, 250);

        // call this to set up everything's state correctly.
        comboBoxChanged (0);
    }

    ~InterprocessCommsDemo()
    {
        close();
        delete server;

        deleteAllChildren();
    }

    void buttonClicked (Button* button)
    {
        if (button == sendButton)
        {
            // The send button has been pressed, so write out the contents of the
            // text box to the socket or pipe, depending on which is active.
            const String text (sendText->getText());
            MemoryBlock messageData (text.toUTF8(), text.getNumBytesAsUTF8());

            for (int i = activeConnections.size(); --i >= 0;)
            {
                if (! activeConnections[i]->sendMessage (messageData))
                {
                    // the write failed, so indicate that the connection has broken..
                    appendMessage (T("send message failed!"));
                }
            }
        }
    }

    void comboBoxChanged (ComboBox*)
    {
        // This is called when the user picks a different mode from the drop-down list..
        const int modeId = modeSelector->getSelectedId();

        close();

        if (modeId < 8)
        {
            open ((modeId & 2) != 0,
                  (modeId & 4) != 0);
        }
    }

    //==============================================================================
    // Just closes any connections that are currently open.
    void close()
    {
        server->stop();
        activeConnections.clear();

        // Reset the UI stuff to a disabled state.
        sendText->setVisible (false);
        sendButton->setVisible (false);
        incomingMessages->setText (String::empty, false);
        incomingMessages->setVisible (true);

        appendMessage (
            "To demonstrate named pipes, you'll need to run two instances of the JuceDemo application on this machine. On "
            "one of them, select \"named pipe (listening)\", and then on the other, select \"named pipe (connect to existing pipe)\". Then messages that you "
            "send from the 'sender' app should appear on the listener app. The \"pipe name\" field lets you choose a name for the pipe\n\n"
            "To demonstrate sockets, you can either run two instances of the app on the same machine, or on different "
            "machines on your network. In each one enter a socket number, then on one of the apps, select the "
            "\"Socket (listening)\" mode. On the other, enter the host address of the listening app, and select \"Socket (connect to existing socket)\". "
            "Messages should then be be sent between the apps in the same way as through the named pipes.");
    }

    void open (bool asSocket, bool asSender)
    {
        close();

        // Make the appropriate bits of UI visible..
        sendText->setVisible (true);
        sendButton->setVisible (true);

        incomingMessages->setText (String::empty, false);
        incomingMessages->setVisible (true);

        // and try to open the socket or pipe...
        bool openedOk = false;

        if (asSender)
        {
            // if we're connecting to an existing server, we can just create a connection object
            // directly.
            DemoInterprocessConnection* newConnection = new DemoInterprocessConnection (*this);

            if (asSocket)
            {
                openedOk = newConnection->connectToSocket (socketHost->getText(),
                                                           socketNumber->getText().getIntValue(),
                                                           1000);
            }
            else
            {
                openedOk = newConnection->connectToPipe (pipeName->getText());
            }

            if (openedOk)
                activeConnections.add (newConnection);
            else
                delete newConnection;
        }
        else
        {
            // if we're starting up a server, we need to tell the server to start waiting for
            // clients to connect. It'll then create connection objects for us when clients arrive.
            if (asSocket)
            {
                openedOk = server->beginWaitingForSocket (socketNumber->getText().getIntValue());

                if (openedOk)
                    appendMessage (T("Waiting for another app to connect to this socket.."));
            }
            else
            {
                DemoInterprocessConnection* newConnection = new DemoInterprocessConnection (*this);

                openedOk = newConnection->createPipe (pipeName->getText());

                if (openedOk)
                {
                    appendMessage (T("Waiting for another app to connect to this pipe.."));
                    activeConnections.add (newConnection);
                }
                else
                {
                    delete newConnection;
                }
            }
        }

        if (! openedOk)
        {
            modeSelector->setSelectedId (8);

            AlertWindow::showMessageBox (AlertWindow::WarningIcon,
                                         T("Interprocess Comms Demo"),
                                         T("Failed to open the socket or pipe..."));
        }
    }

    void appendMessage (const String& message)
    {
        incomingMessages->setCaretPosition (INT_MAX);
        incomingMessages->insertTextAtCaret (message + T("\n"));
        incomingMessages->setCaretPosition (INT_MAX);
    }

    //==============================================================================
    class DemoInterprocessConnection  : public InterprocessConnection
    {
        InterprocessCommsDemo& owner;
        int ourNumber;

    public:
        DemoInterprocessConnection (InterprocessCommsDemo& owner_)
            : InterprocessConnection (true),
              owner (owner_)
        {
            static int totalConnections = 0;
            ourNumber = ++totalConnections;
        }

        ~DemoInterprocessConnection()
        {
        }

        void connectionMade()
        {
            owner.appendMessage (T("Connection #") + String (ourNumber) + T(" - connection started"));
        }

        void connectionLost()
        {
            owner.appendMessage (T("Connection #") + String (ourNumber) + T(" - connection lost"));
        }

        void messageReceived (const MemoryBlock& message)
        {
            owner.appendMessage (T("Connection #") + String (ourNumber) + T(" - message received: ") + message.toString());
        }
    };

    //==============================================================================
    class DemoInterprocessConnectionServer   : public InterprocessConnectionServer
    {
        InterprocessCommsDemo& owner;

    public:
        DemoInterprocessConnectionServer (InterprocessCommsDemo& owner_)
            : owner (owner_)
        {
        }

        ~DemoInterprocessConnectionServer()
        {
        }

        InterprocessConnection* createConnectionObject()
        {
            DemoInterprocessConnection* newConnection = new DemoInterprocessConnection (owner);

            owner.activeConnections.add (newConnection);
            return newConnection;
        }
    };

    OwnedArray <DemoInterprocessConnection, CriticalSection> activeConnections;


    //==============================================================================
    juce_UseDebuggingNewOperator

private:
    ComboBox* modeSelector;
    TextEditor* sendText;
    TextButton* sendButton;
    TextEditor* incomingMessages;

    TextEditor* pipeName;
    TextEditor* socketNumber;
    TextEditor* socketHost;

    DemoInterprocessConnectionServer* server;
};


//==============================================================================
Component* createInterprocessCommsDemo()
{
    return new InterprocessCommsDemo();
}
