/*
 * (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 drivers Output drivers

   \section generalDriver Overview

   Magics supports various vector and raster graphical output formats. Within Magics
   a generic description of graphics objects is generated which
   than gets converted by a chosen driver to the requested format(s).

   \sa DriverManager::DriverManager(), OutputHandler(), OutputFactory()

   \section usingDrivers Using drivers

   In MagML the \<drivers\> Tag is used to define all drivers being used. Each output
   format

   Example test/MagML/drivers.magml test/C/drivers.c

   \section addDriver How to add a new output format / driver

   Let's assume you try to add a new driver called <i>NewDriver</i>.

   - go into the <i>tools</i> directory and run <i>perl newdriver New</i>
   - edit your <i>NewDriver.xml</i> and copy it to <i>src/xml</i>
   - add this file in <i>src/xml/Makefile.am</i>
   - edit <i>NewDriver.cc/.h</i> and copy them to <i>src/drivers/</i>
   - add the new files in <i>src/drivers/Makefile.am</i>
   - look at <i>NewDriver.todo</i> and add factories in:
        -# <i>src/common/OutputHandler.cc</i>
        -# <i>src/common/OutputFactory.cc/.h</i>

*/

/*! \file BaseDriver.h
    \brief Definition of driver base class.
    \author Meteorological Visualisation Section, ECMWF

    Started: Jan 2004

*/
#ifndef MPP_BaseDriver_H
#define MPP_BaseDriver_H

#include <stack>

#include "BaseDriverAttributes.h"
#include "Colour.h"
#include "MagTranslator.h"
#include "MagicsObserver.h"
#include "magics.h"

namespace magics {

class Layer;
class SceneLayer;
class StaticLayer;
class NoDataLayer;
class StepLayer;

class Layout;
class SceneLayout;
class LegendLayout;
class StartPage;
class EndPage;
class RootLayout;
class BinaryObject;
class Polyline;

class Text;
class Symbol;
class TextSymbol;
class ClearObject;
class ComplexSymbol;
class TextItem;
class FlagItem;
class SymbolItem;

class Image;
class Arrow;
class Flag;
class ImportObject;
class PaperPoint;

struct ShadingProperties;
struct FillShadingProperties;
struct DotShadingProperties;
struct HatchShadingProperties;

class SelectionMode;
class MtInputEvent;
class PreviewLayout;
class HistoLayout;
class MagnifierLayout;

/*!
 Codes for Brinary reading and writing
*/
#define BINARY_VERSION 2  // last updated with version 2.15.0

typedef double MFloat;


/*!
 \brief helper compare function for string maps

 The function follows the descriptionn of Josuttis's STL book at page 213.
*/
class RuntimeStringCompare {
    static bool nocase_compare(char c1, char c2) { return toupper(c1) < toupper(c2); }

public:
    RuntimeStringCompare() {}
    bool operator()(const string& s1, const string& s2) const {
        return lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), nocase_compare);
    }
};

/*!
 \brief Method to extract points of Polylines

 First all possible commas are replaced by spaces and than all points are read through a stream
*/
inline void parsePoints(vector<PaperPoint>& vPP, string& points) {
    string::size_type pos = points.find_first_of(",");

    while (pos != string::npos) {
        points.replace(pos, 1, " ", 1);
        pos = points.find_first_of(",", pos);
    }

    istringstream totalString(points);
    MFloat x, y;

    while (!totalString.eof()) {
        totalString >> x >> y;
        PaperPoint pp;
        pp.x(x);
        pp.y(y);
        vPP.push_back(pp);
    }
}

/*!
 \brief struct describing an XML element

 Helper construct for symbol plotting.
*/
struct xmlElement {
    string name;
    std::map<string, string> attributes;
    xmlElement() : name("") {}
};

/*!
 \brief struct describing an SVG element

 Helper construct for symbol plotting.
*/
struct svgSymbol {
    string id;
    vector<xmlElement> elements;
    svgSymbol() : id("") {}
};

typedef vector<svgSymbol> vSymbols;

/*!
 \brief struct describing a entry in the font table

 All fonts read in by readFonts() are stored in this way.

 \sa readFonts()
*/
struct magFont {
    int id;
    string magics_name;
    string ps_name;
    string ps_filename;
    string ttf_filename;
    string css_name;
};

struct PixmapInput {
    string filename;
    GraphicsFormat format;
    int resolution;
    MFloat x0;
    MFloat y0;
    MFloat x1;
    MFloat y1;
};

