/*
 * (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 BaseDriverSymbols.h
    \brief Implementation of Symbol methods of driver base class.
    \author Meteorological Visualisation Section, ECMWF

    Started: Jan 2005

*/

using namespace magics;

#include <Symbol.h>
#include <expat.h>


/////////////////////////////////////////////////////
//
//  READING XML
//
/////////////////////////////////////////////////////

/*!
 callback for expat
*/
static void XMLCALL startElement(void* userData, const char* name, const char** atts) {
    vSymbols* s = (vSymbols*)userData;

    if (string(name) == "g") {
        svgSymbol sym;
        sym.id = *(atts + 1);
        s->push_back(sym);
        return;
    }
    if (s->empty())
        return;

    svgSymbol& sym = s->back();
    xmlElement ele;
    ele.name = name;
    while (*atts) {
        ele.attributes[*(atts)] = *(atts + 1);
        atts += 2;
    }
    sym.elements.push_back(ele);
}

/*!
 callback for expat
*/
static void XMLCALL endElement(void*, const char*) {}


MAGICS_NO_EXPORT bool BaseDriver::checkDistanceMoreThan(const PaperPoint* pp, double distance) const {
    for (vector<const PaperPoint*>::const_iterator iter = vecPoints_.begin(); iter != vecPoints_.end(); ++iter) {
        const double d = sqrt((pp->x() - (*iter)->x()) * (pp->x() - (*iter)->x()) +
                              (pp->y() - (*iter)->y()) * (pp->y() - (*iter)->y()));
        if (d < distance)
            return false;
    }
    return true;
}

MAGICS_NO_EXPORT void BaseDriver::loadSymbols() const {
    if (sym_.empty()) {
        string filename   = buildConfigPath("symbols.svg");
        XML_Parser parser = XML_ParserCreate(NULL);
        XML_SetUserData(parser, &sym_);
        XML_SetElementHandler(parser, startElement, endElement);

        FILE* in = fopen(filename.c_str(), "r");
        if (!in) {
            MagLog::error() << "BaseDriver::loadSymbols() -> Symbol file " << filename
                            << "could NOT been found! NO symbols will be plotted!" << endl;
            return;
        }

        int done = 0;
        char buf[BUFSIZ];
        do {
            size_t len = fread(buf, 1, sizeof(buf), in);
            done       = len < sizeof(buf);
            if (XML_Parse(parser, buf, len, done) == XML_STATUS_ERROR) {
                MagLog::error() << "BaseDriver::loadSymbols()-> XML ERROR: "
                                << XML_ErrorString(XML_GetErrorCode(parser)) << " at line  "
                                << XML_GetCurrentLineNumber(parser) << endl;
            }
        } while (!done);
        XML_ParserFree(parser);
        fclose(in);

    }  // endif of reading symbols.svg
}

MAGICS_NO_EXPORT void BaseDriver::renderTextSymbols(const TextSymbol& symbol) const {
    const int nPos  = symbol.size();
    const int nText = symbol.text().size();

    ASSERT(nText >= nPos);
    if (nText < nPos)
        MagLog::debug() << " BaseDriverSymbols > Not enough texts (" << nText << ") for " << nPos << " symbols!";

    // Loop for all positions
    for (int i = 0; i < nPos; i++) {
        if (!symbol.text().at(i).empty()) {
            Text text;
            text.setJustification(MCENTRE);
            text.setVerticalAlign(MHALF);
            text.setBlanking(symbol.blanking());

            MFloat shiftX = 0.;
            MFloat shiftY = 0.;

            // default is M_NONE
            if (symbol.position() == Symbol::M_NONE) {
                //			std::cout<<"M_NONE"<< std::endl;
            }
            else if (symbol.position() == Symbol::M_RIGHT) {
                shiftX = convertCM(symbol.getHeight() * .5) / coordRatioX_;
                text.setJustification(MLEFT);
            }
            else if (symbol.position() == Symbol::M_LEFT) {
                shiftX = -convertCM(symbol.getHeight() * .5) / coordRatioX_;
                text.setJustification(MRIGHT);
            }
            else if (symbol.position() == Symbol::M_BELOW) {
                shiftY = setY(-convertCM(symbol.getHeight() * .6) / coordRatioY_);
                text.setVerticalAlign(MTOP);
            }
            else if (symbol.position() == Symbol::M_ABOVE) {
                shiftY = setY(convertCM(symbol.getHeight() * 0.8) / coordRatioY_);
                text.setVerticalAlign(MBOTTOM);
            }

            // MagLog::dev()<<"x: " << symbol[i].x()<<"  y: "<<symbol[i].y()<<"+"<< shiftY<< endl;
            text.push_back(PaperPoint(symbol[i].x() + shiftX, symbol[i].y() + setFlagY(shiftY)));
            text.setAngle(0.);
            text.addText(symbol.text().at(i), symbol.font());  // 75 dpi
            renderText(text);
        }
    }
    renderSymbols(symbol);
}


