/*
 * (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 InputData.cc
    \brief Implementation of the Template class InputData.

    Magics Team - ECMWF 2004

    Started: Thu 6-May-2004

    Changes:

*/

#include "InputData.h"
#include "SciMethods.h"

#include "magics_windef.h"
#ifdef MAGICS_ON_WINDOWS
#include <iterator>
#endif

/*!
 Class information are given to the output-stream.
*/

void InputData::print(ostream& out) const {
    out << "InputData[";
    InputDataAttributes::print(out);
    out << "]";
}


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


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


void InputData::prepare() {
    if (x_values_.size())
        return;

    if (magCompare(x_type_, "date")) {
        dateSetting(date_x_, x_values_, baseDateX_, true);
        dateSetting(date_x2_, x2_values_, baseDateX_, false);
    }
    else {
        numberSetting(x_, x_values_);
        numberSetting(x2_, x2_values_);
        numberSetting(longitudes_, x_values_);
    }
    if (magCompare(y_type_, "date")) {
        dateSetting(date_y_, y_values_, baseDateY_, true);
        dateSetting(date_y2_, y2_values_, baseDateY_, false);
    }
    else {
        numberSetting(y_, y_values_);
        numberSetting(y2_, y2_values_);
        numberSetting(latitudes_, y_values_);
    }

    vector<double>::iterator x          = x_values_.begin();
    vector<double>::iterator y          = y_values_.begin();
    vector<double>::iterator v          = values_.begin();
    vector<double>::reverse_iterator x2 = x2_values_.rbegin();
    vector<double>::reverse_iterator y2 = y2_values_.rbegin();

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

void InputData::customisedPoints(const Transformation& transformation, const std::set<string>& needs,
                                 CustomisedPointsList& out, bool) {
    customisedPoints(transformation, needs, out);
}
void InputData::customisedPoints(const Transformation& transformation, const std::set<string>& needs,
                                 CustomisedPointsList& out) {
    prepare();
    vector<double>::const_iterator x  = x_values_.begin();
    vector<double>::const_iterator x2 = x2_values_.begin();
    vector<double>::const_iterator y  = y_values_.begin();
    vector<double>::const_iterator y2 = y2_values_.begin();
    vector<double>::const_iterator xc = x_component_.begin();
    vector<double>::const_iterator yc = y_component_.begin();
    vector<double>::const_iterator v  = values_.begin();
    while (x != x_values_.end() && y != y_values_.end()) {
        double valx = *x;
        double valy = *y;
        if (magCompare(x_type_, "date")) {
            DateTime ref(transformation.getReferenceX());
            double shift = ref - baseDateX_;
            valx -= shift;
        }
        if (magCompare(y_type_, "date")) {
            DateTime ref(transformation.getReferenceY());
            double shift = ref - baseDateY_;
            valy -= shift;
        }

        CustomisedPoint* point = new CustomisedPoint();


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


        if (same(*x, x_missing_))
            point->missing(true);
        (*point)["x"]       = valx;
        (*point)["x_lower"] = valx;
        x++;

        if (same(*y, y_missing_))
            point->missing(true);
        (*point)["y"]       = valy;
        (*point)["y_lower"] = valy;
        y++;

        double speed = 0;
        double angle = 0;
        if (xc != x_component_.end() && yc != y_component_.end()) {
            speed = sqrt((*xc * *xc) + (*yc * *yc));
            angle = (270. - atan2(*yc, *xc) * (180 / M_PI));
            if (angle == 0)
                angle = 0.1;
            (*point)["x_component"] = *(xc++);
            (*point)["y_component"] = *(yc++);
            (*point)["intensity"]   = speed;
            (*point)["direction"]   = angle;
        }
        if (v != values_.end()) {
            (*point)["colour_component"] = *(v++);
        }
        else
            (*point)["colour_component"] = speed;

        if (needs.find("bar") != needs.end()) {
            if (y2 != y2_values_.end()) {
                (*point)["y_upper"] = *(y2++);
            }
            if (x2 != x2_values_.end()) {
                (*point)["x_upper"] = *(x2++);
            }
        }
    }


    if (needs.find("area") == needs.end())
        return;
    vector<double>::reverse_iterator xr2 = x2_values_.rbegin();
    vector<double>::reverse_iterator yr2 = y2_values_.rbegin();
    double shiftx                        = 0;
    double shifty                        = 0;
    if (magCompare(x_type_, "date")) {
        DateTime ref(transformation.getReferenceX());
        shiftx = ref - baseDateX_;
    }
    if (magCompare(y_type_, "date")) {
        DateTime ref(transformation.getReferenceX());
        shifty = ref - baseDateY_;
    }
    while (xr2 != x2_values_.rend() && yr2 != y2_values_.rend()) {
        double x = *xr2;

        CustomisedPoint* point = new CustomisedPoint();
        if (same(*xr2, x_missing_))
            point->missing(true);
        (*point)["x"] = *(xr2++) - shiftx;

        if (same(*yr2, y_missing_))
            point->missing(true);
        (*point)["y"] = *(yr2++) - shifty;
        out.push_back(point);
    }
}

void InputData::customisedPoints(const std::set<string>&, CustomisedPointsList& out) {
    prepare();
    vector<double>::const_iterator x  = x_values_.begin();
    vector<double>::const_iterator y  = y_values_.begin();
    vector<double>::const_iterator xc = x_component_.begin();
    vector<double>::const_iterator yc = y_component_.begin();
    vector<double>::const_iterator 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_.end())
            (*point)["x"] = *(x++);
        if (y != y_.end())
            (*point)["y"] = *(y++);
        double speed = 0;
        if (xc != x_component_.end() && yc != y_component_.end()) {
            speed                   = sqrt((*xc * *xc) + (*yc * *yc));
            (*point)["x_component"] = *(xc++);
            (*point)["y_component"] = *(yc++);
        }
        if (v != values_.end()) {
            (*point)["colour_component"] = *(v++);
        }
        else
            (*point)["colour_component"] = speed;
    }
}

