/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include <QDebug>
#include <QBuffer>
#include <QFile>
#ifdef METVIEW_QT5
#include <QtXmlPatterns/QXmlQuery>
#include <QtXmlPatterns/QXmlResultItems>
#else
#include <QXmlQuery>
#include <QXmlResultItems>
#endif

#include "MvQKeyManager.h"
#include "MvQXmlQuery.h"
#include "MvKeyProfile.h"
#include "MvMiscelaneous.h"

#ifdef METVIEW
#include "mars.h"
#endif

QMap<MvQKeyManager::KeyType, QString> MvQKeyManager::keyTypeName_;

MvQKeyManager::MvQKeyManager(KeyType t, string fProfUser) :
    fProfUser_(fProfUser),
    keyType_(t),
    systemProfOnly_(false),
    predefinedKeysOnly_(false)
{
    if (keyType_ != GribType && keyType_ != BufrType &&
        keyType_ != FrameType && keyType_ != PlaceMarkType &&
        keyType_ != LayerType && keyType_ != BookmarkType) {
#ifdef METVIEW
        marslog(LOG_WARN, "MvQKeyManager::MvQKeyManager --> No keyType specified!");
#endif
        return;
    }

    if (keyType_ == LayerType) {
        systemProfOnly_ = true;
    }

    if (keyType_ == BufrType) {
        predefinedKeysOnly_ = true;
    }
    if (keyTypeName_.isEmpty() == true) {
        keyTypeName_[GribType]      = "GRIB";
        keyTypeName_[BufrType]      = "BUFR";
        keyTypeName_[FrameType]     = "Frame";
        keyTypeName_[PlaceMarkType] = "PlaceMark";
        keyTypeName_[LayerType]     = "Layer";
        keyTypeName_[BookmarkType]  = "Bookmark";
    }

    if (fProfUser_.empty()) {
        QMap<KeyType, QString> keyFileName;

        //System defaults
        keyFileName[GribType]  = "GribKeyProfile_default.xml";
        keyFileName[BufrType]  = "BufrKeyProfile_default.xml";
        keyFileName[FrameType] = "FrameKeyProfile_default.xml";
        keyFileName[LayerType] = "LayerKeyProfile_default.xml";
        //keyFileName[PlaceMarkType]="PlaceMarkKeyProfile_default.xml";

        if (keyFileName.contains(keyType_)) {
            fProfDefault_ = metview::shareDirFile(keyFileName[keyType_].toStdString());
        }

        if (!systemProfOnly_) {
            //All keys
            keyFileName.clear();
            keyFileName[GribType]  = "GribKeyProfile_all.xml";
            keyFileName[BufrType]  = "BufrKeyProfile_all.xml";
            keyFileName[FrameType] = "FrameKeyProfile_all.xml";

            if (keyFileName.contains(keyType_)) {
                fProfAll_ = metview::shareDirFile(keyFileName[keyType_].toStdString());
            }

            //User settings
            keyFileName.clear();
            keyFileName[GribType]      = "GribKeyProfile.xml";
            keyFileName[BufrType]      = "BufrKeyProfile.xml";
            keyFileName[FrameType]     = "FrameKeyProfile.xml";
            keyFileName[PlaceMarkType] = "PlaceMarkKeyProfile.xml";
            keyFileName[BookmarkType]  = "Bookmarks.xml";

            if (keyFileName.contains(keyType_)) {
                fProfUser_ = metview::preferenceDirFile(keyFileName[keyType_].toStdString());
            }
        }
    }
}

MvQKeyManager::MvQKeyManager(const MvQKeyManager& copy)
{
    fProfUser_          = copy.fProfUser_;
    fProfDefault_       = copy.fProfDefault_;
    fProfAll_           = copy.fProfAll_;
    keyType_            = copy.keyType_;
    systemProfOnly_     = copy.systemProfOnly_;
    predefinedKeysOnly_ = copy.predefinedKeysOnly_;
    for (vector<MvKeyProfile*>::const_iterator it = copy.data().begin(); it != copy.data().end(); it++) {
        data_.push_back((*it)->clone());
    }
}

