/*
 * (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.
 */

/*! \defgroup projections Geographical projections

\section supportedProjection Supported projections

Currently Magics++ supports following projections:

 - Cylindrical
 - Polar-stereographic
 - Satellite

 You can select the projection by setting the parameter SUBPAGE_MAP_PROJECTION .

\section addProjection How to add a new projection

 - add your <i>NewProjection.xml</i> in <i>src/xml</i>

 - add this file in <i>src/xml/Makefile.am</i>

 - add <i>NewProjection.cc/.h</i> in <i>src/drivers/common/</i>,
   inheriting from Transformation, NewProjectionParameters

 - add the new files in <i>src/common/Makefile.am</i>

 - add new projection in src/common/SubPageAttributes.cc:\n
    static SimpleObjectMaker<NewProjection, Transformation> new_NewProjection("new_name");

 - add new projection in src/common/ViewAttributes.cc:\n
    static SimpleObjectMaker<NewProjection, Transformation> new_NewProjection("new_name");

 - Last but not least: add the new projection to the documentation!
*/

/*! \file Transformation.h

 Magics Team - ECMWF 2004
*/

#ifndef Transformation_H
#define Transformation_H

#include "magics.h"

#include "BasicGraphicsObject.h"
#include "CustomisedPoint.h"
#include "Factory.h"
#include "MagTranslator.h"
#include "PaperPoint.h"
#include "SceneVisitor.h"
#include "TeProjection.h"
#include "UserPoint.h"

namespace magics {

class Layout;
class GridPlotting;
class LabelPlotting;
class BasicSceneObject;
class MetaDataCollector;
class AbstractMatrix;
class MatrixHandler;
class PointsHandler;
class XmlNode;

class Polyline;

class ViewFilter {
public:
    ViewFilter() {}
    ViewFilter(double xmin, double xmax, double ymin, double ymax, double xres, double yres);
    ~ViewFilter() {}
    bool in(const PaperPoint& xy);

    vector<bool> done;
    double xmin_;
    double xmax_;
    double ymin_;
    double ymax_;
    double xres_;
    double yres_;
    int xdim_;
    int ydim_;
};

class Transformation {
public:
    Transformation();
    virtual ~Transformation();
    virtual void init();
    virtual void cleanPCEnveloppe();
    virtual void cleaninit();
    enum CoordinateType
    {
        GeoType,
        XyType
    };
    CoordinateType coordinateType() const { return coordinateType_; }

    virtual void geoProjection(int& geo) const { geo = 0; }  // Useful for Streamlines !

    virtual void toxml(ostream&) const {}
    // Xml Methods !
    virtual void set(const map<string, string>&) {}
    virtual void set(const XmlNode&) {}
    virtual bool accept(const string&) { return false; }
    void toxml(ostream&, int) const {}
    virtual Transformation* clone() const {
        Transformation* object = new Transformation();
        return object;
    }

    virtual Polyline& getUserBoundingBox() const { NOTIMP; }
    virtual Polyline& getPCBoundingBox() const { NOTIMP; }
    virtual Polyline& getSimplePCBoundingBox() const { NOTIMP; }

    bool needTopAxis() const { return topAxis_; }
    void needTopAxis(bool top) { topAxis_ = top; }

    virtual bool addSouth() const { return false; }

    virtual void operator()(const Polyline& poly, BasicGraphicsObjectContainer& out) const;

    string writeLongitude(const UserPoint&) const;
    string writeLatitude(const UserPoint&) const;

    virtual bool wrapAround(const Polyline&) const { return false; }
    virtual void coastSetting(map<string, string>&, double, double) const { NOTIMP; }
    virtual bool verifyDef(const string&) const { return false; }

    virtual double ratio() const { return 1; }

    virtual double x(double x) const { return x; }
    virtual double y(double y) const { return y; }
    virtual double rx(double x) const { return x; }
    virtual double ry(double y) const { return y; }

    /*
    // is the polyline out the projection!
    virtual bool out(const Polyline&) const override;
    // is the polyline in the projection?
    virtual bool in(const Polyline&) const override;
    */
    // is the point in projected area?
    virtual bool in(const UserPoint&) const;
    virtual bool in(const PaperPoint&) const;
    virtual bool inExtended(const PaperPoint&) const;

    // is the point in PC in the projected area?
    bool in(double x, double y) const;
    

    bool inX(double x) const { return (getAbsoluteMinX() <= x && x <= getAbsoluteMaxX()); }
    bool inY(double y) const { return (getAbsoluteMinY() <= y && y <= getAbsoluteMaxY()); }

    // Needed for Image processing!
    virtual TeProjection& getProjection() {
        static TeDatum datum;
        static TeLatLong dummy(datum);
        return dummy;
    }