MAGICS_NO_EXPORT void BaseDriver::renderComplexSymbols(const ComplexSymbol& symbol) const {
    obs_distance_ = convertCM(symbol.getDistanceApart()) / coordRatioX_;

    if ((obs_distance_ > 0.) && !checkDistanceMoreThan(&symbol[0], obs_distance_)) {
        return;
    }

    vecPoints_.push_back(&symbol[0]);
    for (const auto& item : symbol.items())
        item->redisplay(symbol, *this);
}

MAGICS_NO_EXPORT void BaseDriver::renderTextItem(const TextItem& textItem, const ComplexSymbol& owner) const {
    Text text;
    const MFloat scaling = convertCM(owner.getHeight() * 1.5);
    const MFloat pX      = 1. / coordRatioX_;
    const MFloat pY      = 1. / coordRatioY_;

    // cout <<">>>>>>>>>> "<<(*text.textBegin()).text()<<" -> "<<owner[0].x() <<" "<<owner[0].y()<< endl;
    const MFloat posX = owner[0].x() + (textItem.x() * scaling * pX);
    const MFloat posY = owner[0].y() + (setSymbolY(textItem.y()) * scaling * pY);
    text.push_back(PaperPoint(posX, posY));
    text.setJustification(textItem.justification());
    //
    //! \todo We need to decide if we need to align text in WMO symbols
    //
    //	if(owner[0].x() > 0) text.setJustification(MRIGHT);
    //	else if(owner[0].x() < 0) text.setJustification(MLEFT);

    text.setVerticalAlign(MHALF);
    text.setAngle(0.);
    text.addText(textItem.text(), textItem.font());
    renderText(text);
}

MAGICS_NO_EXPORT void BaseDriver::renderFlagItem(const FlagItem& flagItem, const ComplexSymbol& owner) const {
    Flag flag;
    flag.setThickness(flagItem.thickness());
    flag.setLength(flagItem.length());
    flag.setOriginMarker(flagItem.getOriginMarker());
    flag.setOriginHeight(flagItem.getOriginHeight());  // NOT flagItem.getOriginHeight() !!!
    flag.setColour(flagItem.getColour());

    flag.setHemisphere(flagItem.getHemisphere());

    const MFloat scaling = convertCM(owner.getHeight());
    const MFloat pX      = 1. / coordRatioX_;
    const MFloat pY      = 1. / coordRatioY_;

    const MFloat posX = owner[0].x() + (flagItem.x() * scaling * pX);
    const MFloat posY = owner[0].y() + (setSymbolY(flagItem.y()) * scaling * pY);
    ArrowPoint a(PaperPoint(posX, posY));
    a.set(flagItem.speed(), flagItem.direction());
    flag.push_back(a);

    renderWindFlag(flag);
}

