/*
 * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM)
 *
 * Copyright (C) 2013 Open Education Foundation
 *
 * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour
 * l'Education Numérique en Afrique (GIP ENA)
 *
 * This file is part of OpenBoard.
 *
 * OpenBoard 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, version 3 of the License,
 * with a specific linking exception for the OpenSSL project's
 * "OpenSSL" library (or with modified versions of it that use the
 * same license as the "OpenSSL" library).
 *
 * OpenBoard 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 OpenBoard. If not, see <http://www.gnu.org/licenses/>.
 */




#include "UBWidgetUniboardAPI.h"

#include <QWebView>
#include <QDomDocument>
#include <QtGui>

#include "core/UB.h"
#include "core/UBApplication.h"
#include "core/UBSettings.h"

#include "document/UBDocumentProxy.h"

#include "board/UBBoardController.h"
#include "board/UBDrawingController.h"
#include "board/UBBoardPaletteManager.h"

#include "domain/UBGraphicsScene.h"
#include "domain/UBGraphicsWidgetItem.h"

#include "adaptors/UBThumbnailAdaptor.h"

#include "UBWidgetMessageAPI.h"
#include "frameworks/UBFileSystemUtils.h"
#include "core/UBDownloadManager.h"

#include "core/memcheck.h"

//Known extentions for files, add if you know more supported
const QString audioExtentions = ".mp3.wma.ogg";
const QString videoExtentions = ".avi.flv";
const QString imageExtentions = ".png.jpg.tif.bmp.tga";
const QString htmlExtentions = ".htm.html.xhtml";

//Allways use aliases instead of const char* itself
const QString imageAlias    = "image";
const QString imageAliasCap = "Image";
const QString videoAlias    = "video";
const QString videoAliasCap = "Video";
const QString audioAlias    = "audio";
const QString audioAliasCap = "Audio";

//Xml tag names
const QString tMainSection = "mimedata";
const QString tType = "type";
const QString tPath = "path";
const QString tMessage = "message";
const QString tReady = "ready";

const QString tMimeText = "text/plain";


//Name of path inside widget to store objects
const QString objectsPath = "objects";

UBWidgetUniboardAPI::UBWidgetUniboardAPI(UBGraphicsScene *pScene, UBGraphicsWidgetItem *widget)
    : QObject(pScene)
    , mScene(pScene)
    , mGraphicsWidget(widget)
    , mIsVisible(false)
    , mMessagesAPI(0)
    , mDatastoreAPI(0)
 {
    UBGraphicsW3CWidgetItem* w3CGraphicsWidget = dynamic_cast<UBGraphicsW3CWidgetItem*>(widget);

    if (w3CGraphicsWidget)
    {
        mMessagesAPI = new UBWidgetMessageAPI(w3CGraphicsWidget);
        mDatastoreAPI = new UBDatastoreAPI(w3CGraphicsWidget);
    }

    connect(UBDownloadManager::downloadManager(), SIGNAL(downloadFinished(bool,sDownloadFileDesc,QByteArray)), this, SLOT(onDownloadFinished(bool,sDownloadFileDesc,QByteArray)));
}


UBWidgetUniboardAPI::~UBWidgetUniboardAPI()
{
    // NOOP
}

QObject* UBWidgetUniboardAPI::messages()
{
    return mMessagesAPI;
}


QObject* UBWidgetUniboardAPI::datastore()
{
    return mDatastoreAPI;
}


void UBWidgetUniboardAPI::setTool(const QString& toolString)
{
    if (UBApplication::boardController->activeScene() != mScene)
        return;

    const QString lower = toolString.toLower();

    if (lower == "pen")
    {
        UBDrawingController::drawingController()->setStylusTool(UBStylusTool::Pen);
    }
    else if (lower == "marker")
    {
        UBDrawingController::drawingController()->setStylusTool(UBStylusTool::Marker);
    }
    else if (lower == "arrow")
    {
        UBDrawingController::drawingController()->setStylusTool(UBStylusTool::Selector);
    }
    else if (lower == "play")
    {
        UBDrawingController::drawingController()->setStylusTool(UBStylusTool::Play);
    }
    else if (lower == "line")
    {
        UBDrawingController::drawingController()->setStylusTool(UBStylusTool::Line);
    }
}


