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

#include "MvDate.h"

#include <iostream>
#include <fstream>

//This is how flextra writes out the trajectory output in trajout.f
//format(i9,2f9.4,f7.4,f7.1,2f8.1,f8.3,f6.1,e9.2)
static const int fieldWidthArr[] = {9, 9, 9, 7, 7, 8, 8, 8, 6, 9};
static vector<int> fieldWidthVec(fieldWidthArr, fieldWidthArr + sizeof(fieldWidthArr) / sizeof(fieldWidthArr[0]));

//=========================================================
//
//  MvFlextraItem
//
//=========================================================

MvFlextraItem::MvFlextraItem(int id) :
    id_(id)
{
    pointKeyIndex_["date"]         = 0;
    pointKeyIndex_["elapsedTime"]  = 1;
    pointKeyIndex_["lon"]          = 2;
    pointKeyIndex_["lat"]          = 3;
    pointKeyIndex_["eta"]          = 4;
    pointKeyIndex_["pres"]         = 5;
    pointKeyIndex_["z"]            = 6;
    pointKeyIndex_["zAboveGround"] = 7;
    pointKeyIndex_["pv"]           = 8;
    pointKeyIndex_["theta"]        = 9;

    pointKeyType_["date"]         = DateType;
    pointKeyType_["elapsedTime"]  = IntType;
    pointKeyType_["lon"]          = FloatType;
    pointKeyType_["lat"]          = FloatType;
    pointKeyType_["eta"]          = FloatType;
    pointKeyType_["pres"]         = FloatType;
    pointKeyType_["z"]            = FloatType;
    pointKeyType_["zAboveGround"] = FloatType;
    pointKeyType_["pv"]           = FloatType;
    pointKeyType_["theta"]        = FloatType;
}

string MvFlextraItem::metaData(const string& key) const
{
    map<string, string>::const_iterator it = metaData_.find(key);
    if (it != metaData_.end())
        return it->second;
    else
        return string();
}

void MvFlextraItem::pointData(const string& key, vector<string>& data, MvFlextraItem::DataType& type) const
{
    map<string, int>::const_iterator it = pointKeyIndex_.find(key);
    if (it != pointKeyIndex_.end()) {
        int id = it->second;
        for (unsigned int i = 0; i < points_.size(); i++) {
            if (id >= 0 && id < static_cast<int>(points_.at(i).size())) {
                data.push_back(points_.at(i).at(id));
            }
            else {
                data.push_back(string());
            }
        }
    }

    map<string, DataType>::const_iterator itT = pointKeyType_.find(key);
    if (itT != pointKeyType_.end())
        type = itT->second;
}

//=========================================================
//
//  MvFlextra
//
//=========================================================

MvFlextraBlock::MvFlextraBlock()
{
    direction_     = "Forward";
    constantStep_  = true;
    uncertaintyTr_ = false;
}

MvFlextraBlock::~MvFlextraBlock()
{
    for (size_t i = 0; i < items_.size(); i++) {
        delete items_[i];
    }
}


void MvFlextraBlock::decode(string fileName)
{
    ifstream in(fileName.c_str());
    string line;
    decode(in, line);
    in.close();
}