MAGICS_NO_EXPORT void BaseDriver::renderSymbolItem(const SymbolItem& symbol, const ComplexSymbol& owner) const {
    setNewColour(symbol.colour());
    setLineParameters(M_SOLID, 1.);  // reset line
    loadSymbols();
    if (sym_.empty()) {
        MagLog::error() << "BaseDriver::renderSymbols(" << symbol.symbol() << ")-> NO symbols available!" << std::endl;
        return;
    }

    const int noSymbols = sym_.size();
    svgSymbol sym;
    int ii = 0;
    for (; ii < noSymbols; ii++) {
        sym = sym_[ii];
        if (sym.id == symbol.symbol())
            break;
    }
    if (ii == noSymbols) {
        sym = sym_[0];
        MagLog::warning() << "BaseDriver::renderSymbols(" << symbol.symbol()
                          << ")-> symbol NOT available! use question mark." << std::endl;
    }

    const unsigned int si = sym.elements.size();
    const MFloat pX       = 1. / coordRatioX_;
    const MFloat pY       = 1. / coordRatioY_;

    MFloat scaling = convertCM(symbol.height() * 1.5);  // convertCM scales them up!

    const MFloat posX = owner[0].x() + (symbol.x() * scaling * pX);
    const MFloat posY = owner[0].y() + (setSymbolY(symbol.y()) * scaling * pY);

    for (unsigned int i = 0; i < si; i++)  // for all elements in the symbol description
    {
        if (sym.elements[i].name == "circle") {
            const MFloat r  = atof(sym.elements[i].attributes["r"].c_str()) * scaling;
            const MFloat cx = atof(sym.elements[i].attributes["cx"].c_str()) * scaling * pX;
            const MFloat cy = setSymbolY(atof(sym.elements[i].attributes["cy"].c_str()) * scaling * pY);
            const int s     = atoi(sym.elements[i].attributes["fill"].c_str());
            circle(posX + cx, posY + cy, r, s);
        }
        else if (sym.elements[i].name == "snowflake") {
            const MFloat r  = atof(sym.elements[i].attributes["r"].c_str()) * scaling * pX;
            const MFloat cx = atof(sym.elements[i].attributes["cx"].c_str()) * scaling * pX;
            const MFloat cy = setSymbolY(atof(sym.elements[i].attributes["cy"].c_str()) * scaling * pY);
            snowflake(posX + cx, posY + cy, r);
        }
        else if (sym.elements[i].name == "drizzle") {
            const MFloat r  = atof(sym.elements[i].attributes["r"].c_str()) * scaling * pX;
            const MFloat cx = atof(sym.elements[i].attributes["cx"].c_str()) * scaling * pX;
            const MFloat cy = setSymbolY(atof(sym.elements[i].attributes["cy"].c_str()) * scaling * pY);
            drizzle(posX + cx, posY + cy, r);
        }
        else if (sym.elements[i].name == "triangle") {
            const MFloat r  = atof(sym.elements[i].attributes["r"].c_str()) * scaling * pX;
            const int s     = atoi(sym.elements[i].attributes["fill"].c_str());
            const MFloat cx = atof(sym.elements[i].attributes["cx"].c_str()) * scaling * pX;
            const MFloat cy = setSymbolY(atof(sym.elements[i].attributes["cy"].c_str()) * scaling * pY);
            const int li    = atoi(sym.elements[i].attributes["line"].c_str());
            triangle(posX + cx, posY + cy, r, s, li);
        }
        else if (sym.elements[i].name == "lightning") {
            const MFloat r  = atof(sym.elements[i].attributes["r"].c_str()) * scaling * pX;
            const MFloat cx = atof(sym.elements[i].attributes["cx"].c_str()) * scaling * pX;
            const MFloat cy = setSymbolY(atof(sym.elements[i].attributes["cy"].c_str()) * scaling * pY);
            lightning(posX + cx, posY + cy, r);
        }
        if (sym.elements[i].name == "polyline") {
            vector<PaperPoint> vPP;
            parsePoints(vPP, sym.elements[i].attributes["points"]);
            const int size = vPP.size();

            if (size > 0) {
                const MFloat sX = posX;
                const MFloat sY = posY;
                for (int s = 0; s < size; s++) {
                    vPP[s].x((vPP[s].x() * scaling * pX) + sX);
                    vPP[s].y(setSymbolY(setY((vPP[s].y()) * scaling * pY)) + sY);
                }
            }

            if (!magCompare("none", sym.elements[i].attributes["fill"]))
                renderSimplePolygon(vPP);
            else
                renderPolyline(vPP);
        }
    }  // endfor
}