void UBWidgetUniboardAPI::setPenColor(const QString& penColor)
{
    if (UBApplication::boardController->activeScene() != mScene)
        return;

    UBSettings* settings = UBSettings::settings();

    bool conversionState = false;

    int index = penColor.toInt(&conversionState) - 1;

    if (conversionState && index > 0 && index <= 4)
    {
        UBApplication::boardController->setPenColorOnDarkBackground(settings->penColors(true).at(index - 1));
        UBApplication::boardController->setPenColorOnLightBackground(settings->penColors(false).at(index - 1));
    }
    else
    {
        QColor svgColor;
        svgColor.setNamedColor(penColor);
        if (svgColor.isValid())
        {
            UBApplication::boardController->setPenColorOnDarkBackground(svgColor);
            UBApplication::boardController->setPenColorOnLightBackground(svgColor);
        }
    }
}


void UBWidgetUniboardAPI::setMarkerColor(const QString& penColor)
{
    if (UBApplication::boardController->activeScene() != mScene)
        return;

    UBSettings* settings = UBSettings::settings();

    bool conversionState = false;

    int index = penColor.toInt(&conversionState);

    if (conversionState && index > 0 && index <= 4)
    {
        UBApplication::boardController->setMarkerColorOnDarkBackground(settings->markerColors(true).at(index - 1));
        UBApplication::boardController->setMarkerColorOnLightBackground(settings->markerColors(false).at(index - 1));
    }
    else
    {
        QColor svgColor;
        svgColor.setNamedColor(penColor);
        if (svgColor.isValid())
        {
            UBApplication::boardController->setMarkerColorOnDarkBackground(svgColor);
            UBApplication::boardController->setMarkerColorOnLightBackground(svgColor);
        }
    }
}


void UBWidgetUniboardAPI::addObject(QString pUrl, int width, int height, int x, int y, bool background)
{
    // not great, but make it easily scriptable --
    //
    // download url should be moved to the scene from the controller
    //

    if (UBApplication::boardController->activeScene() != mScene)
        return;

    UBApplication::boardController->downloadURL(QUrl(pUrl), QString(), QPointF(x, y), QSize(width, height), background);

}


void UBWidgetUniboardAPI::setBackground(bool pIsDark, bool pIsCrossed)
{
    if (mScene) {
        if (pIsCrossed)
            mScene->setBackground(pIsDark, UBPageBackground::crossed);
        else
            mScene->setBackground(pIsDark, UBPageBackground::plain);
    }
}


void UBWidgetUniboardAPI::moveTo(const qreal x, const qreal y)
{
    if (qIsNaN(x) || qIsNaN(y)
        || qIsInf(x) || qIsInf(y))
        return;

    if (mScene)
    mScene->moveTo(QPointF(x, y));
}


void UBWidgetUniboardAPI::drawLineTo(const qreal x, const qreal y, const qreal pWidth)
{
    if (qIsNaN(x) || qIsNaN(y) || qIsNaN(pWidth)
        || qIsInf(x) || qIsInf(y) || qIsInf(pWidth))
        return;

    if (mScene)
    mScene->drawLineTo(QPointF(x, y), pWidth, 
        UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Line);
}


void UBWidgetUniboardAPI::eraseLineTo(const qreal x, const qreal y, const qreal pWidth)
{
    if (qIsNaN(x) || qIsNaN(y) || qIsNaN(pWidth)
       || qIsInf(x) || qIsInf(y) || qIsInf(pWidth))
       return;

    if (mScene)
    mScene->eraseLineTo(QPointF(x, y), pWidth);
}


void UBWidgetUniboardAPI::clear()
{
    if (mScene)
            mScene->clearContent(UBGraphicsScene::clearItemsAndAnnotations);
}


void UBWidgetUniboardAPI::zoom(const qreal factor, const qreal x, const qreal y)
{
   if (qIsNaN(factor) || qIsNaN(x) || qIsNaN(y)
       || qIsInf(factor) || qIsInf(x) || qIsInf(y))
       return;


    if (UBApplication::boardController->activeScene() != mScene)
        return;

    UBApplication::boardController->zoom(factor, QPointF(x, y));
}


void UBWidgetUniboardAPI::centerOn(const qreal x, const qreal y)
{
   if (qIsNaN(x) || qIsNaN(y)
       || qIsInf(x) || qIsInf(y))
       return;

    if (UBApplication::boardController->activeScene() != mScene)
        return;

    UBApplication::boardController->centerOn(QPointF(x, y));
}


void UBWidgetUniboardAPI::move(const qreal x, const qreal y)
{
    if (qIsNaN(x) || qIsNaN(y)
        || qIsInf(x) || qIsInf(y))
        return;

    if (UBApplication::boardController->activeScene() != mScene)
        return;

    UBApplication::boardController->handScroll(x, y);
}