MvQKeyManager::~MvQKeyManager()
{
    clear();
}

QString MvQKeyManager::keyTypeName(KeyType t) const
{
    return keyTypeName_.value(t);
}

MvQKeyManager* MvQKeyManager::clone()
{
    MvQKeyManager* cp = new MvQKeyManager(*this);
    return cp;
}

void MvQKeyManager::clear()
{
    for (vector<MvKeyProfile*>::iterator it = data_.begin(); it != data_.end(); it++) {
        delete (*it);
        (*it) = 0;
    }

    data_.clear();
}

void MvQKeyManager::update(MvQKeyManager* m)
{
    clear();
    for (vector<MvKeyProfile*>::const_iterator it = m->data().begin(); it != m->data().end(); it++) {
        data_.push_back((*it)->clone());
    }
}

MvKeyProfile* MvQKeyManager::addProfile(string name)
{
    MvKeyProfile* profile = new MvKeyProfile(name);
    createDefaultProfile(profile);
    data_.push_back(profile);
    return profile;
}

void MvQKeyManager::addProfile(MvKeyProfile* profile)
{
    data_.push_back(profile);
}

MvKeyProfile* MvQKeyManager::addProfile(string name, MvKeyProfile* oriProf)
{
    MvKeyProfile* newProf = new MvKeyProfile(name);
    data_.push_back(newProf);

    for (unsigned int i = 0; i < oriProf->size(); i++) {
        MvKey* key    = oriProf->at(i);
        MvKey* newKey = new MvKey(key->name(), key->shortName(), key->description());
        newKey->setReadIntAsString(key->readIntAsString());

        for (map<string, string>::const_iterator it = key->metaData().begin(); it != key->metaData().end(); it++) {
            newKey->setMetaData(it->first, it->second);
        }
        newProf->addKey(newKey);
    }

    return newProf;
}


MvKeyProfile* MvQKeyManager::addProfile(string name, MvKeyProfile* oriProf, KeyType /*oriKeyType*/)
{
    MvKeyProfile* newProf = new MvKeyProfile(name);
    data_.push_back(newProf);

    for (unsigned int i = 0; i < oriProf->size(); i++) {
        MvKey* key = oriProf->at(i);

        string keyName = key->name();
        string desc    = key->description();
        if (keyType_ == FrameType) {
            //Need a better solution!!!
            if (keyName == "MV_Index") {
                keyName = "MV_Frame";
                desc    = "Frame index";
            }
        }

        MvKey* newKey = new MvKey(keyName, key->shortName(), desc);
        newKey->setReadIntAsString(key->readIntAsString());

        for (map<string, string>::const_iterator it = key->metaData().begin(); it != key->metaData().end(); it++) {
            newKey->setMetaData(it->first, it->second);
        }
        newProf->addKey(newKey);
    }

    return newProf;
}

void MvQKeyManager::changeProfile(string name, MvKeyProfile* prof)
{
    if (!prof || prof->systemProfile())
        return;

    for (vector<MvKeyProfile*>::iterator it = data_.begin(); it != data_.end(); it++) {
        if ((*it)->name() == name) {
            delete *it;
            *it = prof->clone();
        }
    }
}

void MvQKeyManager::deleteProfile(int index)
{
    vector<MvKeyProfile*>::iterator it = data_.begin() + index;

    if ((*it)->systemProfile())
        return;

    delete (*it);
    data_.erase(it);
}

MvKeyProfile* MvQKeyManager::findProfile(string name)
{
    for (vector<MvKeyProfile*>::iterator it = data_.begin(); it != data_.end(); it++) {
        if ((*it)->name() == name) {
            return *it;
        }
    }

    return 0;
}