void MvFlextraBlock::decode(ifstream& in, string& line)
{
    bool headerPassed      = false;
    bool firstRowPassed    = false;
    bool directionDetected = false;

    map<string, string> trTypeName;
    trTypeName["3-DIMENSIONAL"] = "3D";

    map<string, string> info;
    MvDate baseDate;
    string metaDataText;
    string keySuffix;
    vector<string> metaData;
    map<string, vector<string> > trInfo;
    map<string, string> constInfo;
    MvFlextraItem* item = 0;

    int cnt = 0;

    //Read meta-data first

    metaData_["Metview::type"] = "FLEXTRA";


    while (getline(in, line)) {
        if (line.find("Number of header lines") != string::npos) {
            break;
        }

        else if (line.find("*") != string::npos && headerPassed == false) {
            string value, valueFrom, valueTo;
            if (parseHeaderLine(line, "TYPE OF TRAJECTORIES:", value)) {
                if (trTypeName.find(value) != trTypeName.end()) {
                    metaData_["type"] = trTypeName[value];
                }
                else {
                    metaData_["type"] = value;
                }
            }
            else if (parseHeaderLine(line, "INTEGRATION SCHEME:", value))
                metaData_["integration"] = value;
            else if (parseHeaderLine(line, "INTERPOLATION METHOD:", value))
                metaData_["interpolation"] = value;
            else if (parseHeaderLine(line, "SPATIAL CFL CRITERION:", value))
                metaData_["cflSpace"] = value;
            else if (parseHeaderLine(line, "TEMPORAL CFL CRITERION:", value))
                metaData_["cflTime"] = value;
            else if (parseHeaderLine(line, "START POINT COMMENT:", value)) {
                metaData_["startComment"] = value;
                metaData_["name"]         = value;
                comment_                  = value;
            }
            else if (parseHeaderLine(line, "MODEL RUN COMMENT:", value))
                metaData_["runComment"] = value;
            else if (parseHeaderLine(line, "NORMAL INTERVAL BETWEEN WIND FIELDS:", value))
                metaData_["normalInterval"] = value;
            else if (parseHeaderLine(line, "MAXIMUM INTERVAL BETWEEN WIND FIELDS:", value))
                metaData_["maxInterval"] = value;
            else if (parseHeaderLine(line, "LONGITUDE RANGE:", valueFrom, valueTo)) {
                metaData_["west"] = valueFrom;
                metaData_["east"] = valueTo;
                if (parseHeaderLine(line, "GRID DISTANCE:", value)) {
                    metaData_["dx"] = value;
                }
            }
            else if (parseHeaderLine(line, "LATITUDE RANGE:", valueFrom, valueTo)) {
                metaData_["south"] = valueFrom;
                metaData_["north"] = valueTo;
                if (parseHeaderLine(line, "GRID DISTANCE:", value)) {
                    metaData_["dy"] = value;
                }
            }
        }
        else if (line.find("DATE:") != string::npos) {
            cnt++;

            item = new MvFlextraItem(cnt);
            items_.push_back(item);

            stringstream sst_cnt;
            sst_cnt << cnt;

            //Parse "Date:" line
            stringstream sst(line);
            string s, sDate, sTime, sIndex;
            sst >> s >> sDate >> s >> sTime >> s >> s >> sIndex;

            s     = sDate;
            sDate = s.substr(0, 4) + "-" + s.substr(4, 2) + "-" + s.substr(6, 2);

            s = sTime;
            if (s.size() < 6) {
                s = string(6 - s.size(), '0').append(s);
            }
            sTime = s.substr(0, 2) + ":" + s.substr(3, 2) + ":" + s.substr(4, 2);

            s        = sDate + " " + sTime;
            baseDate = MvDate(s.c_str());

            item->addMetaData("startDate", sDate);
            item->addMetaData("startTime", sTime);
            item->addMetaData("id", sst_cnt.str());
            item->addMetaData("stopIndex", sIndex);

            //Initialise the rest of the meta-data
            item->addMetaData("startLat", "-");
            item->addMetaData("startLon", "-");
            item->addMetaData("startEta", "-");
            item->addMetaData("startPres", "-");
            item->addMetaData("startZ", "-");
            item->addMetaData("startZAboveGround", "-");
            item->addMetaData("startPv", "-");
            item->addMetaData("startTheta", "-");

            //Get header line
            getline(in, line);

            headerPassed   = true;
            firstRowPassed = false;
        }
        else if (headerPassed) {
            if (!firstRowPassed) {
                istringstream issMeta(line);
                string s, lon, lat, eta, pres, z, z_oro, pv, theta;
                issMeta >> s >> lon >> lat >> eta >> pres >> z >> z_oro >> pv >> theta;
                item->addMetaData("startLat", lat);
                item->addMetaData("startLon", lon);
                item->addMetaData("startEta", eta);
                item->addMetaData("startPres", pres);
                item->addMetaData("startZ", z);
                item->addMetaData("startZAboveGround", z_oro);
                item->addMetaData("startPv", pv);
                item->addMetaData("startTheta", theta);

                firstRowPassed = true;
            }

            if (!directionDetected) {
                istringstream iss(line);
                string s;
                iss >> s;

                if (s != "0") {
                    if (s.find("-") != string::npos) {
                        direction_ = "Backward";
                    }
                    metaData_["direction"] = direction_;
                    directionDetected      = true;
                }
            }

            //Parse the line with the data taking the widths form the
            //original flextra ouput fortran FORMAT statement
            vector<string> pointData;
            int pos = 0;
            for (size_t i = 0; i < fieldWidthVec.size(); i++) {
                pointData.push_back(line.substr(pos, fieldWidthVec[i]));
                pos += fieldWidthVec[i];
            }

            istringstream iss(pointData[0]);
            double d;
            iss >> d;

            MvDate md = baseDate;
            md += d / 86400.;

            char buf[100];
            md.Format("yyyy-mm-dd HH:MM:SS", buf);

            pointData.insert(pointData.begin(), string(buf));

            item->addPoint(pointData);
        }
    }


    stringstream ssNum;
    ssNum << items_.size();
    metaData_["trNum"] = ssNum.str();

    //Find meta-data that is the same for all the trajectories
    //Check lat-lon
    if (isMetaDataConst("startLat") && isMetaDataConst("startLon")) {
        metaData_["startLat"] = items_.at(0)->metaData("startLat");
        metaData_["startLon"] = items_.at(0)->metaData("startLon");
    }

    if (isMetaDataConst("startDate") && isMetaDataConst("startTime")) {
        metaData_["startDate"] = items_.at(0)->metaData("startDate");
        metaData_["startTime"] = items_.at(0)->metaData("startTime");
    }

    string keys[6] = {"startEta", "startPres", "startZ", "startZAboveGround", "startPv", "startTheta"};
    for (unsigned int i = 0; i < 6; i++) {
        string s = keys[i];
        if (isMetaDataConst(s)) {
            metaData_[s] = items_.at(0)->metaData(s);
        }
    }

    //Check the timestep type (constant ot non-constant)
    checkStepType();


    //in.close();
}