    virtual void gridLongitudes(const GridPlotting&) const {}
    virtual void gridLatitudes(const GridPlotting&) const {}

    virtual void labels(const LabelPlotting&, DrawingVisitor&) const {}
    virtual void labels(const LabelPlotting&, LeftAxisVisitor&) const {}
    virtual void labels(const LabelPlotting&, RightAxisVisitor&) const {}
    virtual void labels(const LabelPlotting&, TopAxisVisitor&) const {}
    virtual void labels(const LabelPlotting&, BottomAxisVisitor&) const {}

    virtual void aspectRatio(double&, double&);
    virtual void fill(double&, double&);  // fill the space , can adapt the coordiantes to return the biggest area..
    virtual void tile(double&, double&);  // fill the space , can adapt the coordiantes to return the biggest area..
    virtual void forceNewArea(double, double, double, double, double&, double&);
    virtual UserPoint reference() const;

    // Basic reprojection method!
    virtual PaperPoint operator()(const UserPoint& xy) const { return PaperPoint(xy.x(), xy.y()); }

    virtual bool fast_reproject(double& x, double& y) const { return true; }

    virtual double patchDistance(double x) const { return x; }

    virtual PaperPoint operator()(const PaperPoint& xy) const { return xy; }
    virtual void operator()(const UserPoint& geo, Polyline& out) const;
    virtual void operator()(const UserPoint& xy, vector<PaperPoint>& out) const;
    void operator()(const Polyline& from, vector<Polyline*>& out) const;
    virtual void revert(const vector<std::pair<double, double> >&, vector<std::pair<double, double> >&) const;

    virtual void revert(const PaperPoint& xy, UserPoint& point) const { point = UserPoint(xy.x(), xy.y()); }

    // Does the projection needs the coastalines to be shifted!
    virtual bool needShiftedCoastlines() const { return false; }

    // Set the bounding box in user coordinates
    virtual void boundingBox(double&, double&, double&, double&) const;
    virtual void smallestBoundingBox(double& x1, double& y1, double& x2, double& y2) const {
        boundingBox(x1, y1, x2, y2);
    }

    virtual void setDataMinMaxX(double minx, double maxx) const {
        dataMinX_ = std::min(minx, dataMinX_);
        dataMaxX_ = std::max(maxx, dataMaxX_);
    }
    virtual void setDataMinMaxY(double miny, double maxy) const {
        dataMinY_ = std::min(miny, dataMinY_);
        dataMaxY_ = std::max(maxy, dataMaxY_);
    }

    virtual void setAutomaticX(bool) {}
    virtual void setAutomaticY(bool) {}
    virtual bool getAutomaticX() const { return false; }
    virtual bool getAutomaticY() const { return false; }

    virtual void setDataMinMaxX(double minx, double maxx, const string& ref) const;
    virtual void setDataMinMaxY(double miny, double maxy, const string& ref) const;

    virtual vector<double> getDataVectorMinX() const { return vector<double>(); }
    virtual vector<double> getDataVectorMaxX() const { return vector<double>(); }
    virtual vector<double> getDataVectorMinY() const { return vector<double>(); }
    virtual vector<double> getDataVectorMaxY() const { return vector<double>(); }

    virtual const string& getReferenceX() const { return referenceX_; }
    virtual const string& getReferenceY() const { return referenceY_; }
    void setReferenceX(const string& ref) const { referenceX_ = ref; }
    void setReferenceY(const string& ref) const { referenceY_ = ref; }

    virtual void adjustXAxis(Layout&) const {}
    virtual void adjustYAxis(Layout&) const {}

    virtual double getWidth() { return -1; }
    virtual double getHeight() { return -1; }

    virtual double x(const string& val) const { return tonumber(val); }
    virtual double y(const string& val) const { return tonumber(val); }

    virtual double getMinX() const { return -1; }
    virtual double getMaxX() const { return -1; }
    virtual double getMinY() const { return -1; }
    virtual double getMaxY() const { return -1; }

    virtual void setMinMaxX(double, double) {}
    virtual void setMinMaxY(double, double) {}

    virtual double getMinPCX() const { return -1; }
    virtual double getMaxPCX() const { return -1; }
    virtual double getMinPCY() const { return -1; }
    virtual double getMaxPCY() const { return -1; }
    // Add the gutter information : Useful for wind tiling
    virtual double getExtendedMinPCX() const { return getMinPCX(); }
    virtual double getExtendedMaxPCX() const { return getMaxPCX(); }
    virtual double getExtendedMinPCY() const { return getMinPCY(); }
    virtual double getExtendedMaxPCY() const { return getMaxPCY(); }