void UBWidgetUniboardAPI::addText(const QString& text, const qreal x, const qreal y, const int size, const QString& font
        , bool bold, bool italic)
{
    if (qIsNaN(x) || qIsNaN(y)
        || qIsInf(x) || qIsInf(y))
        return;

    if (UBApplication::boardController->activeScene() != mScene)
        return;

    if (mScene)
        mScene->addTextWithFont(text, QPointF(x, y), size, font, bold, italic);

}


int UBWidgetUniboardAPI::pageCount()
{
    if (mScene && mScene->document())
        return mScene->document()->pageCount();
    else
        return -1;
}


int UBWidgetUniboardAPI::currentPageNumber()
{
    // TODO UB 4.x widget find a better way to get the current page number

    if (UBApplication::boardController->activeScene() != mScene)
        return -1;

    return UBApplication::boardController->activeSceneIndex() + 1;
}

QString UBWidgetUniboardAPI::getObjDir()
{
    return mGraphicsWidget->getOwnFolder().toLocalFile() + "/" + objectsPath + "/";
}

void UBWidgetUniboardAPI::showMessage(const QString& message)
{
    UBApplication::boardController->showMessage(message, false);
}


QString UBWidgetUniboardAPI::pageThumbnail(const int pageNumber)
{
    if (UBApplication::boardController->activeScene() != mScene)
        return "";

    UBDocumentProxy *doc = UBApplication::boardController->selectedDocument();

    if (!doc)
        return "";

    if (pageNumber > doc->pageCount())
        return "";

    QUrl url = UBThumbnailAdaptor::thumbnailUrl(doc, pageNumber - 1);

    return url.toString();

}


void UBWidgetUniboardAPI::resize(qreal width, qreal height)
{
    if (qIsNaN(width) || qIsNaN(height)
        || qIsInf(width) || qIsInf(height))
        return;

    if (mGraphicsWidget)
    {
        mGraphicsWidget->resize(width, height);
    }
}


void UBWidgetUniboardAPI::setPreference(const QString& key, QString value)
{
    if (mGraphicsWidget)
    {
            mGraphicsWidget->setPreference(key, value);
    }
}


QString UBWidgetUniboardAPI::preference(const QString& key , const QString& pDefault)
{
    if (mGraphicsWidget && mGraphicsWidget->preferences().contains(key))
    {
        return mGraphicsWidget->preference(key);
    }
    else
    {
        return pDefault;
    }
}


QStringList UBWidgetUniboardAPI::preferenceKeys()
{
    QStringList keys;

    if (mGraphicsWidget)
        keys = mGraphicsWidget->preferences().keys();

    return keys;
}


QString UBWidgetUniboardAPI::uuid()
{
    if (mGraphicsWidget)
        return UBStringUtils::toCanonicalUuid(mGraphicsWidget->uuid());
    else
        return "";
}


QString UBWidgetUniboardAPI::locale()
{
    return QLocale().name();
}

QString UBWidgetUniboardAPI::lang()
{
    QString lang = QLocale().name();

    if (lang.length() > 2)
        lang[2] = QLatin1Char('-');

    return lang;
}

void UBWidgetUniboardAPI::returnStatus(const QString& method, const QString& status)
{
    QString msg = QString(tr("%0 called (method=%1, status=%2)")).arg("returnStatus").arg(method).arg(status);
    UBApplication::showMessage(msg);
}

void UBWidgetUniboardAPI::usedMethods(QStringList methods)
{
    // TODO: Implement this method
    foreach(QString method, methods)
    {

    }
}

void UBWidgetUniboardAPI::response(bool correct)
{
    Q_UNUSED(correct);
    // TODO: Implement this method
}

void UBWidgetUniboardAPI::sendFileMetadata(QString metaData)
{
    //  Build the QMap of metadata and send it to application
    QMap<QString, QString> qmMetaDatas;
    QDomDocument domDoc;
    domDoc.setContent(metaData);
    QDomElement rootElem = domDoc.documentElement();
    QDomNodeList children = rootElem.childNodes();
    for(int i=0; i<children.size(); i++){
        QDomNode dataNode = children.at(i);
        QDomElement keyElem = dataNode.firstChildElement("key");
        QDomElement valueElem = dataNode.firstChildElement("value");
        qmMetaDatas[keyElem.text()] = valueElem.text();
    }
    UBApplication::boardController->displayMetaData(qmMetaDatas);
}

void UBWidgetUniboardAPI::enableDropOnWidget(bool enable)
{
    if (mGraphicsWidget)
    {
        mGraphicsWidget->setAcceptDrops(enable);
    }
}