void MvQKeyManager::loadProfiles()
{
    MvKeyProfile* firstSysProf = 0;
    loadProfiles(fProfDefault_, data_);
    for (vector<MvKeyProfile*>::iterator it = data_.begin(); it != data_.end(); it++) {
        (*it)->setSystemProfile(true);
        if (!firstSysProf) {
            firstSysProf = *it;
        }
    }

    if (predefinedKeysOnly_) {
        loadAllKeys(dataAll_);
        for (vector<MvKeyProfile*>::iterator itProf = data_.begin(); itProf != data_.end(); ++itProf) {
            (*itProf)->expand(dataAll_);
        }
    }

    //If there are no user profiles
    if (!systemProfOnly_ && loadProfiles(fProfUser_, data_) == false) {
        //If there are no system profiles
        if (data_.size() == 0) {
            addProfile("Default");
            //createDefaultProfile(prof);
        }
        //If there is at least one system profile we create a
        //copy of it and add it to the profile list as "Default" and
        //make it editable
        else if (firstSysProf) {
            MvKeyProfile* p = firstSysProf->clone();
            p->setSystemProfile(false);
            p->setName("Default");
            data_.push_back(p);
        }
        saveProfiles();
    }
}

void MvQKeyManager::loadAllKeys(vector<MvKeyProfile*>& target)
{
    if (systemProfOnly_)
        return;

    loadProfiles(fProfAll_, target);
}

bool MvQKeyManager::loadProfiles(string fileName, vector<MvKeyProfile*>& target)
{
    if (fileName.empty())
        return false;

    QFile file(QString::fromStdString(fileName));

    //For bufr we corrently do not load/save profiles!!!
    if (file.open(QFile::ReadOnly | QFile::Text) == false) {
#ifdef METVIEW
        marslog(LOG_WARN, "MvQKeyManager::loadProfiles --> Cannot find profile file: %s ", fileName.c_str());
#endif
        return false;
    }

    QString xmlConf(file.readAll());
    file.close();

    //qDebug() << xmlConf;

    QXmlResultItems result;

    QByteArray ba = xmlConf.toUtf8();
    QBuffer buffer(&ba);  // This is a QIODevice.
    buffer.open(QIODevice::ReadOnly);

    MvQXmlQuery query;
    query.bindVariable("myDoc", &buffer);
    query.setQuery("doc($myDoc)//Profile");
    query.evaluateTo(&result);

    //Profile nodes
    QXmlItem item(result.next());
    while (!item.isNull()) {
        if (item.isNode()) {
            QXmlResultItems keyResult;
            QString name;

            //QXmlQuery query;
            query.setFocus(item);

            //Name of profile node
            query.setQuery("string(./@name)");
            query.evaluateTo(&name);
            name = name.simplified();

            //Create new profile
            MvKeyProfile* prof = new MvKeyProfile(name.toStdString());
            target.push_back(prof);

            query.setQuery("./Key");
            query.evaluateTo(&keyResult);

            //Key nodes
            QXmlItem keyItem(keyResult.next());
            while (!keyItem.isNull()) {
                if (keyItem.isNode()) {
                    QString keyName, keyShortName, keyDesc, keyEditable = "true";

                    QMap<QString, QString> att;
                    query.getAttributes(keyItem, att);

                    if (att.contains("name"))
                        keyName = att["name"];

                    if (att.contains("shortName"))
                        keyShortName = att["shortName"];

                    if (att.contains("desc"))
                        keyDesc = att["desc"];

                    if (att.contains("editable"))
                        keyEditable = att["editable"];

                    MvKey* key = new MvKey(keyName.toStdString(),
                                           keyShortName.toStdString(),
                                           keyDesc.toStdString());

                    if (keyEditable == "false" || keyEditable == "0" ||
                        predefinedKeysOnly_) {
                        key->setEditable(false);
                    }

                    if (key->name() == "MV_Index") {
                        key->setValueType(MvKey::IntType);
                    }

                    prof->push_back(key);

                    //Parse data tags!
                    QXmlResultItems dataResult;
                    query.setFocus(keyItem);
                    query.setQuery("./Data");
                    query.evaluateTo(&dataResult);

                    QXmlItem dataItem(dataResult.next());
                    while (!dataItem.isNull()) {
                        if (dataItem.isNode()) {
                            QMap<QString, QString> dataAtt;
                            query.getAttributes(dataItem, dataAtt);

                            QMapIterator<QString, QString> it(dataAtt);
                            while (it.hasNext()) {
                                it.next();

                                key->setMetaData(it.key().toStdString(),
                                                 it.value().toStdString());
                            }
                        }
                        dataItem = dataResult.next();
                    }
                }

                keyItem = keyResult.next();
            }
        }

        item = result.next();
    }

    buffer.close();

    return true;
}

