/*
 * googleftmanager.cpp
 * Copyright (C) 2006  Remko Troncon
 *
 * This program is free software; you can redistribute it and/or
 * modify it 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.
 *
 * This program 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 */

// libjingle includes
#define POSIX
#include "talk/base/sigslot.h"
#include "talk/xmpp/constants.h"
#include "talk/xmpp/jid.h"
#include "talk/xmllite/xmlelement.h"
#include "talk/xmllite/xmlprinter.h"
#include "talk/base/network.h"
#include "talk/p2p/base/session.h"
#include "talk/p2p/base/sessionmanager.h"
#include "talk/base/helpers.h"
#include "talk/p2p/client/basicportallocator.h"
#include "talk/p2p/base/sessionclient.h"
#include "talk/p2p/client/sessionsendtask.h"
#include "talk/p2p/client/httpportallocator.h"
#include "talk/base/physicalsocketserver.h"
#include "talk/base/thread.h"
#include "talk/base/socketaddress.h"
#include "talk/session/fileshare/fileshare.h"

#include <QString>
#include <QDomElement>
#include <QDir>

#include "xmpp_xmlcommon.h"
#include "googleftmanager.h"

// Should change in the future
#define JINGLE_NS "http://www.google.com/session"
#define JINGLEINFO_NS "google:jingleinfo"

using namespace XMPP;

// ----------------------------------------------------------------------------

/**
 * \class GoogleJingleInfoTask
 * A class for retrieving information from the server about Google's
 * Jingle support.
 */
class GoogleJingleInfoTask : public Task
{
public:
    GoogleJingleInfoTask(Task* parent) : Task(parent) {
    }

    void onGo() {
        QDomElement iq = createIQ(doc(), "get", "", id());
        QDomElement query = doc()->createElement("query");
        query.setAttribute("xmlns", JINGLEINFO_NS);
        iq.appendChild(query);
        send(iq);
    }

    bool take(const QDomElement& x) {
        if(!iqVerify(x, "", id()))
            return false;

        if(x.attribute("type") == "result") {
            // TODO:Parse info
            setSuccess();
        }
        else {
            setError(x);
        }
        return true;
    }
};


// ----------------------------------------------------------------------------

/**
 * \class JingleIQResponder
 * \brief A task that ensures that we don't send an unsupported error
 */
class JingleIQResponder : public XMPP::Task
{
public:
    JingleIQResponder(XMPP::Task * parent) : Task(parent) {}
    ~JingleIQResponder() {}

    bool take(const QDomElement& e) {
        if(e.tagName() != "iq")
            return false;
        QDomElement first = e.firstChild().toElement();
        if (!first.isNull() && first.attribute("xmlns") == JINGLE_NS) {
            return true;
        }
        return false;
    }
};

// ----------------------------------------------------------------------------

/**
 * \brief A class for handling signals from libjingle.
 */

class GoogleSessionListener : public sigslot::has_slots<>
{
public:
    GoogleSessionListener(GoogleFTManager* manager);
    void fileShareSessionCreated(cricket::FileShareSession *);
    void sendStanza(const buzz::XmlElement *stanza);
    void signalingReady();

private:
    GoogleFTManager* manager_;
};


GoogleSessionListener::GoogleSessionListener(GoogleFTManager* manager) : manager_(manager)
{
}

void GoogleSessionListener::sendStanza(const buzz::XmlElement *stanza)
{
    QString st(stanza->Str().c_str());
    st.replace("<sta:","<");
    st.replace("</sta:","</");
    st.replace("<cli:","<");
    st.replace("</cli:","</");
    st.replace(":cli=","=");
    st.replace("xmlns:sta","xmlns");
    manager_->sendStanza(st);
}

void GoogleSessionListener::fileShareSessionCreated(cricket::FileShareSession* session)
{
    new GoogleFileTransfer(session, manager_);
}

void GoogleSessionListener::signalingReady()
{
    manager_->session_manager_->OnSignalingReady();
}

// ----------------------------------------------------------------------------

/**
 * \brief A class for handling signals from libjingle.
 */
