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

    Magics Team - ECMWF 2004

    Started: Thu 6-May-2004

    Changes:

*/

#include "XYList.h"


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

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

void XYList::prepare() {
    if (!this->empty())
        return;
    vector<double>::const_iterator i = x_.begin();
    vector<double>::const_iterator j = y_.begin();

    while (i != x_.end() && j != y_.end()) {
        this->push_back(new UserPoint(*i, *j, *j));
        i++;
        j++;
    }
    /*
        vector<double>::const_reverse_iterator i2 = x2_.rbegin();
        vector<double>::const_reverse_iterator j2 = y2_.rbegin();

        vector<double>::const_reverse_iterator ei2 = x2_.rend();
        vector<double>::const_reverse_iterator ej2 = y2_.rend();
        while ( i2 != ei2 && j2 != ej2 ) {
            this->push_back(new UserPoint(*i2, *j2, *j2));
            i2++;
            j2++;
        }
    */
}


void XYList::customisedPoints(const std::set<string>& needs, CustomisedPointsList& out) {
    vector<double>::const_iterator x          = x_.begin();
    vector<double>::const_iterator y          = y_.begin();
    vector<double>::const_reverse_iterator x2 = x2_.rbegin();
    vector<double>::const_reverse_iterator y2 = y2_.rbegin();
    vector<double>::const_iterator x_upper    = x_upper_.begin();
    vector<double>::const_iterator y_upper    = y_upper_.begin();
    vector<double>::const_iterator x_lower    = x_lower_.begin();
    vector<double>::const_iterator y_lower    = y_lower_.begin();

    bool bar  = (needs.find("bar") != needs.end());
    bool area = (needs.find("area") != needs.end());
    while (x != x_.end()) {
        CustomisedPoint* point = new CustomisedPoint();

        if (x != x_.end()) {
            if (same(*x, x_missing_))
                point->missing(true);
            (*point)["x"] = *(x++);
        }
        if (y != y_.end()) {
            if (same(*y, y_missing_))
                point->missing(true);
            (*point)["y"] = *(y++);
        }

        if (bar) {
            if (x_lower != x_lower_.end()) {
                if (same(*x_lower, x_missing_))
                    point->missing(true);
                (*point)["x_lower"] = *(x_lower++);
            }
            if (y_lower != y_lower_.end()) {
                if (same(*y_lower, y_missing_))
                    point->missing(true);
                (*point)["y_lower"] = *(y_lower++);
            }
            if (x_upper != x_upper_.end()) {
                if (same(*x_upper, x_missing_))
                    point->missing(true);
                (*point)["x_upper"] = *(x_upper++);
            }
            if (y_upper != y_upper_.end()) {
                if (same(*y_upper, y_missing_))
                    point->missing(true);
                (*point)["y_upper"] = *(y_upper++);
            }
        }
        out.push_back(point);
    }

    // now we add the lower values in case one!
    if (needs.find("area") == needs.end())
        return;


    while (x2 != x2_.rend() && y2 != y2_.rend()) {
        CustomisedPoint* point = new CustomisedPoint();
        if (same(*x2, x_missing_))
            point->missing(true);
        (*point)["x"] = *(x2++);

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


        if (bar) {
            if (x_lower != x_lower_.end()) {
                if (same(*x_lower, x_missing_))
                    point->missing(true);
                (*point)["x_lower"] = *(x_lower++);
            }
            if (y_lower != y_lower_.end()) {
                if (same(*y_lower, y_missing_))
                    point->missing(true);
                (*point)["y_lower"] = *(y_lower++);
            }
            if (x_upper != x_upper_.end()) {
                if (same(*x_upper, x_missing_))
                    point->missing(true);
                (*point)["x_upper"] = *(x_upper++);
            }
            if (y_upper != y_upper_.end()) {
                if (same(*y_upper, y_missing_))
                    point->missing(true);
                (*point)["y_upper"] = *(y_upper++);
            }
        }
        out.push_back(point);
    }
}


void XYList::customisedPoints(const Transformation&, const std::set<string>& s, CustomisedPointsList& out) {
    customisedPoints(s, out);
}

double offset(const string& val) {
    static map<string, double> offsets;
    if (offsets.empty()) {
        offsets["hours"]   = 3600;
        offsets["hour"]    = 3600;
        offsets["days"]    = 3600 * 24;
        offsets["day"]     = 3600 * 24;
        offsets["minute"]  = 60;
        offsets["minutes"] = 60;
        offsets["second"]  = 1;
    }
    map<string, double>::iterator off = offsets.find(lowerCase(val));
    return (off != offsets.end()) ? off->second : 1;
}

