//***************************************************************
// CLass: DirtyListExecutorOSCOSC
//
// Description:
//
//
// Author: Chris Browet <cbro@semperpax.com> (C) 2010
//
// Copyright: See COPYING file that comes with this distribution
//
//******************************************************************

#include "Global.h"
#include "DirtyListExecutorOSC.h"

#include "MainWindow.h"
#include "Features.h"
#include "DownloadOSM.h"
#include "MerkaartorPreferences.h"
#include "Command.h"
#include "Utils.h"

#include <QMessageBox>
#include <QDebug>
#include <QProgressDialog>
#include <QRegExp>

extern int glbAdded, glbUpdated, glbDeleted;

DirtyListExecutorOSC::DirtyListExecutorOSC(Document* aDoc, const DirtyListBuild& aFuture)
    : DirtyListVisit(aDoc, aFuture, false)
    , Done(0)
    , theDownloader(0)
{
}

DirtyListExecutorOSC::DirtyListExecutorOSC(Document* aDoc, const DirtyListBuild& aFuture, const ChangesetInfo& info, OsmServer server, int aTasks)
: DirtyListVisit(aDoc, aFuture, false), changesetInfo(info), Tasks(aTasks), Done(0), server(server), theDownloader(0)
{
    theDownloader = new Downloader(server);
}

DirtyListExecutorOSC::~DirtyListExecutorOSC()
{
    delete theDownloader;
}


int DirtyListExecutorOSC::sendRequest(const QString& Method, const QString& URL, const QString& Data, QString& Rcv)
{
    if (inError())
        return false;

    int rCode = -1;

    QMessageBox::StandardButton theChoice = QMessageBox::Retry;
    while (theChoice == QMessageBox::Retry) {
        QUrl theUrl(URL);
        if (!theDownloader->request(Method,theUrl,Data))
        {
            qDebug() << QString("Upload error: request (%1); Server message is '%2'").arg(theDownloader->resultCode()).arg(theDownloader->resultText());
            if (theDownloader->resultCode() == 401) {
                QMessageBox::warning(Progress,tr("Error uploading request"),
                    tr("Please check your username and password in the Preferences menu"));
                theChoice = QMessageBox::Abort;
            } else {
                QString msg = tr("There was an error uploading this request (%1)\nServer message is '%2'").arg(theDownloader->resultCode()).arg(theDownloader->resultText());
                if (!theDownloader->errorText().isEmpty())
                    msg += tr("\nAPI message is '%1'").arg(theDownloader->errorText());
                theChoice = QMessageBox::warning(Progress,tr("Error uploading request"), msg, QMessageBox::Abort | QMessageBox::Retry);
                continue;
            }
        }

        QByteArray Content = theDownloader->content();
        Rcv = QString::fromUtf8(Content.data());
        rCode = theDownloader->resultCode();

        qDebug() << QString("Upload: rCode (%1); Msg (%2)").arg(rCode).arg(Rcv);

        if (rCode == 200)
            break;

        theChoice = QMessageBox::warning(Progress,tr("Error uploading request"),
                                         tr(
                                                 "There was an error uploading this request (%1)\n\"%2\"\n"
                                                 "Please redownload the problematic feature to handle the conflict."
                                                 ).arg(rCode).arg(Rcv),
                                         QMessageBox::Abort | QMessageBox::Retry);
    }
    if (theChoice == QMessageBox::Abort) {
        errorAbort = true;
        return rCode;
    }
    return rCode;
}

QByteArray DirtyListExecutorOSC::getChanges()
{
    Progress = new QProgressDialog(0);
    Progress->setWindowTitle(tr("Checking changes..."));
    Progress->setWindowFlags(Progress->windowFlags() & ~Qt::WindowContextHelpButtonHint);
    Progress->setWindowFlags(Progress->windowFlags() | Qt::MSWindowsFixedSizeDialogHint);
    Progress->setWindowModality(Qt::WindowModal);
    Progress->setMinimumDuration(0);
    Progress->setMaximum(Tasks+2);
    Progress->show();

    OscBuffer.buffer().clear();
    OscBuffer.open(QIODevice::WriteOnly);

    OscStream.setDevice(&OscBuffer);
    OscStream.writeStartDocument();

    OscStream.writeStartElement("osmChange ");
    OscStream.writeAttribute("version", "0.6");
    OscStream.writeAttribute("generator", QString("Merkaartor %1").arg(BuildMetadata::VERSION));

    runVisit();

    SAFE_DELETE(Progress)

    OscStream.writeEndDocument();
    OscBuffer.close();

    return OscBuffer.buffer();
}