void MvFlextraBlock::writeAll(const string& outFile, int& metaDataCnt)
{
    ofstream out;
    out.open(outFile.c_str());

    metaDataCnt = 0;
    writeMetaData(out, metaDataCnt);

    //Write out trajectory data
    for (vector<MvFlextraItem*>::const_iterator it = items_.begin(); it != items_.end(); it++) {
        const MvFlextraItem* item = *it;

        for (vector<vector<string> >::const_iterator itP = item->points().begin(); itP != item->points().end(); itP++) {
            out << item->id();
            for (vector<string>::const_iterator itS = (*itP).begin(); itS != (*itP).end(); itS++) {
                out << "," << *itS;
            }
            out << endl;
        }
    }

    out.close();
}

void MvFlextraBlock::writeHighlightPoints(const string& outFile, int& metaDataCnt, string periodHour)
{
    ofstream out;
    out.open(outFile.c_str());

    metaDataCnt = 0;
    writeMetaData(out, metaDataCnt);

    //Write out trajectory data
    for (vector<MvFlextraItem*>::const_iterator it = items_.begin(); it != items_.end(); it++) {
        const MvFlextraItem* item = *it;

        for (vector<vector<string> >::const_iterator itP = item->points().begin(); itP != item->points().end(); itP++) {
            const vector<string>& data = (*itP);

            if (data.size() > 0) {
                MvDate md(data.at(0).c_str());
                if (md.Minute() == 0 && md.Second() == 0) {
                    int h = md.Hour();

                    bool found = false;
                    if (periodHour == "6h")
                        found = (h % 6 == 0);
                    else if (periodHour == "12h")
                        found = (h % 12 == 0);
                    else if (periodHour == "24h")
                        found = (h == 0);
                    else if (periodHour == "48h" && data.size() > 1) {
                        istringstream iss(data[1]);
                        int isec;
                        iss >> isec;
                        found = (h == 0 && (isec / 86400) % 2 == 0);
                    }

                    if (found) {
                        out << item->id() << "," << data[0] << "," << data[2] << "," << data[3] << endl;
                    }
                }
            }
        }
    }

    out.close();
}


void MvFlextraBlock::writeLabels(const string& outFile, int& metaDataCnt, string periodHour, vector<string>& labels)
{
    ofstream out;
    out.open(outFile.c_str());

    metaDataCnt = 0;
    writeMetaData(out, metaDataCnt);

    //Write out trajectory data
    for (vector<MvFlextraItem*>::const_iterator it = items_.begin(); it != items_.end(); it++) {
        const MvFlextraItem* item = *it;

        for (vector<vector<string> >::const_iterator itP = item->points().begin(); itP != item->points().end(); itP++) {
            const vector<string>& data = (*itP);

            if (data.size() > 0) {
                MvDate md(data.at(0).c_str());
                if (md.Minute() == 0 && md.Second() == 0) {
                    int h = md.Hour();

                    bool found = false;
                    if (periodHour == "6h")
                        found = (h % 6 == 0);
                    else if (periodHour == "12h")
                        found = (h % 12 == 0);
                    else if (periodHour == "24h")
                        found = (h == 0);
                    else if (periodHour == "48h" && data.size() > 1) {
                        istringstream iss(data[1]);
                        int isec;
                        iss >> isec;
                        found = (h == 0 && (isec / 86400) % 2 == 0);
                    }

                    if (found) {
                        char buf1[100];
                        md.Format("dd:HH", buf1);
                        out << "1"
                            << "," << data[0] << "," << data[2] << "," << data[3] << endl;
                        labels.push_back(buf1);
                    }
                }
            }
        }
    }

    out.close();
}