MatrixHandler& InputData::matrix() {
    if (!matrix_) {
        prepare();
        if (binning_) {
            matrix_ = (*binning_)(*this);
        }
        else if (input_binning_.get())
            matrix_ = (*input_binning_)(*this);
    }

    matrixHandlers_.push_back(new MatrixHandler(*matrix_));
    return *(matrixHandlers_.back());
}


void InputData::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 (...) {
    }
}
pair<double, double> adjust(double min, double max) {
    double epsilon = max > min ? 0.00001 : -0.00001;
    min            = min - (min * epsilon);
    max            = max + (max * epsilon);

    return make_pair(min, max);
}

void InputData::visit(Transformation& transformation) {
    // get the data ...
    try {
        if (!input_automatic_)
            return;
        prepare();
        if (transformation.getAutomaticX()) {
            double min = INT_MAX;
            double max = -INT_MAX;
            for (vector<double>::iterator x = x_values_.begin(); x != x_values_.end(); ++x) {
                if (same(*x, x_missing_))
                    continue;
                if (min > *x)
                    min = *x;
                if (max < *x)
                    max = *x;
            }
            for (vector<double>::iterator x = x2_values_.begin(); x != x2_values_.end(); ++x) {
                if (same(*x, x_missing_))
                    continue;
                if (min > *x)
                    min = *x;
                if (max < *x)
                    max = *x;
            }
            pair<double, double> minmax = adjust(min, max);


            if (magCompare(x_type_, "date")) {
                transformation.setDataMinMaxX(minmax.first, minmax.second, baseDateX_);
            }
            else {
                transformation.setMinMaxX(minmax.first, minmax.second);
            }
        }
        if (transformation.getAutomaticY()) {
            double min = INT_MAX;
            double max = -INT_MAX;
            for (vector<double>::iterator y = y_values_.begin(); y != y_values_.end(); ++y) {
                if (same(*y, y_missing_))
                    continue;
                if (min > *y)
                    min = *y;
                if (max < *y)
                    max = *y;
            }
            for (vector<double>::iterator y = y2_values_.begin(); y != y2_values_.end(); ++y) {
                if (same(*y, y_missing_))
                    continue;
                if (min > *y)
                    min = *y;
                if (max < *y)
                    max = *y;
            }
            pair<double, double> minmax = adjust(min, max);
            if (magCompare(y_type_, "date")) {
                transformation.setDataMinMaxY(minmax.first, minmax.second, baseDateY_);
            }
            else {
                transformation.setMinMaxY(minmax.first, minmax.second);
            }
        }
    }
    catch (...) {
    }
}


PointsHandler& InputData::points(const Transformation& transformation, bool all) {
    prepare();

    if (magCompare(x_type_, "date") || magCompare(y_type_, "date")) {
        // create a new points list!
        PointsList* points = new PointsList();
        for (PointsList::iterator pt = begin(); pt != end(); ++pt) {
            UserPoint* point = *pt;
            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;
            }
            if (!transformation.in(*point)) {
                point->flagMissing();
                if (all)
                    points->push_back(point);
                else
                    delete point;
            }
            else {
                points->push_back(point);
            }
        }
        PointsHandler* handler;
        if (all)
            handler = new PointsHandler(*points);
        else
            handler = new BoxPointsHandler(*points, transformation, false);
        pointsHandlers_.push_back(handler);
    }
    else {
        if (all)
            pointsHandlers_.push_back(new PointsHandler(*this));
        else
            pointsHandlers_.push_back(new BoxPointsHandler(*this, transformation, false));
    }

    return *(pointsHandlers_.back());
}


void InputData::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));
    }
}