bool DirtyListExecutorOSC::executeChanges(QWidget* aParent)
{
    bool ok = true;

#ifndef _MOBILE
    MainWindow* main = dynamic_cast<MainWindow*>(aParent);
    main->createProgressDialog();

    Progress = main->getProgressDialog();
    if (Progress) {
        Progress->setMaximum(Tasks);
        Progress->setWindowTitle(QApplication::translate("Downloader", "Uploading..."));
    }

    QProgressBar* Bar = main->getProgressBar();
    Bar->setTextVisible(false);

    QLabel* Lbl = main->getProgressLabel();

    if (Progress)
        Progress->show();

    if (theDownloader)
        theDownloader->setAnimator(Progress,Lbl,Bar,false);

    if ((ok = start()))
    {
        OscBuffer.buffer().clear();
        OscBuffer.open(QIODevice::WriteOnly);
        OscStream.setDevice(&OscBuffer);
        OscStream.writeStartDocument();

        OscStream.writeStartElement("osmChange ");
        OscStream.writeAttribute("version", "0.6");
        OscStream.writeAttribute("generator", QString("Merkaartor %1").arg(BuildMetadata::VERSION));

        Lbl->setText(QApplication::translate("Downloader","Preparing changes"));
        if ((ok = runVisit())) {
            Lbl->setText(QApplication::translate("Downloader","Waiting for server response"));
            ok = stop();
        }
    }
    main->deleteProgressDialog();
#endif

    return ok;
}

bool DirtyListExecutorOSC::start()
{
    ChangeSetId.clear();
    Progress->setValue(++Done);
    qDebug() << QString("OPEN changeset");

    Progress->setLabelText(tr("OPEN changeset"));
    QEventLoop L; L.processEvents(QEventLoop::ExcludeUserInputEvents);

    QString DataIn;
    {
        QXmlStreamWriter stream(&DataIn);
        stream.writeStartElement("osm");
        stream.writeStartElement("changeset");
        stream.writeStartElement("tag");
        stream.writeAttribute("k", "created_by");
        stream.writeAttribute("v",
                QString("Merkaartor %1 (%2)").arg(BuildMetadata::VERSION).arg(QLocale::system().name().split("_")[0]));
        stream.writeEndElement();
        if (changesetInfo.comment.length()) {
            stream.writeStartElement("tag");
            stream.writeAttribute("k", "comment");
            stream.writeAttribute("v", changesetInfo.comment);
            stream.writeEndElement();
        }
        if (changesetInfo.source.length()) {
            stream.writeStartElement("tag");
            stream.writeAttribute("k", "source");
            stream.writeAttribute("v", changesetInfo.source);
            stream.writeEndElement();
        }
        stream.writeEndDocument();
    }
    qDebug() << DataIn;

    QString DataOut;
    QString URL = theDownloader->getURLToOpenChangeSet();
    if (sendRequest("PUT",URL,DataIn, DataOut) != 200)
        return false;

    ChangeSetId = DataOut;

    return true;
}