class GoogleFileTransferListener : public sigslot::has_slots<>
{
public:
    GoogleFileTransferListener(GoogleFileTransfer*);
    void stateChanged(cricket::FileShareState);
    void progressChanged(cricket::FileShareSession*);
    void resampleImage(std::string path, int width, int height, talk_base::HttpTransaction* trans);

private:
    GoogleFileTransfer* session_;
};

GoogleFileTransferListener::GoogleFileTransferListener(GoogleFileTransfer* s) : session_(s)
{
}

void GoogleFileTransferListener::stateChanged(cricket::FileShareState state)
{
    switch(state) {
        case cricket::FS_OFFER:
            emit session_->manager_->incomingFileTransfer(session_);
            break;

        case cricket::FS_TRANSFER:
            qDebug("Transfer started");
            break;

        case cricket::FS_COMPLETE:
            qDebug("Transfer complete");
            break;

        case cricket::FS_LOCAL_CANCEL:
        case cricket::FS_REMOTE_CANCEL:
            qDebug("FS_CANCEL");
            break;

        case cricket::FS_FAILURE:
            qDebug("FS_FAILURE");
            break;
    }
}

void GoogleFileTransferListener::progressChanged(cricket::FileShareSession* sess)
{
    size_t progress;
    std::string itemname;
    if (sess->GetProgress(progress) && sess->GetCurrentItemName(&itemname)) {
        emit session_->progressChanged(progress,QString(itemname.c_str()));
    }
}

void GoogleFileTransferListener::resampleImage(std::string, int, int, talk_base::HttpTransaction* trans)
{
    // From PCP
    session_->session_->ResampleComplete(NULL, trans, false);
}

// ----------------------------------------------------------------------------

GoogleFileTransfer::GoogleFileTransfer(cricket::FileShareSession* s, GoogleFTManager* manager) : session_(s), manager_(manager)
{
    listener_ = new GoogleFileTransferListener(this);
    session_->SignalState.connect(listener_, &GoogleFileTransferListener::stateChanged);
    session_->SignalNextFile.connect(listener_, &GoogleFileTransferListener::progressChanged);
    session_->SignalUpdateProgress.connect(listener_, &GoogleFileTransferListener::progressChanged);
    session_->SignalResampleImage.connect(listener_, &GoogleFileTransferListener::resampleImage);

    // Temporary
#ifdef Q_OS_MAC
    QDir home(QDir::homePath() + "/Desktop");
#else
    QDir home = QDir::home();
#endif
    QDir dir(home.path() + "/googletalk_files");
    if(!dir.exists())
        home.mkdir("googletalk_files");
    session_->SetLocalFolder(dir.path().toStdString());
}

XMPP::Jid GoogleFileTransfer::peer() const
{
    return Jid(session_->jid().BareJid().Str().c_str());
}

QString GoogleFileTransfer::fileName() const
{
    return description();
}

QString GoogleFileTransfer::description() const
{
    QString description;
    if (session_->manifest()->size() == 1)
        description = QString("'%1'").arg(session_->manifest()->item(0).name.c_str());
    else if (session_->manifest()->GetFileCount() && session_->manifest()->GetFolderCount())
        description = QString("%1 files and %2 directories").arg(session_->manifest()->GetFileCount()).arg(session_->manifest()->GetFolderCount());
    else if (session_->manifest()->GetFileCount())
        description = QString("%1 files").arg(session_->manifest()->GetFileCount());
    else if (session_->manifest()->GetFolderCount())
        description = QString("%1 directories").arg(session_->manifest()->GetFolderCount());
    else
        description = "(Unknown)";
    return description;
}

qlonglong GoogleFileTransfer::fileSize() const
{
    size_t filesize;
    if (!session_->GetTotalSize(filesize))
        filesize = -1;
    return filesize;
}

void GoogleFileTransfer::accept(qlonglong, qlonglong)
{
    session_->Accept();
}

void GoogleFileTransfer::reject()
{
    session_->Decline();
}

void GoogleFileTransfer::cancel()
{
    session_->Cancel();
}

// ----------------------------------------------------------------------------

