/*
 * (C) Copyright 1996-2016 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

/*! \file TableDecoder.cc
    \brief Implementation of the Template class TableDecoder.

    Magics Team - ECMWF 2004

    Started: Thu 6-May-2004

    Changes:

*/

#include "TableDecoder.h"
#include "MagException.h"
#include "SciMethods.h"
#include "TableReader.h"
#include "TextVisitor.h"
#include "Tokenizer.h"


using namespace ::magics;


TableDecoder::TableDecoder() : matrix_(0) {
    MetviewIcon::setInfo("_datatype", "TABLE");
    MetviewIcon::setInfo("_description", "Table Data");
}


/*!
 Class information are given to the output-stream.
*/
void TableDecoder::print(ostream& out) const {
    out << "TableDecoder[";
    TableDecoderAttributes::print(out);
    out << "]";
}


void TableDecoder::dateSetting(vector<string>& dates, vector<double>& values, DateTime& base) {
    if (dates.empty())
        return;
    base = DateTime(dates.front());
    for (vector<string>::iterator date = dates.begin(); date != dates.end(); ++date) {
        DateTime d(*date);
        values.push_back(d - base);
    }
}


void TableDecoder::numberSetting(vector<double>& from, vector<double>& values) {
    std::copy(from.begin(), from.end(), back_inserter(values));
}