void MvQKeyManager::saveProfiles()
{
    //Open file for writing
    QFile out(fProfUser_.c_str());
    if (out.open(QFile::WriteOnly | QFile::Text) == false) {
#ifdef METVIEW
        marslog(LOG_WARN, "MvQKeyManager::saveProfiles --> Cannot create profile file: %s ", fProfUser_.c_str());
#endif
        return;
    }

    QString s;

    s = "<MetviewKeyProfile type=\"" + keyTypeName_[keyType_] + "\">\n";
    out.write(s.toUtf8());

    for (vector<MvKeyProfile*>::iterator it = data_.begin(); it != data_.end(); it++) {
        MvKeyProfile* prof = *it;


        //We do not save system profiles into the user profile file
        if (prof->systemProfile())
            continue;


        s = "\t<Profile name=\"" + QString(prof->name().c_str()) + "\">\n";
        out.write(s.toUtf8());

        for (vector<MvKey*>::iterator it1 = prof->begin(); it1 != prof->end(); it1++) {
            MvKey* key = *it1;
            s          = "\t\t<Key name=\"" + QString::fromStdString(key->name()) + "\"" +
                " shortName=\"" + QString::fromStdString(key->shortName()) + "\"" +
                " desc=\"" + QString::fromStdString(key->description()) + "\"";

            out.write(s.toUtf8());

            if (key->metaData().size() > 0) {
                s = ">\n";
                out.write(s.toUtf8());

                for (map<string, string>::const_iterator it2 = key->metaData().begin();
                     it2 != key->metaData().end(); it2++) {
                    s = "\t\t\t<Data " + QString::fromStdString(it2->first) + "=\"" + QString::fromStdString(it2->second) + "\"/>\n";
                    out.write(s.toUtf8());
                }

                s = "\t\t</Key>\n";
                out.write(s.toUtf8());
            }
            else {
                s = "/>\n";
                out.write(s.toUtf8());
            }
        }
        s = "\t</Profile>\n";
        out.write(s.toUtf8());
    }

    s = "</MetviewKeyProfile>";
    out.write(s.toUtf8());

    out.close();
}

void MvQKeyManager::createDefaultProfile(MvKeyProfile* prof)
{
    if (keyType_ == BookmarkType)
        return;

    if (keyType_ == FrameType) {
        prof->addKey(new MvKey("MV_Frame", "Frame", "Frame index"));
    }
    else {
        prof->addKey(new MvKey("MV_Index", "Index", "Message index"));
    }
}


QString MvQKeyManager::findUniqueProfileName(QString profName)
{
    int cnt = 0;
    for (vector<MvKeyProfile*>::const_iterator it = data_.begin(); it != data_.end(); it++) {
        QString qsName((*it)->name().c_str());

        if (qsName == profName) {
            if (cnt == 0)
                cnt = 1;
        }
        else if (qsName.startsWith(profName + " (")) {
            QStringList lst = qsName.split("(");
            if (lst.count() == 2) {
                lst = lst[1].split(")");
                if (lst.count() == 2) {
                    int n = lst[0].toInt();
                    if (n >= cnt)
                        cnt = n + 1;
                }
            }
        }
    }

    if (cnt > 0)
        return profName + " (" + QString::number(cnt) + ")";
    else
        return profName;
}