bool DirtyListExecutorOSC::stop()
{
    QString DataIn, DataOut;
    QString errFeat;

    OscStream.writeEndDocument();
    OscBuffer.close();
    qDebug() << OscBuffer.buffer();
    QString URL = theDownloader->getURLToUploadDiff(ChangeSetId);
    switch (sendRequest("POST", URL, QString::fromUtf8(OscBuffer.buffer().data()), DataOut)) {
    case 200: {
        QDomDocument resDoc;
        if (resDoc.setContent(DataOut)) {

            QDomNodeList nl = resDoc.elementsByTagName("diffResult");
            if (nl.size()) {
                QDomElement resRoot = nl.at(0).toElement();
                QDomElement c = resRoot.firstChildElement();
                while (!c.isNull()) {
                    IFeature::FeatureType aType = IFeature::FeatureType::Uninitialized;
                    if (c.tagName() == "node")
                        aType = IFeature::Point;
                    else if (c.tagName() == "way")
                        aType = IFeature::LineString;
                    else if (c.tagName() == "relation")
                        aType = IFeature::OsmRelation;
                    else {
                        qDebug() << "Unknown element found in response.";
                    }

                    Feature* F = theDocument->getFeature(IFeature::FId(aType, c.attribute("old_id").toLongLong()));
                    if (F) {
                        F->setId(IFeature::FId(aType, c.attribute("new_id").toLongLong()));
                        F->setVersionNumber(c.attribute("new_version").toInt());
                        F->setLastUpdated(Feature::OSMServer);
                        F->setUser("me");
                        F->setTime(QDateTime::currentDateTime());

                        if (!g_Merk_Frisius) {
                            F->layer()->remove(F);
                            document()->getUploadedLayer()->add(F);
                        }
                        F->setUploaded(true);
                        F->setDirtyLevel(0);

                    } else
                        qDebug() << "Feature not found in diff upload result:" << c.attribute("old_id");

                    c = c.nextSiblingElement();
                }
                theDocument->history().cleanup();
            }
        }
        break;
    }

    case 409: {    // Confilct
//            QRegExp rx(".*node.*(\\d+)", Qt::CaseInsensitive);
//            if (rx.indexIn(DataOut) > -1) {
//                errFeat = "node_" + rx.cap(1);
//            } else {
//                QRegExp rx(".*way.*(\\d+)", Qt::CaseInsensitive);
//                if (rx.indexIn(DataOut) > -1) {
//                    errFeat = "way_" + rx.cap(1);
//                } else {
//                    QRegExp rx(".*relation.*(\\d+)", Qt::CaseInsensitive);
//                    if (rx.indexIn(DataOut) > -1) {
//                        errFeat = "rel_" + rx.cap(1);
//                    }
//                }
//            }
//            qDebug() << errFeat;
            break;
        }
    case 410: {    // Gone
//            QRegExp rx(".*node.*(\\d+)", Qt::CaseInsensitive);
//            if (rx.indexIn(DataOut) > -1) {
//                errFeat = "node_" + rx.cap(1);
//            } else {
//                QRegExp rx(".*way.*(\\d+)", Qt::CaseInsensitive);
//                if (rx.indexIn(DataOut) > -1) {
//                    errFeat = "way_" + rx.cap(1);
//                } else {
//                    QRegExp rx(".*relation.*(\\d+)", Qt::CaseInsensitive);
//                    if (rx.indexIn(DataOut) > -1) {
//                        errFeat = "rel_" + rx.cap(1);
//                    }
//                }
//            }
//            qDebug() << errFeat;
            break;
        }

    default:
        break;
    }

    qDebug() << QString("CLOSE changeset");

    Progress->setLabelText(tr("CLOSE changeset"));
    QEventLoop L; L.processEvents(QEventLoop::ExcludeUserInputEvents);

    URL = theDownloader->getURLToCloseChangeSet(ChangeSetId);
    QUrl theUrl(URL);
    theDownloader->setAnimator(NULL, NULL, NULL, false);
    if (!theDownloader->request("PUT",theUrl,DataIn)) {
        QMessageBox::warning(NULL, tr("Changeset could not be closed."), tr("An unknown error has occurred. It might already be closed, or will be closed automatically. If you want to be sure, please, check manually on the osm.org website."));
    }

    return true;
}

void DirtyListExecutorOSC::OscCreate(Feature* F)
{
    if (LastAction != "create") {
        if (!LastAction.isEmpty())
            OscStream.writeEndElement();
        OscStream.writeStartElement("create");
        LastAction = "create";
    }

    F->toXML(OscStream, Progress, true, ChangeSetId);
}

void DirtyListExecutorOSC::OscModify(Feature* F)
{
    if (LastAction != "modify") {
        if (!LastAction.isEmpty())
            OscStream.writeEndElement();
        OscStream.writeStartElement("modify");
        LastAction = "modify";
    }

    F->toXML(OscStream, Progress, true, ChangeSetId);
}

