/***************************** 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 "MvQOdbMetaData.h"

#include <QtGlobal>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QStringList>
#include <QTextStream>

#include <odc/api/ColumnType.h>
#include <odc/Reader.h>


static QMap<odc::api::ColumnType, MvQOdbColumn::OdbColumnType> typeIdMap;
static QMap<odc::api::ColumnType, QString> typeIdNameMap;


void MvQOdbTable::findLinks()
{
    linksTo_.clear();

    foreach (MvQOdbColumn* col, columns_) {
        if (col->type().contains("@LINK")) {
            QStringList lst = col->name().split("@");
            if (lst.count() > 1 && lst[0].isEmpty() == false) {
                linksTo_ << lst[0];
            }
        }
    }
}


MvQOdbMetaData::MvQOdbMetaData(std::string path, std::string odbVersion)
{
    path_ = QString::fromStdString(path);

    if (odbVersion == "ODB_NEW") {
#ifndef METVIEW_ODB_NEW
        odbVersion_ = InvalidVersion;
        return;
#else
        odbVersion_ = Version2;
#endif
    }
    else if (odbVersion == "ODB_OLD") {
        odbVersion_ = Version1;
    }
    else {
        odbVersion_ = InvalidVersion;
    }

    if (typeIdMap.isEmpty()) {
        typeIdMap[odc::api::IGNORE] = MvQOdbColumn::None;
        typeIdMap[odc::api::INTEGER] = MvQOdbColumn::Int;
        typeIdMap[odc::api::REAL] = MvQOdbColumn::Float;
        typeIdMap[odc::api::STRING] = MvQOdbColumn::String;
        typeIdMap[odc::api::BITFIELD] = MvQOdbColumn::Bitfield;
        typeIdMap[odc::api::DOUBLE] = MvQOdbColumn::Double;

        typeIdNameMap[odc::api::IGNORE] = "none";
        typeIdNameMap[odc::api::INTEGER] = "int";
        typeIdNameMap[odc::api::REAL] = "float";
        typeIdNameMap[odc::api::STRING] = "string";
        typeIdNameMap[odc::api::BITFIELD] = "bitfield";
        typeIdNameMap[odc::api::DOUBLE] = "double";
    }

    if (odbVersion_ == Version1) {
        loadSchemaFile();
    }

    else if (odbVersion_ == Version2) {
#ifdef METVIEW_ODB_NEW
        loadOdbHeader();
#endif
    }
}

MvQOdbMetaData::~MvQOdbMetaData()
{
    foreach (MvQOdbTable* t, tables_.values()) {
        delete t;
    }

    foreach (MvQOdbColumn* c, columns_) {
        delete c;
    }

    foreach (MvQOdbVar* v, vars_) {
        delete v;
    }
}

QString MvQOdbMetaData::odbVersionString()
{
    if (odbVersion_ == Version1) {
        return "ODB 1";
    }
    else if (odbVersion_ == Version2) {
        return "ODB 2";
    }
    else {
        return "Invalid";
    }

    return QString();
}

void MvQOdbMetaData::removeCommentFromLine(QString& line)
{
    int commentIndex = line.indexOf("//");
    if (commentIndex != -1) {
        line.remove(commentIndex, line.size() - commentIndex + 1);
    }
}


void MvQOdbMetaData::loadSchemaFile()
{
    QDir dir(path_);
    QStringList filters;
    filters << "*.sch";
    QStringList schLst = dir.entryList(filters, QDir::Files | QDir::NoDotAndDotDot);

    QMap<QString, QList<MvQOdbBitfieldMember*> > bitfields;

    if (schLst.count() < 1) {
        return;
    }

    QFile file(path_ + "/" + schLst[0]);

    if (file.open(QFile::ReadOnly | QFile::Text) == false) {
        return;
    }

    QTextStream in(&file);

    while (!in.atEnd()) {
        QString line = in.readLine();

        // Vars
        if (line.startsWith("SET")) {
            QStringList lst = line.split(" ",
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
                                         Qt::SkipEmptyParts);
#else
                                         QString::SkipEmptyParts);
#endif

            if (lst.count() == 4 && lst[2] == "=") {
                QString name, val;
                if (lst[1].startsWith("$")) {
                    name = lst[1].remove(0, 1);
                }
                if (lst[3].endsWith(";")) {
                    val = lst[3].remove(lst[3].size() - 1, 1);
                }

                if (name.isEmpty() == false &&
                    val.isEmpty() == false) {
                    vars_.push_back(new MvQOdbVar(name, val));
                }
            }
        }

        // Bitfield
        if (line.startsWith("CREATE TYPE")) {
            QString name, bitLen, bitPos;
            int bitPosInt = 0, bitLenInt = 0;

            removeCommentFromLine(line);
            QString str = line;
            while (!in.atEnd() && line.contains(");") == false) {
                line = in.readLine();
                removeCommentFromLine(line);
                str += " " + line;
            }

            QStringList lst = str.split(" ",
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
                                        Qt::SkipEmptyParts);
#else
                                        QString::SkipEmptyParts);
#endif

            if (lst.size() < 3) {
                break;
            }

            QString bfName = lst[2];
            QList<MvQOdbBitfieldMember*> bf;

            for (int i = 5; i < lst.size(); i += 2) {
                name = lst[i];
                if (i + 1 < lst.size() && lst[i + 1].endsWith(",")) {
                    bitLen = lst[i + 1].remove(lst[i + 1].size() - 1, 1);
                    bitLen.remove("bit");
                    bitLenInt = bitLen.toInt();
                    bitPos = QString::number(bitPosInt);
                    bf.push_back(new MvQOdbBitfieldMember(name, bitPosInt, bitLenInt));
                    bitPosInt += bitLenInt;
                }
            }
            bitfields[bfName] = bf;
        }

        // Table
        if (line.startsWith("CREATE TABLE")) {
            removeCommentFromLine(line);

            QString name, type;
            QString str = line;
            while (!in.atEnd() && line.contains(");") == false) {
                line = in.readLine();
                removeCommentFromLine(line);
                str += " " + line;
            }

            QStringList lst = str.split(" ",
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
                                        Qt::SkipEmptyParts);
#else
                                        QString::SkipEmptyParts);
#endif

            if (lst.size() < 3) {
                break;
            }

            // qDebug() <<  lst;

            QString tableName = lst[2];
            auto* table = new MvQOdbTable(tableName);
            for (int i = 5; i < lst.size(); i += 2) {
                name = lst[i] + "@" + tableName;
                // qDebug() << "table: " << name;
                if (i + 1 < lst.size() && lst[i + 1].endsWith(",")) {
                    type = lst[i + 1].remove(lst[i + 1].size() - 1, 1);
                    auto* col = new MvQOdbColumn(name, type, tableName);
                    columns_.push_back(col);
                    table->addColumn(col);
                }
            }

            table->findLinks();

            tables_[tableName] = table;

            // qDebug() << "table:" << tableName << table->linksTo() ;
        }
    }

    file.close();

    foreach (MvQOdbColumn* col, columns_) {
        if (bitfields.contains(col->type())) {
            foreach (MvQOdbBitfieldMember* bf, bitfields[col->type()]) {
                col->addBitfieldMember(new MvQOdbBitfieldMember(*bf));
            }
            // qDebug() << "num:" << bitfields[col->type()].size() << col->bitfieldNum();
        }
        // qDebug() << col->name();
    }

    // Delete tmp bitfields
    QMapIterator<QString, QList<MvQOdbBitfieldMember*> > it(bitfields);
    while (it.hasNext()) {
        it.next();
        foreach (MvQOdbBitfieldMember* b, it.value())
            delete b;
    }

    getTableTree();
}

void MvQOdbMetaData::getTableTree()
{
    QMapIterator<QString, MvQOdbTable*> it(tables_);
    while (it.hasNext()) {
        it.next();
        foreach (QString tblName, it.value()->linksTo()) {
            if (tables_.contains(tblName)) {
                tables_[tblName]->addToLinksFrom(tblName);
            }
        }
    }

    // Find the root in the ODB table tree
    // It has only "to" links!!

    int currentYPosMax = 0;

    it.toFront();
    while (it.hasNext()) {
        it.next();
        if (it.value()->linksFrom().isEmpty()) {
            MvQOdbTable* rt = it.value();

            rt->setTreePosX(0);
            rt->setTreePosY(currentYPosMax);
            // qDebug() << "root" << rt->name();

            int posy = currentYPosMax;
            computeTreePos(rt, posy);

            QMapIterator<QString, MvQOdbTable*> ityp(tables_);
            while (ityp.hasNext()) {
                ityp.next();
                if (ityp.value()->treePosY() > currentYPosMax) {
                    currentYPosMax = ityp.value()->treePosY();
                }
            }
            currentYPosMax++;
        }
    }

    /*int posy=0;
    if(rootTables_.count() > 0)
        computeTreePos(rootTables_[0],posy);*/

    /*QMapIterator<QString, MvQOdbTable*> it(tables_);
    while (it.hasNext())
    {
            it.next();
        if(it.value()->treePosX() > xr)
        {
            xr=it.value()->treePosX();
        }

        if(it.value()->treePosY() > yr)
        {
            yr=it.value()->treePosY();
        }

    }*/

    it.toFront();
    while (it.hasNext()) {
        it.next();
        // qDebug() << it.value()->name() << it.value()->treePosX() << it.value()->treePosY();
    }

    /*
    xp=parent->treePosX()+1;
    yp=parent->treePosY();
    int i=0;
    foreach(MvQOdbTable* t,parent->linksFrom())
    {
        t->setTreePosX(xp);
        t->setTreePosY(yp);
        yp++;
    }



    //For each table find the position (x,y) in the tree!
    int posX=1;
    std::vector<int> posY(100,0);
    QStack<QList<OdbTable*>::iterator> itActStack, itEndStack;
    std::list<OdbTable*>::iterator itAct=root_->linksTo().begin();
    std::list<OdbTable*>::iterator itEnd=root_->linksTo().end();

    while(itAct != itEnd)
    {
        (*itAct)->treePosX(posX);
        (*itAct)->treePosY(posY[posX]);

        //std::cout << "table: " << (*itAct)->name_ <<   " x: " << (*itAct)->treePosX_  << " y: "  << (*itAct)->treePosY_ << std::endl;

        posY[posX]=posY[posX]++;

        if(!(*itAct)->linksTo().empty())
        {
            itActStack.push(itAct);
            itEndStack.push(itEnd);

            //std::cout << " to stack: " << (*itAct)->name_ << std::endl;

            std::list<OdbTable*>::iterator itTmp=itAct;
            itAct=(*itTmp)->linksTo().begin();
            itEnd=(*itTmp)->linksTo().end();
            posX++;

            posY[posX]=posY[posX-1]-1;

            continue;
        }

        itAct++;

        while (itAct == itEnd && itActStack.empty() == false)
        {
            itAct=itActStack.top();
            itEnd=itEndStack.top();

            itActStack.pop();
            itEndStack.pop();

            //std::cout << " rom stack: " << (*itAct)->name_ << std::endl;

            itAct++;
            posX--;

            posY[posX]=posY[posX+1];

        }
    }	*/
}