struct Pixmap {
    MFloat x0;
    MFloat y0;
    MFloat x1;
    MFloat y1;
    int w;
    int h;
    unsigned char* pixmap;
    int landscape;
    bool alpha;
    bool offset;
};


/*! \class BaseDriver
    \brief Base class for all drivers of Magics.
    \ingroup drivers

    This abstract class provides an interface for Magics
    to call various device drivers.

    Every new driver has to inherit this class and has
    to provide own implementation of abstract members.
*/
class BaseDriver : public BaseDriverAttributes, public MagicsObserver {
public:
    BaseDriver();
    virtual ~BaseDriver() override;

    virtual void set(const XmlNode& node) override { BaseDriverAttributes::set(node); }
    void setWidth(double width) { width_ = width; }

    virtual void set(const std::map<string, string>& map) override { BaseDriverAttributes::set(map); }

    virtual void open() {}
    virtual void close() {}
    void printOutputName(const std::string& str) const;

    virtual void project(const Layout&) const {}
    virtual void unproject() const {}

    virtual void newLayer() const { debugOutput("BaseDriver::newLayer"); }
    virtual void closeLayer() const { debugOutput("BaseDriver::closeLayer"); }
    virtual void newLayer(Layer&) const { debugOutput("BaseDriver::newLayer()"); }
    virtual void closeLayer(Layer&) const { debugOutput("BaseDriver::closeLayer()"); }
    virtual void newLayer(StaticLayer&) const { debugOutput("BaseDriver::newStaticLayer"); }
    virtual void closeLayer(StaticLayer&) const { debugOutput("BaseDriver::closeStaticLayer"); }
    virtual void newLayer(StepLayer&) const { debugOutput("BaseDriver::newStepLayer"); }
    virtual void closeLayer(StepLayer&) const { debugOutput("BaseDriver::closeStepLayer"); }

    intarray frames() const { return frame_list_; }

    MAGICS_NO_EXPORT void redisplay(const StartPage&) const;
    MAGICS_NO_EXPORT void redisplay(const EndPage&) const;

    MAGICS_NO_EXPORT void redisplay(const Layout&) const;
    MAGICS_NO_EXPORT void redisplay(const RootLayout&) const;
    virtual MAGICS_NO_EXPORT void redisplay(const LegendLayout&) const;
    virtual MAGICS_NO_EXPORT void redisplay(const SceneLayout&) const;

    virtual MAGICS_NO_EXPORT void redisplay(const Layer&) const;
    virtual MAGICS_NO_EXPORT void redisplay(const SceneLayer&) const;
    virtual MAGICS_NO_EXPORT void redisplay(const StaticLayer&) const;
    virtual MAGICS_NO_EXPORT void redisplay(const NoDataLayer&) const;
    virtual MAGICS_NO_EXPORT void redisplay(const StepLayer&) const;
    virtual MAGICS_NO_EXPORT void redisplay(const Polyline&) const;


#ifdef MAGICS_QT
    virtual MAGICS_NO_EXPORT void redisplay(const PreviewLayout&) const {}
    virtual MAGICS_NO_EXPORT void redisplay(const HistoLayout&) const {}
    virtual MAGICS_NO_EXPORT void redisplay(const MagnifierLayout&) const {}
#endif
    typedef void (BaseDriver::*ModeFunction)(const SelectionMode&);
    typedef void (BaseDriver::*ControlFunction)(bool);
    typedef void (BaseDriver::*InputEventFunction)(MtInputEvent*);

    virtual void redisplay(const BinaryObject&) const;
    void setDimensionsFromBinary(string mbg_tmpl, MFloat& ratio, int& width) const;

    //! Method to redisplay a Text.
    MAGICS_NO_EXPORT void redisplay(const Text&) const;
    //! Method to redisplay a Symbol.
    MAGICS_NO_EXPORT void redisplay(const Symbol&) const;
    //! Method to redisplay a TextSymbol.
    MAGICS_NO_EXPORT void redisplay(const TextSymbol&) const;
    //! Method to redisplay a ComlpexSymbol.
    void redisplay(const ComplexSymbol&) const;
    void redisplay(const ClearObject&) const;
    void redisplay(const TextItem&, const ComplexSymbol&) const;
    void redisplay(const FlagItem&, const ComplexSymbol&) const;
    void redisplay(const SymbolItem&, const ComplexSymbol&) const;