void UBWidgetUniboardAPI::ProcessDropEvent(QGraphicsSceneDragDropEvent *event)
{
    const QMimeData *pMimeData = event->mimeData();

    QString destFileName;
    QString contentType;
    bool downloaded = false;

    QGraphicsView *tmpView = mGraphicsWidget->scene()->views().at(0);
    QPoint dropPoint(mGraphicsWidget->mapFromScene(tmpView->mapToScene(event->pos().toPoint())).toPoint());
    Qt::DropActions dropActions = event->possibleActions();
    Qt::MouseButtons dropMouseButtons = event->buttons();
    Qt::KeyboardModifiers dropModifiers = event->modifiers();
    QMimeData *dropMimeData = new QMimeData;
    qDebug() << event->possibleActions();


    if (pMimeData->hasHtml()) { //Dropping element from web browser
        QString qsHtml = pMimeData->html();
        QString url = UBApplication::urlFromHtml(qsHtml);

        if(!url.isEmpty()) {
            QString str = "test string";

            QMimeData mimeData;
            mimeData.setData(tMimeText, str.toLatin1());

            sDownloadFileDesc desc;
            desc.dest = sDownloadFileDesc::graphicsWidget;
            desc.modal = true;
            desc.srcUrl = url;
            desc.currentSize = 0;
            desc.name = QFileInfo(url).fileName();
            desc.totalSize = 0; // The total size will be retrieved during the download

            desc.dropPoint = event->pos().toPoint(); //Passing pure event point. No modifications
            desc.dropActions = dropActions;
            desc.dropMouseButtons = dropMouseButtons;
            desc.dropModifiers = dropModifiers;

            registerIDWidget(UBDownloadManager::downloadManager()->addFileToDownload(desc));

        }

    } else  if (pMimeData->hasUrls()) { //Local file processing
        QUrl curUrl = pMimeData->urls().first();
        QString sUrl = curUrl.toString();

        if (sUrl.startsWith("file://") || sUrl.startsWith("/")) {
            QString fileName = curUrl.toLocalFile();
            QString extention = UBFileSystemUtils::extension(fileName);
            contentType = UBFileSystemUtils::mimeTypeFromFileName(fileName);

            if (supportedTypeHeader(contentType)) {
                destFileName = getObjDir() + QUuid::createUuid().toString() + "." + extention;

                if (!UBFileSystemUtils::copyFile(fileName, destFileName)) {
                    qDebug() << "can't copy from" << fileName << "to" << destFileName;
                    return;
                }
                downloaded = true;

            }
        }
    }
    qDebug() << destFileName;
    QString mimeText = createMimeText(downloaded, contentType, destFileName);
    dropMimeData->setData(tMimeText, mimeText.toLatin1());

    event->setMimeData(dropMimeData);
}

void UBWidgetUniboardAPI::onDownloadFinished(bool pSuccess, sDownloadFileDesc desc, QByteArray pData)
{
    //if widget recieves is waiting for this id then process
    if (!takeIDWidget(desc.id))
        return;

    if (!pSuccess) {
        qDebug() << "can't download the whole data. An error occured";
        return;
    }

    QString contentType = desc.contentTypeHeader;
    QString extention = UBFileSystemUtils::fileExtensionFromMimeType(contentType);

    if (!supportedTypeHeader(contentType)) {
        qDebug() << "actions for mime type" << contentType << "are not supported";
        return;
    }

    QString objDir = getObjDir();
    if (!QDir().exists(objDir)) {
        if (!QDir().mkpath(objDir)) {
            qDebug() << "can't create objects directory path. Check the permissions";
            return;
        }
    }

    QString destFileName = objDir + QUuid::createUuid().toString() + "." + extention;
    QFile destFile(destFileName);

    if (!destFile.open(QIODevice::WriteOnly)) {
        qDebug() << "can't open" << destFileName << "for wrighting";
        return;
    }

    if (destFile.write(pData) == -1) {
        qDebug() << "can't implement data writing";
        return;
    }

    QGraphicsView *tmpView = mGraphicsWidget->scene()->views().at(0);
    QPoint dropPoint(mGraphicsWidget->mapFromScene(tmpView->mapToScene(desc.dropPoint)).toPoint());

    QMimeData dropMimeData;
    QString mimeText = createMimeText(true, contentType, destFileName);
    dropMimeData.setData(tMimeText, mimeText.toLatin1());

    destFile.close();

    //To make js interpreter accept drop event we need to generate move event first.
    QDragMoveEvent pseudoMove(dropPoint, desc.dropActions, &dropMimeData, desc.dropMouseButtons, desc.dropModifiers);
    QApplication::sendEvent(mGraphicsWidget,&pseudoMove);

    QDropEvent readyEvent(dropPoint, desc.dropActions, &dropMimeData, desc.dropMouseButtons, desc.dropModifiers);
    //sending event to destination either it had been downloaded or not
    QApplication::sendEvent(mGraphicsWidget,&readyEvent);
    readyEvent.acceptProposedAction();
}

