/**
 * Whiteboard session manager
 *
 * Authors:
 * David Yip <yipdw@rose-hulman.edu>
 * Bob Jamison (Pedro port)
 *
 * Copyright (c) 2005 Authors
 *
 * Released under GNU GPL, read the file 'COPYING' for more information
 */

#include <functional>
#include <algorithm>
#include <iostream>
#include <time.h>

#include <gtkmm.h>
#include <glibmm/i18n.h>

#include "xml/node.h"
#include "xml/repr.h"

#include "util/ucompose.hpp"

#include "xml/node-observer.h"

#include "pedro/pedrodom.h"

#include "ui/view/view-widget.h"

#include "application/application.h"
#include "application/editor.h"

#include "document-private.h"
#include "interface.h"
#include "sp-namedview.h"
#include "document.h"
#include "desktop.h"
#include "desktop-handles.h"

#include "jabber_whiteboard/invitation-confirm-dialog.h"
#include "jabber_whiteboard/message-verifier.h"
#include "jabber_whiteboard/session-manager.h"
#include "jabber_whiteboard/inkboard-document.h"
#include "jabber_whiteboard/defines.h"

#include "jabber_whiteboard/dialog/choose-desktop.h"

namespace Inkscape {

namespace Whiteboard {

//#########################################################################
//# S E S S I O N    M A N A G E R
//#########################################################################

SessionManager *sessionManagerInstance = NULL;

void SessionManager::showClient()
{
	SessionManager::instance().gui.show();
}

SessionManager&
SessionManager::instance()
{
    if (!sessionManagerInstance)
        sessionManagerInstance = new SessionManager();
	return *sessionManagerInstance;
}

SessionManager::SessionManager() 
{
    getClient().addXmppEventListener(*this);

    this->CheckPendingInvitations = 
        Glib::signal_timeout().connect(sigc::mem_fun(
            *this, &SessionManager::checkInvitationQueue), 50);
}

SessionManager::~SessionManager()
{
    getClient().removeXmppEventListener(*this);
    getClient().disconnect();
}

/**
 * Initiates a shared session with a user or conference room.
 * 
 * \param to The recipient to which this desktop will be linked, specified as a JID.
 * \param type Type of the session; i.e. private message or group chat.
 */
void
SessionManager::initialiseSession(Glib::ustring const& to, State::SessionType type)
{

    SPDocument* doc = makeInkboardDocument(g_quark_from_static_string("xml"), "svg:svg", type, to);
    InkboardDocument* inkdoc = dynamic_cast< InkboardDocument* >(doc->rdoc);
    if(inkdoc == NULL) return;

    if(type == State::WHITEBOARD_PEER) 
    {
        ChooseDesktop dialog;
        int result = dialog.run();

        if(result == Gtk::RESPONSE_OK)
        {
            SPDesktop *desktop = dialog.getDesktop();

            if(desktop != NULL)
            {
                Inkscape::XML::Document *old_doc =
                    sp_desktop_document(desktop)->rdoc;
                inkdoc->root()->mergeFrom(old_doc->root(),"id");
            }
        }else { return; }
    }

    char * sessionId = createSessionId(10);

    inkdoc->setSessionId(sessionId);

    makeInkboardDesktop(doc);
    addSession(WhiteboardRecord(sessionId, inkdoc));

    inkdoc->startSessionNegotiation();


}

void
SessionManager::terminateSession(Glib::ustring const& sessionId)
{
    WhiteboardList::iterator i = whiteboards.begin();
    for(; i != whiteboards.end(); ++i) {
        if ((*i).first == sessionId) 
            break;
    }

    if (i != whiteboards.end()) {
        (*i).second->terminateSession();
        whiteboards.erase(i);
    }
}

void
SessionManager::addSession(WhiteboardRecord whiteboard)
{
    whiteboards.push_back(whiteboard);
}

InkboardDocument*
SessionManager::getInkboardSession(Glib::ustring const& sessionId)
{
    WhiteboardList::iterator i = whiteboards.begin();
    for(; i != whiteboards.end(); ++i) {
        if ((*i).first == sessionId) {
            return (*i).second;
        }
    }
    return NULL;
}

void
SessionManager::processXmppEvent(const Pedro::XmppEvent &event)
{
    int type = event.getType();

    switch (type) {
        case Pedro::XmppEvent::EVENT_STATUS:
            {
            break;
            }
        case Pedro::XmppEvent::EVENT_ERROR:
            {
            break;
            }
        case Pedro::XmppEvent::EVENT_CONNECTED:
            {
            break;
            }
        case Pedro::XmppEvent::EVENT_DISCONNECTED:
            {
            break;
            }
        case Pedro::XmppEvent::EVENT_MUC_MESSAGE:
        case Pedro::XmppEvent::EVENT_MESSAGE:
            {
            Pedro::Element *root = event.getDOM();

            if (root && root->getTagAttribute("wb", "xmlns") == Vars::INKBOARD_XMLNS)
                processWhiteboardEvent(event);

            break;
            }
        case Pedro::XmppEvent::EVENT_PRESENCE:
            {
            break;
            }
        case Pedro::XmppEvent::EVENT_MUC_JOIN:
            {
            break;
            }
        case Pedro::XmppEvent::EVENT_MUC_LEAVE:
            {
            break;
            }
        case Pedro::XmppEvent::EVENT_MUC_PRESENCE:
            {
            break;
            }
        default:
            {
            break;
            }
    }
}

/**
 * Handles all incoming messages from pedro within a valid namespace, CONNECT_REQUEST messages
 * are handled here, as they have no InkboardDocument to be handled from, all other messages
 * are passed to their appropriate Inkboard document, which is identified by the 'session' 
 * attribute of the 'wb' element
 *
 */
void
SessionManager::processWhiteboardEvent(Pedro::XmppEvent const& event)
{
    Pedro::Element* root = event.getDOM();
    if (root == NULL) {
	g_warning("Received null DOM; ignoring message.");
	return;
    }

    Pedro::DOMString session = root->getTagAttribute("wb", "session");
    Pedro::DOMString type = root->getTagAttribute("message", "type");
    Pedro::DOMString domwrapper = root->getFirstChild()->getFirstChild()->getFirstChild()->getName();

    if (session.empty()) {
        g_warning("Received incomplete Whiteboard message, missing session identifier; ignoring message.");
        return;
    }

    if(root->exists(Message::CONNECT_REQUEST) && type == State::WHITEBOARD_PEER)
    {
        handleIncomingInvitation(Invitation(event.getFrom(),session));

    }else
    { 
        Message::Wrapper wrapper = static_cast< Message::Wrapper >(domwrapper);
        InkboardDocument* doc = getInkboardSession(session);

        if(doc != NULL)
            doc->recieve(wrapper, root->getFirstChild());
    }
}

char*
SessionManager::createSessionId(int size)
{
    // Create a random session identifier
    char * randomString = (char*) malloc (size);
    for (int n=0; n<size; n++)
        randomString[n]=rand()%26+'a';
    randomString[size+1]='\0';

    return randomString;
}

/**
 * Adds an invitation to a queue to be executed in SessionManager::_checkInvitationQueue()
 * as when this method is called, we're still executing in Pedro's context, which causes 
 * issues when we run a dialog main loop.
 *
 */
void
SessionManager::handleIncomingInvitation(Invitation invitation)
{
    // don't insert duplicate invitations
    if (std::find(invitations.begin(),invitations.end(),invitation) != invitations.end())
        return;

    invitations.push_back(invitation);

}

bool
SessionManager::checkInvitationQueue()
{
    // The user is currently busy with an action.  Defer invitation processing 
    // until the user is free.
    int x, y;
    Gdk::ModifierType mt;
    Gdk::Display::get_default()->get_pointer(x, y, mt);
    if (mt & GDK_BUTTON1_MASK) 
        return true;

    if (invitations.size() > 0) 
    {
        // There's an invitation to process; process it.
	Invitation invitation = invitations.front();
        Glib::ustring from = invitation.first;
        Glib::ustring sessionId = invitation.second;

	Glib::ustring primary = 
            "<span weight=\"bold\" size=\"larger\">" + 
            String::ucompose(_("<b>%1</b> has invited you to a whiteboard session."), from) + 
            "</span>\n\n" + 
            String::ucompose(_("Do you wish to accept <b>%1</b>'s whiteboard session invitation?"), from);

        InvitationConfirmDialog dialog(primary);

        dialog.add_button(_("Accept invitation"), Dialog::ACCEPT_INVITATION);
        dialog.add_button(_("Decline invitation"), Dialog::DECLINE_INVITATION);

        Dialog::DialogReply reply = static_cast< Dialog::DialogReply >(dialog.run());


        SPDocument* doc = makeInkboardDocument(g_quark_from_static_string("xml"), "svg:svg", State::WHITEBOARD_PEER, from);

        InkboardDocument* inkdoc = dynamic_cast< InkboardDocument* >(doc->rdoc);
        if(inkdoc == NULL) return true;

        inkdoc->handleState(State::INITIAL,State::CONNECTING);
        inkdoc->setSessionId(sessionId);
        addSession(WhiteboardRecord(sessionId, inkdoc));

        switch (reply) {

            case Dialog::ACCEPT_INVITATION:{
                inkdoc->send(from, Message::PROTOCOL,Message::ACCEPT_INVITATION);
                makeInkboardDesktop(doc);
                break; }

            case Dialog::DECLINE_INVITATION: default: {
                inkdoc->send(from, Message::PROTOCOL,Message::DECLINE_INVITATION);
                terminateSession(sessionId);
                break; }
        }

        invitations.pop_front();

    }

    return true;
}

//#########################################################################
//# HELPER FUNCTIONS
//#########################################################################

SPDocument*
makeInkboardDocument(int code, gchar const* rootname, State::SessionType type, Glib::ustring const& to)
{
    SPDocument* doc;

    InkboardDocument* rdoc = new InkboardDocument(g_quark_from_static_string("xml"), type, to);
    rdoc->setAttribute("version", "1.0");
    rdoc->setAttribute("standalone", "no");
    XML::Node *comment = rdoc->createComment(" Created with Inkscape (http://www.inkscape.org/) ");
    rdoc->appendChild(comment);
    GC::release(comment);

    XML::Node* root = rdoc->createElement(rootname);
    rdoc->appendChild(root);
    GC::release(root);

    Glib::ustring name = String::ucompose(
        _("Inkboard session (%1 to %2)"), SessionManager::instance().getClient().getJid(), to);

    doc = sp_document_create(rdoc, NULL, NULL, name.c_str(), TRUE);
    g_return_val_if_fail(doc != NULL, NULL);

    return doc;
}

// TODO: When the switchover to the new GUI is complete, this function should go away
// and be replaced with a call to Inkscape::NSApplication::Editor::createDesktop.  
// It currently only exists to correctly mimic the desktop creation functionality
// in file.cpp.
//
// \see sp_file_new
SPDesktop*
makeInkboardDesktop(SPDocument* doc)
{
    SPDesktop* dt;

    if (NSApplication::Application::getNewGui()) 
        dt = NSApplication::Editor::createDesktop(doc);

    else 
    {
        SPViewWidget *dtw = sp_desktop_widget_new(sp_document_namedview(doc, NULL));
        g_return_val_if_fail(dtw != NULL, NULL);
        sp_document_unref(doc);

        sp_create_window(dtw, TRUE);
        dt = static_cast<SPDesktop*>(dtw->view);
        sp_namedview_window_from_document(dt);
        sp_namedview_update_layers_from_document(dt);
    }

    return dt;
}

}  // namespace Whiteboard
 
}  // namespace Inkscape


/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