    //! Method to redisplay an Arrow.
    virtual MAGICS_NO_EXPORT void redisplay(const Arrow&) const;
    //! Method to redisplay an Flag.
    virtual MAGICS_NO_EXPORT void redisplay(const Flag&) const;
    //! Method to redisplay a external file.
    void redisplay(const ImportObject&) const;
    //! Method to redisplay a external file.
    MAGICS_NO_EXPORT void redisplay(const Image&) const;

    void shade(const FillShadingProperties&) const;
    void shade(const DotShadingProperties&) const;
    void shade(const HatchShadingProperties&) const;

    bool disable() const { return disabled_; }
    void disable(bool disabled) { disabled_ = disabled; }


    /*!
     SUPER_PAGE_X_LENGTH Default: 29.7
    */
    MAGICS_NO_EXPORT void setXDeviceLength(MFloat xdevicelength) { xDeviceLength_ = xdevicelength; }
    MAGICS_NO_EXPORT MFloat getXDeviceLength() const { return xDeviceLength_; }

    /*!
     SUPER_PAGE_Y_LENGTH Default: 21.0
    */
    MAGICS_NO_EXPORT void setYDeviceLength(MFloat ydevicelength) { yDeviceLength_ = ydevicelength; }
    MAGICS_NO_EXPORT MFloat getYDeviceLength() const { return yDeviceLength_; }


protected:
    MFloat convertCM(const MFloat cm) const { return cm * cmScale_; }
    void setCMscale(const MFloat scale) const { cmScale_ = scale; }

    /*!
      \brief set Y values positive or negative

      Some drivers hava a Y axis which is positive towards the bottom of the plot
      and these need to set this method negative.
    */
    virtual MAGICS_NO_EXPORT MFloat setY(const MFloat y) const { return y; }

    /*!
      \brief set Y values positive or negative dependent how symbols are interpreted

    */
    virtual MAGICS_NO_EXPORT MFloat setSymbolY(const MFloat y) const { return y; }

    /*!
      \brief set Y values positive or negative dependent how angles are interpreted

      Some drivers hava a angle orientation different to PS
      and these need to set this method negative.
    */
    virtual MAGICS_NO_EXPORT MFloat setAngleY(const MFloat y) const { return y; }
    virtual MAGICS_NO_EXPORT MFloat setFlagY(const MFloat y) const { return y; }

    string getFileName(const string& extension, const unsigned int no = 0) const;

    //! Method to print string about this class on to a stream of type ostream (virtual).
    virtual void print(ostream&) const override;
    virtual void startPage() const {}
    virtual void endPage() const {}

    virtual void setNewColour(const Colour&) const {}
    virtual void printLine(const Polyline& line) const;

    virtual void renderText(const Text&) const {}
    virtual void debugOutput(const string& s) const {
        if (debug_) MagLog::debug() << " DRIVERS: " << s << "\n";
    }

    virtual MFloat projectX(const MFloat x) const { return coordRatioX_ * x; }
    virtual MFloat projectY(const MFloat y) const { return coordRatioY_ * y; }


    double LSF(MFloat* x, MFloat* y, int i0) const;
    double arrowHeadLSF(MFloat* x, MFloat* y, int i0) const;
    void renderArrowHeadsAlongLine(const Polyline& line, MFloat* x,  MFloat* y, unsigned long n) const;
    void renderLabelsAlongLine(const Polyline& line, MFloat* x,  MFloat* y, unsigned long n) const;

    mutable int currentPage_;
    mutable string fileName_;
    mutable string currentLayer_;  // from Layer (open & close)

    mutable LineStyle currentLineType_;
    mutable MFloat currentLineWidth_;
    mutable Colour currentColour_;

    mutable MFloat coordRatioX_;
    mutable MFloat coordRatioY_;
    mutable MFloat dimensionX_;
    mutable MFloat dimensionY_;
    mutable MFloat offsetX_;
    mutable MFloat offsetY_;

    mutable std::stack<MFloat> dimensionStack_;
    mutable std::stack<MFloat> scalesX_;
    mutable std::stack<MFloat> scalesY_;

    mutable bool newPage_;

    bool disabled_;
    bool alphaEnabled_;
    mutable int applyGaussianBlur_;
    mutable vSymbols sym_;

    virtual MAGICS_NO_EXPORT void setNewLineWidth(const MFloat w) const { currentLineWidth_ = w; }
    MAGICS_NO_EXPORT MFloat getNewLineWidth() const { return currentLineWidth_; }