void XYList::getReady(const Transformation& transformation) {
    try {
        if (transformation.xAxisType() == "date") {
            // Fisrt create the x_date if base date and offset are used!
            if (x_date_.empty() && !x_base_.empty()) {
                DateTime base(x_base_);
                double off = offset(x_offset_);
                for (vector<double>::const_iterator x = x_.begin(); x != x_.end(); ++x) {
                    DateTime date(base + Second((*x) * off));
                    x_date_.push_back(date);
                }
            }
            if (!x_date_.empty())
                x_.clear();
            for (vector<string>::const_iterator x = x_date_.begin(); x != x_date_.end(); ++x)
                x_.push_back(transformation.x(*x));
        }
    }
    catch (...) {
    }
    try {
        // Fisrt create the x_date if base date and offset are used!
        if (transformation.yAxisType() == "date") {
            if (y_date_.empty() && !y_base_.empty()) {
                DateTime base(y_base_);
                double off = offset(y_offset_);
                for (vector<double>::const_iterator y = y_.begin(); y != y_.end(); ++y) {
                    DateTime date(base + Second((*y) * off));
                    y_date_.push_back(date);
                }
            }
            if (!y_date_.empty())
                y_.clear();
            for (vector<string>::const_iterator y = y_date_.begin(); y != y_date_.end(); ++y)
                y_.push_back(transformation.y(*y));
        }
    }
    catch (...) {
    }

    try {
        if (transformation.xAxisType() == "date") {
            // Fisrt create the x_date if base date and offset are used!
            if (x2_date_.empty() && !x2_base_.empty()) {
                DateTime base(x2_base_);
                double off = offset(x2_offset_);
                for (vector<double>::const_iterator x = x2_.begin(); x != x2_.end(); ++x) {
                    DateTime date(base + Second((*x) * off));
                    x2_date_.push_back(date);
                }
            }
            if (!x2_date_.empty())
                x2_.clear();
            for (vector<string>::const_iterator x = x2_date_.begin(); x != x2_date_.end(); ++x)
                x2_.push_back(transformation.x(*x));
        }
    }
    catch (...) {
    }
    try {
        // Fisrt create the x_date if base date and offset are used!
        if (transformation.yAxisType() == "date") {
            if (y2_date_.empty() && !y2_base_.empty()) {
                DateTime base(y2_base_);
                double off = offset(y2_offset_);
                for (vector<double>::const_iterator y = y2_.begin(); y != y2_.end(); ++y) {
                    DateTime date(base + Second((*y) * off));
                    y2_date_.push_back(date);
                }
            }
            if (!y2_date_.empty())
                y2_.clear();
            for (vector<string>::const_iterator y = y2_date_.begin(); y != y2_date_.end(); ++y)
                y2_.push_back(transformation.y(*y));
        }
    }
    catch (...) {
    }

    try {
        for (vector<string>::const_iterator x = x_upper_date_.begin(); x != x_upper_date_.end(); ++x)
            x_upper_.push_back(transformation.x(*x));
    }
    catch (...) {
    }
    try {
        for (vector<string>::const_iterator y = y_upper_date_.begin(); y != y_upper_date_.end(); ++y)
            y_upper_.push_back(transformation.y(*y));
    }
    catch (...) {
    }

    try {
        for (vector<string>::const_iterator x = x_lower_date_.begin(); x != x_lower_date_.end(); ++x)
            x_lower_.push_back(transformation.x(*x));
    }
    catch (...) {
    }
    try {
        for (vector<string>::const_iterator y = y_lower_date_.begin(); y != y_lower_date_.end(); ++y)
            y_lower_.push_back(transformation.y(*y));
    }
    catch (...) {
    }
}


void XYList::visit(Transformation& transformation) {
    // get the data ...
    try {
        this->prepare();

        if (transformation.getAutomaticX()) {
            transformation.setMinMaxX(*std::min_element(this->x_.begin(), this->x_.end()),
                                      *std::max_element(this->x_.begin(), this->x_.end()));
        }
        if (transformation.getAutomaticY()) {
            transformation.setMinMaxY(*std::min_element(this->y_.begin(), this->y_.end()),
                                      *std::max_element(this->y_.begin(), this->y_.end()));
        }
    }
    catch (...) {
    }
}


void XYList::points(const Transformation&, vector<UserPoint>& data) {
    // here the data are already ready!
    data.reserve(x_.size());
    vector<double>::const_iterator x = x_.begin();
    vector<double>::const_iterator y = y_.begin();
    while (x != x_.end() && y != y_.end()) {
        UserPoint point(*x, *y);
        if (*x == this->x_missing_ || *y == this->y_missing_)
            point.flagMissing();
        data.push_back(point);
        ++x;
        ++y;
    }
}