GoogleFTManager::GoogleFTManager(Client* client) : client_(client)
{
    initialized_ = false;
    connect(client_, SIGNAL(rosterRequestFinished(bool, int, const QString &)), SLOT(initialize()));
    connect(client_, SIGNAL(disconnected()), SLOT(deinitialize()));
}

void GoogleFTManager::initialize()
{
    if (initialized_)
        return;

    QString jid = ((ClientStream&) client_->stream()).jid().full();
    if (jid.isEmpty()) {
        qWarning("googleftmanager.cpp: Empty JID");
        return;
    }
    buzz::Jid j(jid.ascii()); // FIXME: Ascii is evil

    // Static stuff
    if (socket_server_ == NULL) {
        //talk_base::InitializeSSL();
        cricket::InitRandom(j.Str().c_str(),j.Str().size());
        socket_server_ = new talk_base::PhysicalSocketServer();
        thread_ = new talk_base::Thread(socket_server_);
        talk_base::ThreadManager::SetCurrent(thread_);
        thread_->Start();
        network_manager_ = new talk_base::NetworkManager();
        port_allocator_.reset(new cricket::HttpPortAllocator(network_manager_, "psi"));
    }

    listener_ = new GoogleSessionListener(this);
    session_manager_.reset(new cricket::SessionManager(port_allocator_.get(), NULL));
    session_manager_->SignalOutgoingMessage.connect(listener_, &GoogleSessionListener::sendStanza);
    //session_manager_->SignalRequestSignaling.connect(session_manager_, &cricket::SessionManager::OnSignalingReady);
    session_manager_->SignalRequestSignaling.connect(listener_, &GoogleSessionListener::signalingReady);
    file_share_session_client_.reset(new cricket::FileShareSessionClient(session_manager_.get(), j, "psi"));
    file_share_session_client_->SignalFileShareSessionCreate.connect(listener_, &GoogleSessionListener::fileShareSessionCreated);
    session_manager_->AddClient(NS_GOOGLE_SHARE, file_share_session_client_.get());

    // Listen to incoming packets
    connect(client_,SIGNAL(xmlIncoming(const QString&)),SLOT(receiveStanza(const QString&)));

    // IQ Responder
    new JingleIQResponder(client_->rootTask());

    initialized_ = true;
}


void GoogleFTManager::deinitialize()
{
    if (!initialized_)
        return;

    // Stop listening to incoming packets
    disconnect(client_,SIGNAL(xmlIncoming(const QString&)),this,SLOT(receiveStanza(const QString&)));

    delete listener_;
    initialized_ = false;
}


GoogleFTManager::~GoogleFTManager()
{
}

void GoogleFTManager::sendStanza(const QString& stanza)
{
    client_->send(stanza);
}

void GoogleFTManager::receiveStanza(const QString& sstanza)
{
    // Add a namespace to the element (for libjingle to process correctly)
    QDomDocument doc;
    doc.setContent(sstanza);
    /*doc.documentElement().setAttribute("xmlns","jabber:client");
    QString stanza = doc.toString();
    buzz::XmlElement *e = buzz::XmlElement::ForStr(stanza.ascii());
    if (!session_manager_.get()->IsSessionMessage(e))
        return;*/

    QDomNode n = doc.documentElement().firstChild();
    bool ok = false;
    while (!n.isNull() && !ok) {
        QDomElement e = n.toElement();
        if (!e.isNull() && e.attribute("xmlns") == JINGLE_NS) {
            ok = true;
        }
        n = n.nextSibling();
    }
    if (!ok)
        return;
    buzz::XmlElement *e = buzz::XmlElement::ForStr(sstanza.ascii());

    session_manager_->OnIncomingMessage(e);
}

talk_base::PhysicalSocketServer* GoogleFTManager::socket_server_ = NULL;
talk_base::Thread *GoogleFTManager::thread_ = NULL;
talk_base::NetworkManager* GoogleFTManager::network_manager_ = NULL;
talk_base::scoped_ptr<cricket::HttpPortAllocator> GoogleFTManager::port_allocator_;