    virtual double dimension(BasicGraphicsObjectContainer& parent) const { return parent.absoluteWidth(); }

    double getAbsoluteMinPCX() const { return std::min(getMinPCX(), getMaxPCX()); }
    double getAbsoluteMaxPCX() const { return std::max(getMinPCX(), getMaxPCX()); }
    double getAbsoluteMinPCY() const { return std::min(getMinPCY(), getMaxPCY()); }
    double getAbsoluteMaxPCY() const { return std::max(getMinPCY(), getMaxPCY()); }

    double getAbsoluteMinX() const { return std::min(getMinX(), getMaxX()); }
    double getAbsoluteMaxX() const { return std::max(getMinX(), getMaxX()); }
    double getAbsoluteMinY() const { return std::min(getMinY(), getMaxY()); }
    double getAbsoluteMaxY() const { return std::max(getMinY(), getMaxY()); }

    virtual void setNewPCBox(double, double, double, double) {}
    virtual double unitToCm(double, double) const;

    virtual double height() const { return getMaxX() - getMinX(); }

    virtual void thin(double, PaperPoint&, vector<pair<double, double> >&) const;
    virtual void thin(double, PaperPoint&, Matrix&, double) const;
    virtual void getNewDefinition(const UserPoint&, const UserPoint&, string&) const { NOTIMP; }
    virtual void setDefinition(const string&) { NOTIMP; }

    virtual void thin(MatrixHandler&, double x, double y, vector<UserPoint>&) const;

    virtual void reprojectComponents(double&, double&, pair<double, double>&) const;
    virtual void reprojectSpeedDirection(const PaperPoint& point, pair<double, double>&) const;

    void thin(PointsHandler& points, vector<PaperPoint>& out, vector<PaperPoint>&) const;
    void thin(MatrixHandler& points, vector<PaperPoint>& out, vector<PaperPoint>&) const;

    // The view is set in Projection coordinates!
    virtual void filterView(double xmin, double xmax, double ymin, double ymax, double x, double y) const {
        view_ = ViewFilter(xmin, xmax, ymin, ymax, x, y);
    }
    virtual void collect(MetaDataCollector&) const {}
    virtual void visit(MetaDataVisitor&, double, double, double, double, double, double);
    virtual string xAxisType() const { return "regular"; }
    virtual string yAxisType() const { return "regular"; }

    virtual MatrixHandler* prepareData(const AbstractMatrix& matrix) const;
    virtual void wraparound(const UserPoint&, stack<UserPoint>&) const;
    virtual void populate(double lon, double lat, double val, vector<UserPoint>& out) const {
        if (in(lon, lat))
            out.push_back(UserPoint(lon, lat, val));
    }
    double distance(const UserPoint&, const UserPoint&) const;
    string name() const { return name_; }

    void setOutputDimension(double width, double height) { 
        outputWidth_ = width; 
        outputHeight_ = height;
    }

protected:
    virtual void print(ostream&) const;

    CoordinateType coordinateType_;

    string name_;

    mutable double areaMinX_;
    mutable double areaMaxX_;
    mutable double areaMinY_;
    mutable double areaMaxY_;

    mutable double dataMinX_;
    mutable double dataMaxX_;
    mutable double dataMinY_;
    mutable double dataMaxY_;

    mutable string dataReferenceX_;
    mutable string dataReferenceY_;
    mutable string referenceX_;
    mutable string referenceY_;

    mutable ViewFilter view_;

    bool topAxis_;

    mutable Polyline* userEnveloppe_;
    mutable Polyline* PCEnveloppe_;
    mutable Polyline* PCExtendedEnveloppe_;

    // For tiling Mode keep the position in pixel of the asked topleft corner!
    int xTile_;
    int yTile_;
    double askedxmin_;
    double askedxmax_;
    double askedymin_;
    double askedymax_;
    double askedWidth_;
    double askedHeight_;
    double originX_;
    double originY_;
    int tile_;
    int zoomLevel_;
    double unit_;
    double unitEpsilon_;

    double outputWidth_;
    double outputHeight_;

private:
    // No copy allowed
    Transformation(const Transformation&);
    Transformation& operator=(const Transformation&);

    // -- Friends
    friend ostream& operator<<(ostream& s, const Transformation& p) {
        p.print(s);
        return s;
    }
};

template <>
class MagTranslator<string, Transformation> {
public:
    Transformation* operator()(const string& val) { return SimpleObjectMaker<Transformation>::create(val); }
    Transformation* magics(const string& param) {
        Transformation* object;
        ParameterManager::update(param, object);
        return object;
    }
};

}  // namespace magics
#endif