void MvQOdbMetaData::computeTreePos(MvQOdbTable* parent, int& yp)
{
    // qDebug() << parent->name() << parent->treePosX() <<  parent->treePosY();

    int xp = parent->treePosX() + 1;
    // int yp=parent->treePosY();
    // qDebug() << "    " << xp << yp;
    // qDebug() << "    " << parent->linksTo();

    foreach (QString tblName, parent->linksTo()) {
        MvQOdbTable* t = tables_[tblName];
        t->setTreePosX(xp);
        t->setTreePosY(yp);
        computeTreePos(t, yp);
        yp++;
    }
}

void MvQOdbMetaData::getTreePosRange(int& xr, int& yr)
{
    xr = 0;
    yr = 0;

    QMapIterator<QString, MvQOdbTable*> it(tables_);
    while (it.hasNext()) {
        it.next();
        if (it.value()->treePosX() > xr) {
            xr = it.value()->treePosX();
        }

        if (it.value()->treePosY() > yr) {
            yr = it.value()->treePosY();
        }
    }
}

void MvQOdbMetaData::loadOdbHeader()
{
    // odb_start();
    odc::Reader oda(path_.toStdString());

    odc::Reader::iterator it = oda.begin();

    for (auto itc : it->columns()) {
        auto* acol = new MvQOdbColumn;

        std::string s = itc->name();

        QString colName(s.c_str());
        QStringList lst = colName.split("@");

        if (lst.count() > 1) {
            acol->setTable(lst.back());
        }

        acol->setName(QString(s.c_str()));
        acol->setConstant(itc->isConstant());
        acol->setMin(itc->min());
        acol->setMax(itc->max());

        if (itc->hasMissing()) {
            acol->setHasMissingValue(itc->hasMissing());
            acol->setMissingValue(itc->missingValue());
        }

        if (typeIdMap.contains(itc->type())) {
            acol->setTypeId(typeIdMap[itc->type()]);
            acol->setType(typeIdNameMap[itc->type()]);
        }

        if (acol->typeId() == MvQOdbColumn::Bitfield) {
            int pos = 0;
            for (unsigned int i = 0; i < itc->bitfieldDef().first.size(); i++) {
                std::string name = itc->bitfieldDef().first.at(i);
                int size = itc->bitfieldDef().second.at(i);
                acol->addBitfieldMember(new MvQOdbBitfieldMember(QString(name.c_str()), pos, size));
                pos += size;
            }
        }

        columns_.push_back(acol);
    }

    // qDebug() << "odb columns"  << columns_.size() << columns_.size() << columnNum();
}
