/***************************** 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>

#include <QDomDocument>

#include "MvQKeyManager.h"
#include "MvKeyProfile.h"
#include "MvMiscellaneous.h"

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

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

MvQKeyManager::MvQKeyManager(KeyType t, std::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::appDefDirFile(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::appDefDirFile(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 (auto it : copy.data()) {
        data_.push_back(it->clone());
    }
}

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

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

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

void MvQKeyManager::clear()
{
    for (auto& it : data_) {
        delete it;
        it = nullptr;
    }

    data_.clear();
}

void MvQKeyManager::update(MvQKeyManager* m)
{
    clear();
    for (auto it : m->data()) {
        data_.push_back(it->clone());
    }
}

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

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

MvKeyProfile* MvQKeyManager::addProfile(const std::string& name, MvKeyProfile* oriProf)
{
    MvKeyProfile* newProf = oriProf->clone();
    newProf->setName(name);
    if (!systemProfOnly_) {
        newProf->setSystemProfile(false);
    }
    data_.push_back(newProf);
    return newProf;
}


MvKeyProfile* MvQKeyManager::addProfile(const std::string& name, MvKeyProfile* oriProf, KeyType /*oriKeyType*/)
{
    MvKeyProfile* newProf = oriProf->clone();
    newProf->setName(name);
    if (!systemProfOnly_) {
        newProf->setSystemProfile(false);
    }
    data_.push_back(newProf);
    for (unsigned int i = 0; i < newProf->size(); i++) {
        MvKey* key = newProf->at(i);
        if (keyType_ == FrameType) {
            // Need a better solution!!!
            if (key->name() == "MV_Index") {
                key->setName("MV_Frame");
                key->setDescription("Frame index");
            }
        }
    }

    return newProf;
}

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

    for (auto& it : data_) {
        if (it->name() == name) {
            delete it;
            it = prof->clone();
        }
    }
}

void MvQKeyManager::deleteProfile(int index)
{
    auto it = data_.begin() + index;

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

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

MvKeyProfile* MvQKeyManager::findProfile(std::string name)
{
    for (auto& it : data_) {
        if (it->name() == name) {
            return it;
        }
    }

    return nullptr;
}


void MvQKeyManager::loadProfiles()
{
    MvKeyProfile* firstSysProf = nullptr;
    loadProfiles(fProfDefault_, data_);
    for (auto& it : data_) {
        it->setSystemProfile(true);
        if (!firstSysProf) {
            firstSysProf = it;
        }
    }

    if (predefinedKeysOnly_) {
        loadAllKeys(dataAll_);
        for (auto& itProf : data_) {
            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(std::vector<MvKeyProfile*>& target)
{
    if (systemProfOnly_)
        return;

    loadProfiles(fProfAll_, target);
}

bool MvQKeyManager::loadProfiles(std::string fileName, std::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;

    auto doc = QDomDocument("prof");
    doc.setContent(xmlConf);
    QDomElement docElem = doc.documentElement();
    //    qDebug() << "root" << docElem.text() << docElem.tagName();

    QDomNode n = docElem.firstChild();
    while (!n.isNull()) {
        if (n.nodeName() == "Profile" && n.isElement()) {
            auto attr = n.attributes();
            QString name = attr.namedItem("name").toAttr().value();
            name = name.simplified();
            if (!name.isEmpty()) {
                // Create new profile
                auto* prof = new MvKeyProfile(name.toStdString());
                target.push_back(prof);

                auto item = n.firstChild();
                while (!item.isNull()) {
                    //                    qDebug() << "item:" << item.nodeName();
                    auto itemAttr = item.attributes();
                    QString keyName = itemAttr.namedItem("name").toAttr().value();
                    QString keyShortName = itemAttr.namedItem("shortName").toAttr().value();
                    QString keyDesc = itemAttr.namedItem("desc").toAttr().value();
                    QString keyEditable = itemAttr.namedItem("editable").toAttr().value();
                    if (keyEditable.isEmpty()) {
                        keyEditable = "true";
                    }

                    if (!keyName.isEmpty() || !keyShortName.isEmpty()) {
                        auto* key = new MvKey(keyName.toStdString(),
                                              keyShortName.toStdString(),
                                              keyDesc.toStdString());

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

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

                        prof->push_back(key);

                        // get data tag
                        auto dataItem = item.firstChild();
                        while (!dataItem.isNull()) {
                            auto dataAttr = dataItem.attributes();
                            // qDebug() << "key:" << keyName;
                            for (int i = 0; i < dataAttr.size(); i++) {
                                auto da = dataAttr.item(i).toAttr();
                                if (!da.isNull()) {
                                    // qDebug() << "  " << da.name() << da.value();
                                    key->setMetaData(da.name().toStdString(),
                                                     da.value().toStdString());
                                }
                            }
                            dataItem = dataItem.nextSibling();
                        }
                    }

                    item = item.nextSibling();
                }
            }
        }
        n = n.nextSibling();
    }

    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 (auto prof : data_) {
        // 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 (auto key : *prof) {
            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 (const auto& it2 : key->metaData()) {
                    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) {
        auto key = new MvKey("MV_Frame", "Frame", "Frame index");
        key->setValueType(MvKey::IntType);
        prof->addKey(key);
    }
    else {
        auto key = new MvKey("MV_Index", "Index", "Message index");
        key->setValueType(MvKey::IntType);
        prof->addKey(key);
    }
}


QString MvQKeyManager::findUniqueProfileName(QString profName)
{
    int cnt = 0;
    for (auto it : data_) {
        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;
}