QString UBWidgetUniboardAPI::createMimeText(bool downloaded, const QString &mimeType, const QString &fileName)
{
    QString mimeXml;
    QXmlStreamWriter writer(&mimeXml);
    writer.setAutoFormatting(true);
    writer.writeStartDocument();
    writer.writeStartElement(tMainSection);

    writer.writeTextElement(tReady, boolToStr(downloaded));

    if (downloaded) {
        if (!mimeType.isEmpty()) {
            writer.writeTextElement(tType, mimeType);  //writing type of element
        }
        if (!QFile::exists(fileName)) {
            qDebug() << "file" << fileName << "doesn't exist";
            return QString();
        }

        QString relatedFileName = fileName;
        relatedFileName = relatedFileName.remove(mGraphicsWidget->getOwnFolder().toLocalFile() + "/");
        writer.writeTextElement(tPath, relatedFileName);   //writing path to created object
    }

    writer.writeEndElement();
    writer.writeEndDocument();

    return mimeXml;
}

bool UBWidgetUniboardAPI::supportedTypeHeader(const QString &typeHeader) const
{
    return     typeHeader.startsWith(imageAlias) || typeHeader.startsWith(imageAliasCap)
            || typeHeader.startsWith(audioAlias) || typeHeader.startsWith(audioAliasCap)
            || typeHeader.startsWith(videoAlias) || typeHeader.startsWith(videoAliasCap);
}

bool UBWidgetUniboardAPI::takeIDWidget(int id)
{
    if (webDownloadIds.contains(id)) {
        webDownloadIds.removeAll(id);
        return true;
    }
    return false;
}

bool UBWidgetUniboardAPI::isDropableData(const QMimeData *pMimeData) const
{
    QString fileName = QString();
    if (pMimeData->hasHtml()) {
        fileName = UBApplication::urlFromHtml(pMimeData->html());
        if (fileName.isEmpty())
            return false;
    } else if (pMimeData->hasUrls()) {
        fileName = pMimeData->urls().at(0).toLocalFile();
        if (fileName.isEmpty())
            return false;
    }

    if (supportedTypeHeader(UBFileSystemUtils::mimeTypeFromFileName(fileName)))
        return true;

    return false;
}


UBDocumentDatastoreAPI::UBDocumentDatastoreAPI(UBGraphicsW3CWidgetItem *graphicsWidget)
    : UBW3CWebStorage(graphicsWidget)
    , mGraphicsW3CWidget(graphicsWidget)
{
    // NOOP
}



UBDocumentDatastoreAPI::~UBDocumentDatastoreAPI()
{
    // NOOP
}


QString UBDocumentDatastoreAPI::key(int index)
{
   QMap<QString, QString> entries = mGraphicsW3CWidget->datastoreEntries();

   if (index < entries.size())
       return entries.keys().at(index);
   else
       return "";

}


QString UBDocumentDatastoreAPI::getItem(const QString& key)
{
    QMap<QString, QString> entries = mGraphicsW3CWidget->datastoreEntries();
    if (entries.contains(key))
    {
        return entries.value(key);
    }
    else
    {
        return "";
    }
}


int UBDocumentDatastoreAPI::length()
{
   return mGraphicsW3CWidget->datastoreEntries().size();
}


void UBDocumentDatastoreAPI::setItem(const QString& key, const QString& value)
{
    if (mGraphicsW3CWidget)
    {
        mGraphicsW3CWidget->setDatastoreEntry(key, value);
    }
}


void UBDocumentDatastoreAPI::removeItem(const QString& key)
{
    mGraphicsW3CWidget->removeDatastoreEntry(key);
}
void

 UBDocumentDatastoreAPI::clear()
{
    mGraphicsW3CWidget->removeAllDatastoreEntries();
}


UBDatastoreAPI::UBDatastoreAPI(UBGraphicsW3CWidgetItem *widget)
    : QObject(widget)
{
    mDocumentDatastore = new UBDocumentDatastoreAPI(widget);
}


QObject* UBDatastoreAPI::document()
{
    return mDocumentDatastore;
}