    //! Load svg symbols from file
    void loadSymbols() const;

    virtual MAGICS_NO_EXPORT void renderTextSymbols(const TextSymbol& symbol) const;

    virtual void renderComplexSymbols(const ComplexSymbol& symbol) const;
    virtual void renderTextItem(const TextItem&, const ComplexSymbol&) const;
    virtual void renderFlagItem(const FlagItem&, const ComplexSymbol& symbol) const;
    virtual void renderSymbolItem(const SymbolItem&, const ComplexSymbol& symbol) const;
    virtual void renderSymbols(const Symbol& symbol) const;
    virtual void renderPolyline(const int, MFloat*, MFloat*) const {}
    virtual void renderPolyline2(const int, MFloat*, MFloat*) const {}
    void renderPolyline(vector<PaperPoint>& vP) const;
    void renderPolyline2(vector<PaperPoint>& vP) const;

#ifdef MAGICS_ON_WINDOWS
    virtual MAGICS_NO_EXPORT void renderSimplePolygon(const int, MFloat*, MFloat*) const {}
    virtual MAGICS_NO_EXPORT void renderSimplePolygon(const Polyline& line) const {}
#else
    virtual MAGICS_NO_EXPORT void renderSimplePolygon(const int, MFloat*, MFloat*) const = 0;
    virtual MAGICS_NO_EXPORT void renderSimplePolygon(const Polyline& line) const        = 0;
#endif
    virtual void circle(const MFloat, const MFloat, const MFloat, const int) const {}
    virtual MAGICS_NO_EXPORT void setLineParameters(const LineStyle st, const MFloat w) const {
        currentLineType_ = st;
        setNewLineWidth(w);
    }

    // Filling calculating methods
    // PolylineSets rendering methods
    mutable unsigned int indexHatch_;
    mutable Shading currentShading_;
    mutable const ShadingProperties* currentShadingProperties_;

    // wind calculating methods (overridden in Binary)
    virtual MAGICS_NO_EXPORT void renderWindArrow(const Arrow& arrow) const;
    virtual MAGICS_NO_EXPORT void renderWindFlag(const Flag& flag) const;

    // images + bitmap methods
    virtual void renderImage(const ImportObject& object) const;
    MAGICS_NO_EXPORT void renderImage(const Image& obj) const { renderCellArray(obj); }
    virtual MAGICS_NO_EXPORT bool convertToPixmap(const PixmapInput&) const;
    virtual MAGICS_NO_EXPORT bool renderPixmap(const Pixmap&) const;
    virtual MAGICS_NO_EXPORT bool renderCellArray(const Image&) const;

    mutable std::map<string, magFont, RuntimeStringCompare> FontMap_;
    typedef std::map<string, magFont, RuntimeStringCompare>::const_iterator fontMapIter;

    void readFonts() const;
    void relieveFonts() const { FontMap_.clear(); }

private:
    mutable MFloat cmScale_;
    MFloat xDeviceLength_;
    MFloat yDeviceLength_;
    static int numFiles_;
    mutable double obs_distance_;
    mutable stack<const Layout*> staLayouts_;
    mutable vector<const PaperPoint*> vecPoints_;
    bool checkDistanceMoreThan(const PaperPoint* pp, double distance) const;
    void renderSimplePolygon(vector<PaperPoint>& vP) const;

    // methods to draw symbols 
    void snowflake(const MFloat, const MFloat, const MFloat) const;
    void drizzle(const MFloat, const MFloat, const MFloat) const;
    void triangle(const MFloat, const MFloat, const MFloat, const int, const int) const;
    void lightning(const MFloat x, const MFloat y, const MFloat size) const;

    //! Copy constructor - No copy allowed
    BaseDriver(const BaseDriver&);
    //! Overloaded << operator to copy - No copy allowed
    BaseDriver& operator=(const BaseDriver&);

    // -- Friends
    //! Overloaded << operator to call print().
    friend ostream& operator<<(ostream& s, const BaseDriver& p) {
        p.print(s);
        return s;
    }
};

/*!
 \brief translates a string to a driver
*/
template <>
class MagTranslator<string, BaseDriver> {
public:
    MAGICS_NO_EXPORT BaseDriver* operator()(const string& val) { return SimpleObjectMaker<BaseDriver>::create(val); }
    MAGICS_NO_EXPORT BaseDriver* magics(const string& param) {
        BaseDriver* object;
        ParameterManager::update(param, object);
        return object;
    }
};

}  // namespace magics
#endif