void DirtyListExecutorOSC::OscDelete(Feature* F)
{
    if (LastAction != "delete") {
        if (!LastAction.isEmpty())
            OscStream.writeEndElement();
        OscStream.writeStartElement("delete");
        LastAction = "modify";
    }

    F->toXML(OscStream, Progress, true, ChangeSetId);
}



bool DirtyListExecutorOSC::addRelation(Relation *F)
{
    qDebug() << QString("ADD relation %1").arg(F->id().numId);

    Progress->setLabelText(tr("ADD relation %1").arg(F->id().numId) + userName(F));
    QEventLoop L; L.processEvents(QEventLoop::ExcludeUserInputEvents);

    OscCreate(F);

    return false;
}

bool DirtyListExecutorOSC::addRoad(Way *F)
{
    Progress->setValue(++Done);

    qDebug() << QString("ADD way %1").arg(F->id().numId);

    Progress->setLabelText(tr("ADD way %1").arg(F->id().numId) + userName(F));
    QEventLoop L; L.processEvents(QEventLoop::ExcludeUserInputEvents);

    OscCreate(F);

    return false;
}


bool DirtyListExecutorOSC::addPoint(Node* F)
{
    Progress->setValue(++Done);

    qDebug() << QString("ADD node %1").arg(F->id().numId);

    Progress->setLabelText(tr("ADD node %1").arg(F->id().numId) + userName(F));
    QEventLoop L; L.processEvents(QEventLoop::ExcludeUserInputEvents);

    OscCreate(F);

    return false;
}



bool DirtyListExecutorOSC::updateRelation(Relation* F)
{
    Progress->setValue(++Done);

    qDebug() << QString("UPDATE relation %1").arg(F->id().numId);

    Progress->setLabelText(tr("UPDATE relation %1").arg(F->id().numId) + userName(F));
    QEventLoop L; L.processEvents(QEventLoop::ExcludeUserInputEvents);

    OscModify(F);

    return false;
}


bool DirtyListExecutorOSC::updateRoad(Way* F)
{
    Progress->setValue(++Done);

    qDebug() << QString("UPDATE way %1").arg(F->id().numId);

    Progress->setLabelText(tr("UPDATE way %1").arg(F->id().numId) + userName(F));
    QEventLoop L; L.processEvents(QEventLoop::ExcludeUserInputEvents);

    OscModify(F);

    return false;
}

bool DirtyListExecutorOSC::updatePoint(Node* F)
{
    Progress->setValue(++Done);

    qDebug() << QString("UPDATE node %1").arg(F->id().numId);

    Progress->setLabelText(tr("UPDATE node %1").arg(F->id().numId) + userName(F));
    QEventLoop L; L.processEvents(QEventLoop::ExcludeUserInputEvents);

    OscModify(F);

    return false;
}

bool DirtyListExecutorOSC::erasePoint(Node *F)
{
    Progress->setValue(++Done);

    qDebug() << QString("REMOVE node %1").arg(F->id().numId);

    Progress->setLabelText(tr("REMOVE node %1").arg(F->id().numId) + userName(F));
    QEventLoop L; L.processEvents(QEventLoop::ExcludeUserInputEvents);

    OscDelete(F);

    return false;
}

bool DirtyListExecutorOSC::eraseRoad(Way *F)
{
    Progress->setValue(++Done);

    qDebug() << QString("REMOVE way %1").arg(F->id().numId);

    Progress->setLabelText(tr("REMOVE way %1").arg(F->id().numId) + userName(F));
    QEventLoop L; L.processEvents(QEventLoop::ExcludeUserInputEvents);

    OscDelete(F);

    return false;
}

bool DirtyListExecutorOSC::eraseRelation(Relation *F)
{
    Progress->setValue(++Done);

    qDebug() << QString("REMOVE relation %1").arg(F->id().numId);

    Progress->setLabelText(tr("REMOVE relation %1").arg(F->id().numId) + userName(F));
    QEventLoop L; L.processEvents(QEventLoop::ExcludeUserInputEvents);

    OscDelete(F);

    return false;
}