namespace magics {

void TableDecoder::nameGeoMode(TableReader& reader) {
    x_name_ = this->lon_;
    y_name_ = this->lat_;
    reader.setFieldContainer(-1, y_name_, this->y_values_, -999);
    reader.setFieldContainer(-1, x_name_, this->x_values_, -999);


    if (!this->values_.empty() && this->values_ != "-1") {
        v_name_ = this->values_;
        reader.setFieldContainer(-1, v_name_, this->v_values_, -999);
    }
    if (!this->x_component_.empty() && this->x_component_ != "-1") {
        xc_name_ = this->x_component_;
        reader.setFieldContainer(-1, xc_name_, this->xc_values_, -999);
    }
    if (!this->y_component_.empty() && this->y_component_ != "-1") {
        yc_name_ = this->y_component_;
        reader.setFieldContainer(-1, yc_name_, this->yc_values_, -999);
    }
}


void TableDecoder::indexGeoMode(TableReader& reader) {
    int x_index_  = indexToNumber(this->lon_) - 1;
    int y_index_  = indexToNumber(this->lat_) - 1;
    int val_index = indexToNumber(this->values_);
    int xc_index  = indexToNumber(this->x_component_);
    int yc_index  = indexToNumber(this->y_component_);

    reader.setFieldContainer(x_index_, x_name_, this->x_values_, -999);
    reader.setFieldContainer(y_index_, y_name_, this->y_values_, -999);


    if (val_index != -1) {
        reader.setFieldContainer(val_index - 1, v_name_, this->v_values_, -999);
    }
    if (xc_index != -1) {
        reader.setFieldContainer(xc_index - 1, xc_name_, this->xc_values_, -999);
    }
    if (yc_index != -1) {
        reader.setFieldContainer(yc_index - 1, yc_name_, this->yc_values_, -999);
    }
}


void TableDecoder::prepareGeo() {
    TableReader reader;

    bool ok;
    string error;
    reader.setPath(this->path_);
    reader.setHeaderRow(this->header_row_);
    if (!this->delimiter_.empty())
        reader.setDelimiter(this->delimiter_[0]);
    reader.setConsecutiveDelimitersAsOne(this->combine_delimiters_);
    reader.setDataRowOffset(this->data_row_offset_);
    reader.setUserMetaDataRows(this->meta_data_rows_);

    vector<string> names = reader.fieldNames();


    if (!this->x_values_.empty())
        return;

    // first set the containers!!
    if (magCompare(this->name_mode_, "name"))
        nameGeoMode(reader);
    else
        indexGeoMode(reader);


    // Then read and interpret!
    ok = reader.read(error);
    if (!ok) {
        MagLog::error() << error << endl;
        if(MagicsGlobal::strict()) {
            throw MagicsException("TableDecoder reader error: " + error);
        }
	return;
    }

    vector<double>::iterator x = this->x_values_.begin();
    vector<double>::iterator y = this->y_values_.begin();
    vector<double>::iterator v = this->v_values_.begin();

    while (x != this->x_values_.end() && x != this->x_values_.end()) {
        double val = 0;
        if (v != v_values_.end()) {
            val = *v;
            ++v;
        }
        push_back(new UserPoint(*x, *y, val));
        ++x;
        ++y;
    }

    // Meta-data
    map<string, string> meta = reader.userMetaData();

    map<string, string>::const_iterator metaIt;
    if ((metaIt = meta.find("Metview::type")) != meta.end() && metaIt->second == "FLEXTRA") {
        setInfo("_datatype", "TABLE_flextra");
        setInfo("_description", "FLEXTRA Trajectories");
    }

    for (map<string, string>::const_iterator it = meta.begin(); it != meta.end(); it++) {
        setInfo(it->first, it->second);
    }
}


void TableDecoder::nameXYMode(TableReader& reader) {
    x_name_ = this->x_;
    y_name_ = this->y_;
    if (magCompare(this->x_type_, "date")) {
        reader.setFieldContainer(-1, x_name_, this->x_date_values_, "none");
    }
    else {
        reader.setFieldContainer(-1, x_name_, this->x_values_, -999);
    }
    if (magCompare(this->y_type_, "date")) {
        reader.setFieldContainer(-1, y_name_, this->y_date_values_, "none");
    }
    else {
        reader.setFieldContainer(-1, y_name_, this->y_values_, -999);
    }

    if (!this->values_.empty() && this->values_ != "-1") {
        v_name_ = this->values_;
        reader.setFieldContainer(-1, v_name_, this->v_values_, -999);
    }
    if (!this->x_component_.empty() && this->x_component_ != "-1") {
        xc_name_ = this->x_component_;
        reader.setFieldContainer(-1, xc_name_, this->xc_values_, -999);
    }
    if (!this->y_component_.empty() && this->y_component_ != "-1") {
        yc_name_ = this->y_component_;
        reader.setFieldContainer(-1, yc_name_, this->yc_values_, -999);
    }
}


void TableDecoder::indexXYMode(TableReader& reader) {
    int x_index   = indexToNumber(this->x_) - 1;
    int y_index   = indexToNumber(this->y_) - 1;
    int val_index = indexToNumber(this->values_);
    int xc_index  = indexToNumber(this->x_component_);
    int yc_index  = indexToNumber(this->y_component_);

    if (magCompare(this->x_type_, "date")) {
        reader.setFieldContainer(x_index, x_name_, this->x_date_values_, "none");
    }
    else {
        reader.setFieldContainer(x_index, x_name_, this->x_values_, -999);
    }
    if (magCompare(this->y_type_, "date")) {
        reader.setFieldContainer(y_index, y_name_, this->y_date_values_, "none");
    }
    else {
        reader.setFieldContainer(y_index, y_name_, this->y_values_, -999);
    }

    if (val_index != -1) {
        reader.setFieldContainer(val_index - 1, v_name_, this->v_values_, -999);
    }
    if (xc_index != -1) {
        reader.setFieldContainer(xc_index - 1, xc_name_, this->xc_values_, -999);
    }
    if (yc_index != -1) {
        reader.setFieldContainer(yc_index - 1, yc_name_, this->yc_values_, -999);
    }
}


void TableDecoder::prepareXY() {
    TableReader reader;

    bool ok;
    string error;
    reader.setPath(this->path_);
    reader.setHeaderRow(this->header_row_);
    if (!this->delimiter_.empty())
        reader.setDelimiter(this->delimiter_[0]);
    reader.setConsecutiveDelimitersAsOne(this->combine_delimiters_);
    reader.setDataRowOffset(this->data_row_offset_);
    reader.setUserMetaDataRows(this->meta_data_rows_);

    vector<string> names = reader.fieldNames();


    if (!this->x_values_.empty())
        return;

    // first set the containers!!
    // first set the containers!!
    if (magCompare(this->name_mode_, "name"))
        nameXYMode(reader);
    else
        indexXYMode(reader);
    // Then read and interpret!
    ok = reader.read(error);
    if (!ok) {
        MagLog::error() << error << endl;
	if(MagicsGlobal::strict()) {
           throw MagicsException("TableDecoder reader error: " + error);
	}
        return;
    }

    // Now we interpret
    if (magCompare(this->x_type_, "date")) {
        this->dateSetting(this->x_date_values_, this->x_values_, baseDateX_);
    }

    if (magCompare(this->y_type_, "date")) {
        this->dateSetting(this->y_date_values_, this->y_values_, baseDateY_);
    }

    vector<double>::iterator x = this->x_values_.begin();
    vector<double>::iterator y = this->y_values_.begin();
    vector<double>::iterator v = this->v_values_.begin();

    while (x != this->x_values_.end() && x != this->x_values_.end()) {
        double val = 0;
        if (v != v_values_.end()) {
            val = *v;
            ++v;
        }
        push_back(new UserPoint(*x, *y, val));
        if (same(*x, x_missing_) || same(*y, y_missing_))
            back()->flagMissing();
        ++x;
        ++y;
    }

    // Meta-data
    map<string, string> meta = reader.userMetaData();

    map<string, string>::const_iterator metaIt;
    if ((metaIt = meta.find("Metview::type")) != meta.end() && metaIt->second == "FLEXTRA") {
        setInfo("_datatype", "TABLE_flextra");
        setInfo("_description", "FLEXTRA Trajectories");
    }

    for (map<string, string>::const_iterator it = meta.begin(); it != meta.end(); it++) {
        setInfo(it->first, it->second);
    }
}


void TableDecoder::customisedPoints(const Transformation& transformation, const std::set<string>&,
                                    CustomisedPointsList& out) {
    if (transformation.coordinateType() == Transformation::GeoType)
        prepareGeo();
    else
        prepareXY();

    vector<double>::const_iterator x  = x_values_.begin();
    vector<double>::const_iterator y  = y_values_.begin();
    vector<double>::const_iterator xc = this->xc_values_.begin();
    vector<double>::const_iterator yc = this->yc_values_.begin();
    vector<double>::const_iterator v  = this->v_values_.begin();
    while (x != x_values_.end() && y != y_values_.end()) {
        double valx = *x;
        double valy = *y;
        if (magCompare(this->x_type_, "date")) {
            DateTime ref(transformation.getReferenceX());
            double shift = ref - this->baseDateX_;
            valx -= shift;
        }
        if (magCompare(this->y_type_, "date")) {
            DateTime ref(transformation.getReferenceY());
            double shift = ref - this->baseDateY_;
            valy -= shift;
        }
        CustomisedPoint* point = new CustomisedPoint();
        bool todelete          = true;
        if (transformation.in(valx, valy)) {
            point->longitude(valx);
            point->latitude(valy);
            out.push_back(point);
            todelete = false;
        }


        (*point)["x"] = valx;
        ++x;
        (*point)["y"] = valy;
        ++y;


        double speed = 0;
        if (xc != this->xc_values_.end() && yc != this->yc_values_.end()) {
            speed                   = sqrt((*xc * *xc) + (*yc * *yc));
            (*point)["x_component"] = *(xc++);
            (*point)["y_component"] = *(yc++);
        }
        if (v != this->v_values_.end()) {
            (*point)["colour_component"] = *(v++);
        }
        else
            (*point)["colour_component"] = speed;

        if (todelete)
            delete point;
    }
}


MatrixHandler& TableDecoder::matrix() {
    if (!matrix_) {
        prepareXY();
        matrix_ = (*table_binning_)(*this);
    }

    this->matrixHandlers_.push_back(new MatrixHandler(*matrix_));

    return *(this->matrixHandlers_.back());
}


void TableDecoder::getReady(const Transformation& transformation) {
    /*
    try {
        for ( vector<string>::const_iterator x = date_x_.begin(); x != date_x_.end(); ++x )
            x_.push_back(transformation.x(*x));
        }

    catch (...) {}
    try {
        for ( vector<string>::const_iterator y = date_y_.begin(); y != date_y_.end(); ++y )
            y_.push_back(transformation.y(*y));
    }
    catch (...) {}
    */
}

void TableDecoder::visit(Transformation& transformation) {
    // get the data ...
    try {
        if (transformation.coordinateType() == Transformation::GeoType)
            prepareGeo();
        else
            prepareXY();

        if (transformation.getAutomaticX()) {
            if (magCompare(this->x_type_, "date")) {
                double min =
                    (this->x_values_.empty()) ? 0 : *std::min_element(this->x_values_.begin(), this->x_values_.end());
                double max = (this->x_values_.empty())
                                 ? 24 * 3600
                                 : *std::max_element(this->x_values_.begin(), this->x_values_.end());
                transformation.setDataMinMaxX(min, max, this->baseDateX_);
            }
            else {
                double min =
                    (this->x_values_.empty()) ? 0 : *std::min_element(this->x_values_.begin(), this->x_values_.end());
                double max =
                    (this->x_values_.empty()) ? 100 : *std::max_element(this->x_values_.begin(), this->x_values_.end());
                transformation.setMinMaxX(min, max);
            }
        }
        if (transformation.getAutomaticY()) {
            if (magCompare(this->y_type_, "date")) {
                double min =
                    (this->y_values_.empty()) ? 0 : *std::min_element(this->y_values_.begin(), this->y_values_.end());
                double max = (this->y_values_.empty())
                                 ? 24 * 3600
                                 : *std::max_element(this->y_values_.begin(), this->y_values_.end());

                transformation.setDataMinMaxY(min, max, this->baseDateY_);
            }
            else {
                double min =
                    (this->y_values_.empty()) ? 0 : *std::min_element(this->y_values_.begin(), this->y_values_.end());
                double max =
                    (this->y_values_.empty()) ? 100 : *std::max_element(this->y_values_.begin(), this->y_values_.end());

                transformation.setMinMaxY(min, max);
            }
        }
    }
    catch (std::exception& e) {
        if (MagicsGlobal::strict()) {
            throw;
        }
        std::cout << "ERROR: TableDecoder::visit " << e.what() << endl;
    }
    catch (...) {
        if (MagicsGlobal::strict()) {
            throw;
        }
        std::cout << "ERROR: TableDecoder::visit (unknown exception)" << endl;
    }
}


void TableDecoder::customisedPoints(const std::set<string>&, CustomisedPointsList& out) {
    prepareXY();
    vector<double>::const_iterator x  = x_values_.begin();
    vector<double>::const_iterator y  = y_values_.begin();
    vector<double>::const_iterator xc = this->xc_values_.begin();
    vector<double>::const_iterator yc = this->yc_values_.begin();
    vector<double>::const_iterator v  = this->v_values_.begin();
    while (x != x_values_.end() || y != y_values_.end()) {
        CustomisedPoint* point = new CustomisedPoint();

        point->longitude(*x);
        point->latitude(*y);
        out.push_back(point);

        if (x != x_values_.end())
            (*point)["x"] = *(x++);
        if (y != y_values_.end())
            (*point)["y"] = *(y++);
        double speed = 0;
        if (xc != this->xc_values_.end() && yc != this->yc_values_.end()) {
            speed                   = sqrt((*xc * *xc) + (*yc * *yc));
            (*point)["x_component"] = *(xc++);
            (*point)["y_component"] = *(yc++);
        }
        if (v != this->v_values_.end()) {
            (*point)["colour_component"] = *(v++);
        }
        else
            (*point)["colour_component"] = speed;
    }
}


void TableDecoder::visit(TextVisitor& title) {
    // if ( !title_.empty() )
    //	title.add(new TextEntry(title_));

    if (MetviewIcon::info("_datatype") == "TABLE_flextra") {
        string s = "FLEXTRA: ";

        string date = MetviewIcon::info("startDate");
        string time = MetviewIcon::info("startTime");
        string lat  = MetviewIcon::info("startLat");
        string lon  = MetviewIcon::info("startLon");
        string lev  = MetviewIcon::info("startZ");

        s += MetviewIcon::info("direction") + " " + MetviewIcon::info("type");

        if (!date.empty()) {
            s += " " + date;
            if (!time.empty()) {
                s += " " + time;
            }
        }

        if (!lev.empty()) {
            s += " " + lev + "m";
        }

        if (!lat.empty() && !lon.empty()) {
            s += " " + MetviewIcon::info("startComment") + " (" + lon + ", " + lat + ")";
        }


        title.addAutomaticTitle(s);
    }
}


string TableDecoder::legendText(double valMin, double valMax) {
    if (MetviewIcon::info("_datatype") == "TABLE_flextra") {
        ostringstream sst;
        sst << valMin;
        string valStr = sst.str();

        string id, date, time, lev;
        id   = MetviewIcon::info("id_TR");
        date = MetviewIcon::info("startDate_TR");
        time = MetviewIcon::info("startTime_TR");
        lev  = MetviewIcon::info("startZ_TR");

        if (id.empty() || time.empty() || date.empty() || lev.empty()) {
            return string();
        }

        Tokenizer tk("/");
        vector<string> idVec, dateVec, timeVec, levVec;
        tk(id, idVec);
        tk(date, dateVec);
        tk(time, timeVec);
        tk(lev, levVec);

        if (idVec.size() != timeVec.size() || timeVec.size() != dateVec.size() || dateVec.size() != levVec.size()) {
            return string();
        }

        for (unsigned int i = 0; i < idVec.size(); i++) {
            if (idVec[i] == valStr) {
                return dateVec[i].substr(dateVec[i].size() - 2, 2) + "/" + timeVec[i].substr(0, 2) + " " + levVec[i] +
                       " m";
            }
        }
    }
    return string();
}


PointsHandler& TableDecoder::points(const Transformation& transformation, bool all) {
    // fisrt we adjust point!
    // we create another poin list with adjisted points!
    PointsList* list = new PointsList();

    for (vector<UserPoint*>::iterator point = begin(); point != end(); ++point) {
        UserPoint* pt = new UserPoint(**point);
        stack<UserPoint> duplicates;
        check(transformation, pt, duplicates);
        while (!duplicates.empty()) {
            list->push_back(new UserPoint(duplicates.top()));
            duplicates.pop();
        }
    }

    // then we filter them  we filter the points
    if (all)
        this->pointsHandlers_.push_back(new PointsHandler(*list));
    else
        this->pointsHandlers_.push_back(new BoxPointsHandler(*list, transformation, true));

    return *(this->pointsHandlers_.back());
}


bool TableDecoder::check(const Transformation& transformation, UserPoint* point, stack<UserPoint>& duplicates) {
    // We need to adjust the point if we have a date axis!
    if (magCompare(x_type_, "date")) {
        DateTime ref(transformation.getReferenceX());
        double shift = ref - baseDateX_;
        point->x_ -= shift;
    }
    if (magCompare(y_type_, "date")) {
        DateTime ref(transformation.getReferenceY());
        double shift = ref - baseDateY_;
        point->y_ -= shift;
    }

    transformation.wraparound(*point, duplicates);
    if (duplicates.empty()) {
        // we falg the point and add to the list!
        point->flagMissing();
        duplicates.push(*point);
        return false;  // point has been adjusted and is not in the transformation view
    }
    return true;  // point and eventual duplicates! has been adjusted and is in the transformation view!
}


void TableDecoder::customisedPoints(const Transformation& t, const std::set<string>& n, CustomisedPointsList& out,
                                    bool) {
    customisedPoints(t, n, out);
}


void TableDecoder::visit(ValuesCollector& points) {
    const Transformation& transformation = points.transformation();

    points.setCollected(true);

    if (points.size() <= 0 || size() == 0)
        return;

    if (values_.empty())
        points.setHasValue(false);

    for (ValuesCollector::iterator point = points.begin(); point != points.end(); ++point) {
        double y = (*point).y();  // lat
        double x = (*point).x();  // lon

        vector<int> idxV;
        for (int i = 0; i < size(); i++) {
            if (fabs(at(i)->y() - y) < points.searchRadiusY() && fabs(at(i)->x() - x) < points.searchRadiusX()) {
                idxV.push_back(i);
            }
        }

        if (idxV.size() == 0)
            continue;

        double dist = 10000000.;
        int minIdx  = -1;

        // MagLog::debug() << "odb collect idxV : " << lat << " " << lon << " " << idxV.size() << endl;

        for (int i = 0; i < idxV.size(); i++) {
            int idx = idxV[i];
            double d;
            if (transformation.coordinateType() == Transformation::GeoType) {
                d = magics::geoDistanceInKm(at(idx)->y(), at(idx)->x(), y, x);
            }
            else {
                d = (at(idx)->x() - x) * (at(idx)->x() - x) + (at(idx)->y() - y) * (at(idx)->y() - y);
            }

            if (d < dist) {
                minIdx = idx;
                dist   = d;
            }
        }
        if (minIdx >= 0)
            (*point).push_back(new ValuesCollectorData(at(minIdx)->x(), at(minIdx)->y(), at(minIdx)->value(), dist));
    }
}

double TableDecoder::indexToNumber(const string& str) {
    if (str.empty())
        return -1;

    double r;
    std::stringstream ss(str);
    ss >> r;
    return r;
}

}  // namespace magics