/*!
  \brief Symbol render method for ALL drivers.

  This method should be used by all Magics++ drivers to render symbols.

*/
MAGICS_NO_EXPORT void BaseDriver::renderSymbols(const Symbol& symbol) const {
    debugOutput("Start Symbols");
    loadSymbols();

    // if still empty
    if (sym_.empty()) {
        MagLog::error() << "BaseDriver::renderSymbols(" << symbol.getSymbol() << ")-> NO symbols available!"
                        << std::endl;
        return;
    }

    const int noSymbols = sym_.size();
    svgSymbol sym;
    int ii = 0;
    for (; ii < noSymbols; ii++) {
        sym = sym_[ii];
        if (sym.id == symbol.getSymbol())
            break;
    }
    if (ii == noSymbols)
        sym = sym_[0];

    const unsigned int si = sym.elements.size();
    setNewColour(symbol.getColour());
    setLineParameters(M_SOLID, 1.);  // reset line

    const MFloat pX      = 1. / coordRatioX_;
    const MFloat pY      = 1. / coordRatioY_;
    const MFloat scaling = convertCM(symbol.getHeight() * .5);

    for (unsigned int i = 0; i < si; i++)  // for all elements in the symbol description
    {
        const long n = symbol.size();

        if (sym.elements[i].name == "circle") {
            const MFloat r  = atof(sym.elements[i].attributes["r"].c_str()) * scaling;
            const MFloat cx = atof(sym.elements[i].attributes["cx"].c_str()) * scaling * pX;
            const MFloat cy = setSymbolY(setY(atof(sym.elements[i].attributes["cy"].c_str()) * scaling * pY));
            const int s     = atoi(sym.elements[i].attributes["fill"].c_str());
            for (long l = 0; l < n; l++) {
                circle(symbol[l].x() + cx, symbol[l].y() + cy, r, s);
                if (s == 8 && symbol.outline()) {
                    setNewColour(symbol.outlineColour());
                    setLineParameters(symbol.outlineLineStyle(), symbol.outlineThickness());
                    circle(symbol[l].x() + cx, symbol[l].y() + cy, r, 0);
                    // We need to go back to the original values
                    setNewColour(symbol.getColour());
                    setLineParameters(M_SOLID, 1.);
                }
            }
        }
        else if (sym.elements[i].name == "snowflake") {
            const MFloat r  = atof(sym.elements[i].attributes["r"].c_str()) * scaling * pX;
            const MFloat cx = atof(sym.elements[i].attributes["cx"].c_str()) * scaling * pX;
            const MFloat cy = setSymbolY(atof(sym.elements[i].attributes["cy"].c_str()) * scaling * pY);
            for (long l = 0; l < n; l++)
                snowflake(symbol[l].x() + cx, symbol[l].y() + cy, r);
        }
        else if (sym.elements[i].name == "drizzle") {
            const MFloat r  = atof(sym.elements[i].attributes["r"].c_str()) * scaling * pX;
            const MFloat cx = atof(sym.elements[i].attributes["cx"].c_str()) * scaling * pX;
            const MFloat cy = setSymbolY(atof(sym.elements[i].attributes["cy"].c_str()) * scaling * pY);
            for (long l = 0; l < n; l++)
                drizzle(symbol[l].x() + cx, symbol[l].y() + cy, r);
        }
        else if (sym.elements[i].name == "triangle") {
            const MFloat r  = atof(sym.elements[i].attributes["r"].c_str()) * scaling * pX;
            const int s     = atoi(sym.elements[i].attributes["fill"].c_str());
            const MFloat cx = atof(sym.elements[i].attributes["cx"].c_str()) * scaling * pX;
            const MFloat cy = setSymbolY(atof(sym.elements[i].attributes["cy"].c_str()) * scaling * pY);
            const int li    = atoi(sym.elements[i].attributes["line"].c_str());

            for (long l = 0; l < n; l++) {
                triangle(symbol[l].x() + cx, symbol[l].y() + cy, r, s, li);
                if (s == 1 && symbol.outline()) {
                    setNewColour(symbol.outlineColour());
                    setLineParameters(symbol.outlineLineStyle(), symbol.outlineThickness());
                    triangle(symbol[l].x() + cx, symbol[l].y() + cy, r, 0, li);
                    // We need to go back to the original values
                    setNewColour(symbol.getColour());
                    setLineParameters(M_SOLID, 1.);
                }
            }
        }
        else if (sym.elements[i].name == "lightning") {
            const MFloat r  = atof(sym.elements[i].attributes["r"].c_str()) * scaling * pX;
            const MFloat cx = atof(sym.elements[i].attributes["cx"].c_str()) * scaling * pX;
            const MFloat cy = setSymbolY(atof(sym.elements[i].attributes["cy"].c_str()) * scaling * pY);
            for (long l = 0; l < n; l++)
                lightning(symbol[l].x() + cx, symbol[l].y() + cy, r);
        }
        if (sym.elements[i].name == "polyline") {
            for (long l = 0; l < n; l++) {
                vector<PaperPoint> vPP;
                parsePoints(vPP, sym.elements[i].attributes["points"]);
                const int size = vPP.size();

                if (size > 0) {
                    const MFloat sX = symbol[l].x();
                    const MFloat sY = symbol[l].y();
                    for (int s = 0; s < size; s++) {
                        vPP[s].x((vPP[s].x() * scaling * pX) + sX);
                        vPP[s].y(setSymbolY(setY((vPP[s].y()) * scaling * pY)) + sY);
                    }

                    if (!magCompare("none", sym.elements[i].attributes["fill"]))
                        renderSimplePolygon(vPP);
                    else
                        renderPolyline(vPP);
                    if (!magCompare("none", sym.elements[i].attributes["fill"]) && symbol.outline()) {
                        setNewColour(symbol.outlineColour());
                        setLineParameters(symbol.outlineLineStyle(), symbol.outlineThickness());
                        renderPolyline(vPP);
                        // We need to go back to the original values
                        setNewColour(symbol.getColour());
                        setLineParameters(M_SOLID, 1.);
                    }
                }
            }
        }
    }  // endfor
}  // end BaseDriver::renderSymbols()