void MvFlextraBlock::writeMetaData(ofstream& out, int& metaDataCnt)
{
    metaDataCnt = 0;

    //Write out global meta-data
    for (map<string, string>::const_iterator it = metaData_.begin(); it != metaData_.end(); it++) {
        out << it->first + "=" + it->second << " ";
    }
    out << "direction=" << direction_ << endl;
    metaDataCnt++;

    //Write out trajectory meta-data
    string keys[12] = {"id", "startDate", "startTime", "startLat", "startLon", "startEta",
                       "startPres", "startZ", "startZAboveGround", "startPv", "startTheta", "stopIndex"};

    for (int i = 0; i < 12; i++) {
        string s;
        for (vector<MvFlextraItem*>::iterator it = items_.begin(); it != items_.end(); it++) {
            string val = (*it)->metaData(keys[i]);
            if (s.empty()) {
                s.append(val);
            }
            else {
                s.append("/" + val);
            }
        }

        if (!s.empty()) {
            out << keys[i] + "_TR=" + s << endl;
            metaDataCnt++;
        }
    }
}


bool MvFlextraBlock::parseHeaderLine(const string& line, const string& key, string& value)
{
    bool found = false;
    string::size_type pos;
    if ((pos = line.find(key)) != string::npos) {
        stringstream sst(string(line, pos + key.size()));
        string s;
        while (sst >> s) {
            if (s.find("*") == string::npos) {
                if (value.empty()) {
                    value = s;
                }
                else {
                    value.append(s);
                }
                found = true;
            }
        }
    }

    return found;
}

bool MvFlextraBlock::parseHeaderLine(const string& line, const string& key, string& fromValue, string& toValue)
{
    bool found = false;
    string::size_type pos;
    if ((pos = line.find(key)) != string::npos) {
        stringstream sst(string(line, pos + key.size()));
        string s;
        sst >> fromValue;
        sst >> s;
        sst >> toValue;
        found = true;
    }

    return found;
}

bool MvFlextraBlock::isMetaDataConst(const string& key)
{
    if (items_.size() == 0)
        return false;

    string val = items_.at(0)->metaData(key);
    if (val.empty())
        return false;

    for (unsigned int i = 1; i < items_.size(); i++) {
        if (items_.at(i)->metaData(key) != val) {
            return false;
        }
    }

    return true;
}

void MvFlextraBlock::checkStepType()
{
    for (vector<MvFlextraItem*>::const_iterator it = items_.begin(); it != items_.end(); it++) {
        const vector<vector<string> >& pts = (*it)->points();

        if (pts.size() < 3)
            continue;

        istringstream iss0(pts.at(0).at(1));
        double first;
        iss0 >> first;

        istringstream iss1(pts.at(1).at(1));
        double prev;
        iss1 >> prev;

        double diff   = prev - first;
        bool sameDiff = true;
        for (unsigned int i = 2; i < (*it)->points().size() && i < 6; i++) {
            istringstream iss((*it)->points().at(i).at(1));
            double d;
            iss >> d;

            if (d - prev != diff) {
                sameDiff = false;
                break;
            }
            else
                prev = d;
        }

        if (sameDiff)
            constantStep_ = true;
        else
            constantStep_ = false;

        return;
    }

    constantStep_ = true;
}


//=========================================================
//
//  MvFlextra
//
//=========================================================

MvFlextra::MvFlextra(const string& fileName) :
    fileName_(fileName)
{
    decode();
}

MvFlextra::~MvFlextra()
{
    for (size_t i = 0; i < blocks_.size(); i++) {
        delete blocks_[i];
    }
}


void MvFlextra::decode()
{
    ifstream in(fileName_.c_str());

    string line;

    while (line.find("Number of header lines") != string::npos || getline(in, line)) {
        MvFlextraBlock* data = new MvFlextraBlock;
        blocks_.push_back(data);
        data->decode(in, line);
    }

    in.close();


    //Guess if a trajectory block contains uncertainty trajecories or not!
    //It is working only if the FLEXTRA file containing several blocks was
    //generated by metview.
    vector<pair<string, bool> > ref;
    for (unsigned int i = 0; i < blocks_.size(); i++) {
        string comment = blocks_[i]->comment();
        bool cstep     = blocks_[i]->constantStep();

        pair<string, bool> p = make_pair(comment, cstep);

        bool newRef = true;
        for (unsigned int j = 0; j < ref.size(); j++) {
            if (ref[j] == p) {
                blocks_[i]->setUncertantyTr(true);
                newRef = false;
                break;
            }
        }

        if (newRef)
            ref.push_back(p);
    }
}

void MvFlextra::write(FILE* f)
{
    ifstream in(fileName_.c_str());
    string line;

    while (getline(in, line)) {
        fputs(line.c_str(), f);
        fputs("\n", f);
    }
    in.close();
}


void MvFlextra::write(const string& outFile, int block)
{
    if (block < 0 || block >= static_cast<int>(blocks_.size()))
        return;

    ofstream out;
    out.open(outFile.c_str());

    ifstream in(fileName_.c_str());
    string line;

    int cnt = -1;
    while (getline(in, line)) {
        if (line.find("Number of header lines") != string::npos) {
            cnt++;
        }

        if (cnt > block)
            break;

        if (cnt == block) {
            out << line << endl;
        }
    }

    out.close();
}