/*!
  Plotting a snowflake
*/
MAGICS_NO_EXPORT void BaseDriver::snowflake(const MFloat x, const MFloat y, const MFloat size) const {
    const MFloat s5 = size * 1.2;
    const MFloat s3 = size * 0.75;

    vector<PaperPoint> line;
    line.push_back(PaperPoint(x - size, y));
    line.push_back(PaperPoint(x + size, y));
    renderPolyline2(line);
    line.clear();
    line.push_back(PaperPoint(x - s3, y + s5));
    line.push_back(PaperPoint(x + s3, y - s5));
    renderPolyline2(line);
    line.clear();
    line.push_back(PaperPoint(x - s3, y - s5));
    line.push_back(PaperPoint(x + s3, y + s5));
    renderPolyline2(line);
}

/*!
  Plotting drizzle
*/
MAGICS_NO_EXPORT void BaseDriver::drizzle(const MFloat x, const MFloat y, const MFloat size) const {
    const MFloat s5  = size * .6;
    const MFloat s25 = size * .3;

    vector<PaperPoint> line;
    line.push_back(PaperPoint(x + s25, y - s5));
    line.push_back(PaperPoint(x - s25, y - s5));
    line.push_back(PaperPoint(x - s5, y - s25));
    line.push_back(PaperPoint(x - s5, y + s25));
    line.push_back(PaperPoint(x - s25, y + s5));
    line.push_back(PaperPoint(x + s25, y + s5));
    line.push_back(PaperPoint(x + s5, y + s25));
    line.push_back(PaperPoint(x + s5, y - s25));
    line.push_back(PaperPoint(x + (size * .58), y - (size * .85)));
    line.push_back(PaperPoint(x + (size * .45), y - (size * 1.1)));
    line.push_back(PaperPoint(x + (size * .1), y - (size * 1.4)));
    line.push_back(PaperPoint(x + s25, y - (size * .7)));

    renderSimplePolygon(line);
    /*	circle(x,y,s2 * coordRatioX_,8);

        vector<PaperPoint> line;
          line.push_back(PaperPoint(x+ s2      ,y));
          line.push_back(PaperPoint(x+ s2      ,y-(s2*1.75)));
          line.push_back(PaperPoint(x          ,y-(s2*1.95)));
          line.push_back(PaperPoint(x          ,y-(s2*1.77)));
          line.push_back(PaperPoint(x+(s2*0.4) ,y-(s2*1.25)));
          line.push_back(PaperPoint(x+(s2*0.4) ,y-s2));
        renderSimplePolygon(line);
    */
}

/*!
  Plotting lightning symbol
*/
MAGICS_NO_EXPORT void BaseDriver::lightning(const MFloat x, const MFloat y, const MFloat size) const {
    vector<PaperPoint> line;
    line.push_back(PaperPoint(x - (size * .3), y + (size)));
    line.push_back(PaperPoint(x - (size * .3), y - (size)));
    renderPolyline(line);
    line.clear();
    line.push_back(PaperPoint(x - (size * .3), y + (size)));
    line.push_back(PaperPoint(x + (size * .3), y + (size)));
    line.push_back(PaperPoint(x, y));
    line.push_back(PaperPoint(x + (size * .3), y - (size)));
    renderPolyline(line);
    line.clear();
    line.push_back(PaperPoint(x + (size * .35), y - (size * .3)));
    line.push_back(PaperPoint(x + (size * .3), y - (size)));
    line.push_back(PaperPoint(x, y - (size * .7)));
    renderPolyline(line);
}

/*!
  Plotting a triangle
*/
MAGICS_NO_EXPORT void BaseDriver::triangle(const MFloat x, const MFloat y, const MFloat size, const int fill,
                                           const int l) const {
    const MFloat s = 0.5 * size;

    vector<PaperPoint> line;
    line.push_back(PaperPoint(x + s, y - s));
    line.push_back(PaperPoint(x - s, y - s));
    line.push_back(PaperPoint(x, y + size));
    line.push_back(PaperPoint(x + s, y - s));
    if (fill < 1) {
        renderPolyline(line);
        if (l > 0) {
            line.clear();
            line.push_back(PaperPoint(x + s * .6, y - (s * .5)));
            line.push_back(PaperPoint(x - s * .6, y - (s * .5)));
            renderPolyline(line);
        }
    }
    else
        renderSimplePolygon(line);
}
