/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "MvQFeatureFrontItem.h"

#include <sstream>

#include <QDebug>
#include <QMessageBox>
#include <QPainter>

#include "MvQFeatureFactory.h"
#include "MvQMethods.h"
#include "ObjectList.h"

static const std::string FCI_AT_THE_SURFACE = "SURFACE";
static const std::string FCI_ABOVE_THE_SURFACE = "UPPER";
static const std::string FCI_FRONTOGENESIS = "FRONTOGENESIS";
static const std::string FCI_FRONTOLYSIS = "FRONTOLYSIS";

//========================================
//
// MvQFeatureMetCurveItem
//
//========================================

MvQFeatureMetCurveItem::MvQFeatureMetCurveItem(WsType* feature) :
    MvQFeatureCurveItem(feature, false)
{
}

double MvQFeatureMetCurveItem::halo() const
{
    return std::max(symbolSize_.width(), symbolSize_.height());
}

bool MvQFeatureMetCurveItem::getRequestParameters()
{
    if (!req_)
        return false;

    expandStyleRequest();

    // Get input parameters
    std::string val;
    req_.getValue("TYPE", front_type_, true);
    type_normal_ = (front_type_ == FCI_AT_THE_SURFACE ||
                    front_type_ == FCI_ABOVE_THE_SURFACE);

    // Set line parameters
    linePen_ = MvQ::makePen("SOLID",
                            (int)req_("LINE_THICKNESS"),
                            (const char*)req_("COLOUR"));

    // Set symbol parameters
    symbolGap_ = (int)req_("SYMBOL_GAP");
    symbolWidth_ = (int)req_("SYMBOL_WIDTH");

    symbolFacesLeft_ = (req_.getValue("SYMBOL_ORIENTATION", val, true) && val == "LEFT");

    symbolBrush_ = MvQ::makeBrush("SOLID", (const char*)req_("COLOUR"));

    return true;
}

bool MvQFeatureMetCurveItem::getSimpleRequestParameters()
{
    if (!req_)
        return false;

    // Expand defaults
    req_ = ObjectList::ExpandRequest(req_, EXPAND_DEFAULTS);

    std::string val;

    // Get input parameters
    // Set line parameters
    linePen_ = MvQ::makePen("SOLID",
                            (int)req_("LINE_THICKNESS"),
                            (const char*)req_("COLOUR"));

    return true;
}

void MvQFeatureMetCurveItem::flipSymbolDirection()
{
    symbolFacesLeft_ = !symbolFacesLeft_;
    req_("SYMBOL_ORIENTATION") = symbolFacesLeft_ ? "LEFT" : "RIGHT";
    update();
}

void MvQFeatureMetCurveItem::setFrontSubType(const std::string& subType)
{
    if (subType == "surface") {
        front_type_ = FCI_AT_THE_SURFACE;
        req_("TYPE") = (const char*)FCI_AT_THE_SURFACE.c_str();
        type_normal_ = true;
    }
    else if (subType == "upper") {
        front_type_ = FCI_ABOVE_THE_SURFACE;
        req_("TYPE") = (const char*)FCI_ABOVE_THE_SURFACE.c_str();
        type_normal_ = true;
    }
    else if (subType == "frontogenesis") {
        front_type_ = FCI_FRONTOGENESIS;
        req_("TYPE") = (const char*)FCI_FRONTOGENESIS.c_str();
        type_normal_ = false;
        makeCircle(symbolWidth_ / 4., 0., 8., symbolSep_);
    }
    else if (subType == "frontolysis") {
        front_type_ = FCI_FRONTOLYSIS;
        req_("TYPE") = (const char*)FCI_FRONTOLYSIS.c_str();
        type_normal_ = false;
        makeCross(symbolWidth_ / 4., symbolSep_);
    }
    else {
        Q_ASSERT(false);
    }
}

#if 0
void MvQFeatureMetCurveItem::surfaceType()
{
    front_type_ = FCI_AT_THE_SURFACE;
    req_("TYPE") = (const char*)FCI_AT_THE_SURFACE.c_str();
    type_normal_ = true;

    update();
}

void MvQFeatureMetCurveItem::upperType()
{
    front_type_ = FCI_ABOVE_THE_SURFACE;
    req_("TYPE") = (const char*)FCI_ABOVE_THE_SURFACE.c_str();
    type_normal_ = true;

    update();
}

void MvQFeatureMetCurveItem::frontogenesisType()
{
    front_type_ = FCI_FRONTOGENESIS;
    req_("TYPE") = (const char*)FCI_FRONTOGENESIS.c_str();
    type_normal_ = false;

    makeCircle(symbolWidth_/4., 0., 8., symbolSep_);

    update();
}
void MvQFeatureMetCurveItem::frontolysisType()
{
    front_type_ = FCI_FRONTOLYSIS;
    req_("TYPE") = (const char*)FCI_FRONTOLYSIS.c_str();
    type_normal_ = false;

    makeCross(symbolWidth_/4., symbolSep_);

    update();
}
#endif

void MvQFeatureMetCurveItem::buildShape()
{
    auto d = symbolWidth_ + 4;
    buildShapeCore(curveX_, curveY_, d, d);
}

void MvQFeatureMetCurveItem::makeTriangle(const QPointF& p1, const QPointF& p2,
                                          float symbolWidth, bool build_base,
                                          QVector<QPointF>& symb)
{
    // Orthogonal slope
    float offset = 0.01;  // to avoid division by 0
    float slope = (p2.y() - p1.y() + offset) / (p2.x() - p1.x() + offset);
    float slope1 = -1 / slope;

    // Flag to rotate symbol
    float an = atan2(p2.y() - p1.y() + offset, p2.x() - p1.x() + offset);
    float dirFactor = (symbolFacesLeft_) ? 1.0 : -1.0;
    float signal = (an >= 0) ? -dirFactor : dirFactor;

    // Line equation: Y = slope*X + b
    QPointF mpoint = (p1 + p2) / 2.;  // middle point
    float x = mpoint.x() + (signal * sqrt(pow(symbolWidth, 2) /
                                          (1 + pow(slope1, 2))));
    float y = slope1 * (x - mpoint.x()) + mpoint.y();

    // Build symbol
    symb.clear();
    symb << p1;
    symb << QPointF(x, y);
    symb << p2;
    if (build_base) {
        for (int i = endIndex_; i > startIndex_; i--)
            symb << QPointF(curveX_[i], curveY_[i]);
    }

    return;
}

// Build semi-circle with default coordinates
QSizeF MvQFeatureMetCurveItem::makeSemiCircle(float diameter, float offsetX,
                                              float res, QVector<QPointF>& symb)
{
    double radius = diameter / 2.;
    double rs = radius * radius;
    symb.clear();
    for (double x = -radius; x <= radius; x += diameter / res)
        symb << QPointF(x + offsetX, sqrt(abs(rs - (x * x))));

    return {diameter, radius};
}

QSizeF MvQFeatureMetCurveItem::makeCircle(float diameter, float offsetX, float res,
                                          QVector<QPointF>& symb)
{
    float radius = diameter / 2.;
    float rs = radius * radius;
    symb.clear();
    for (double x = -radius; x <= radius; x += diameter / res)
        symb << QPointF(x + offsetX, sqrt(abs(rs - (x * x))));

    for (double x = radius - (diameter / res); x >= -radius; x -= diameter / res)
        symb << QPointF(x + offsetX, -sqrt(abs(rs - (x * x))));

    return {diameter, diameter};
}

void MvQFeatureMetCurveItem::makeHalfCircle(const QPointF& p1,
                                            const QPointF& p2,
                                            bool build_base,
                                            QVector<QPointF>& symb)
{
    // Get initial semi-circle with default coordinates
    symb.clear();
    makeSemiCircle(symbolWidth_, 0., 8., symb);

    // Rotate symbol around first point
    rotateSymbol(p1, p2, symbolFacesLeft_, symb);

    // Translate symbol to middle point
    QPointF mpoint = (p1 + p2) / 2.;
    translateSymbol(mpoint, symb);

    // Add points along the spline curve
    if (build_base) {
        for (int i = endIndex_; i > startIndex_; i--)
            symb << QPointF(curveX_[i], curveY_[i]);
    }
}

QSizeF MvQFeatureMetCurveItem::makeCross(float size, QVector<QPointF>& symb)
{
    symb.clear();
    symb << QPointF(-size, 0);
    symb << QPointF(size, 0);
    symb << QPointF(-1, size);
    symb << QPointF(1, -size);

    return {size, size};
}

void MvQFeatureMetCurveItem::paintSeparator(QPainter* painter,
                                            QPen& pen, QBrush& brush,
                                            const QPointF& p1, const QPointF& p2)
{
    // draw separator between symbols
    if (front_type_ == FCI_FRONTOGENESIS)
        paintSeparatorPolygon(painter, brush, p1, p2);
    else
        paintSeparatorLine(painter, pen, p1, p2);
}

void MvQFeatureMetCurveItem::paintSeparatorPolygon(
    QPainter* painter, QBrush& brush,
    const QPointF& p1, const QPointF& p2)
{
    // Draw polygon separator between symbols
    // Translate symbol to middle point
    QVector<QPointF> pt = symbolSep_;
    translateSymbol(p1, p2, pt);

    painter->setPen(Qt::NoPen);
    painter->setBrush(brush);
    painter->drawPolygon(QPolygonF(pt));
}

void MvQFeatureMetCurveItem::paintSeparatorPolygon2(
    QPainter* painter, QBrush& brush,
    const QPointF& p1, const QPointF& p2)
{
    // Translate symbols to middle point
    QVector<QPointF> pt1 = symbolSep_;
    QVector<QPointF> pt2 = symbolSep_;
    translateSymbol(p1, p2, pt1, pt2);

    painter->setBrush(brush);
    painter->drawPolygon(QPolygonF(pt1));
    painter->drawPolygon(QPolygonF(pt2));
}

void MvQFeatureMetCurveItem::paintSeparatorLine(
    QPainter* painter, QPen& pen,
    const QPointF& p1, const QPointF& p2)
{
    // Rotate symbol around first point
    QVector<QPointF> pt = symbolSep_;
    rotateSymbol(p1, p2, symbolFacesLeft_, pt);

    // Translate symbol to middle point
    translateSymbol(p1, p2, pt);

    painter->setPen(pen);
    MvQ::safeDrawLine(pt[0], pt[1], painter);
    MvQ::safeDrawLine(pt[2], pt[3], painter);
}

// Draw a line
// Given a spline curve (vector<double> splineX_, std::vector<double> splineY_) and
// references indexes (startIndex,endIndex), draw segments of line between
// points (p1,p2).
// Usually, points (p1,p2) are interpolated values along the spline curve.
// Requirement: Indexes/points must follow the following order along the curve:
// a) startIndex -> p1 -> startIndex+1 -> ... -> endIndex -> p2
// b) startIndex -> p1 -> p2 -> startIndex+1 -> endIndex
void MvQFeatureMetCurveItem::paintLineSegment(
    QPainter* painter,
    const int startIndex, const int endIndex,
    const QPointF& p1, const QPointF& p2)
{
    // Line segment within the current spline segment
    QPointF p11(curveX_[startIndex + 1], curveY_[startIndex + 1]);
    float arclen1 = sqrt(pow(curveX_[startIndex + 1] - p1.x(), 2) +
                         pow(curveY_[startIndex + 1] - p1.y(), 2));
    float arclen2 = sqrt(pow(p2.x() - p1.x(), 2) + pow(p2.y() - p1.y(), 2));
    if (arclen2 <= arclen1) {
        MvQ::safeDrawLine(p1, p2, painter);
        return;
    }

    // First segment
    MvQ::safeDrawLine(p1, p11, painter);

    Q_ASSERT(startIndex >= -1);
    // Remaining segments except the last one
    for (int i = startIndex + 1; i < endIndex; i++) {
        QPointF p11(curveX_[i], curveY_[i]);
        QPointF p22(curveX_[i + 1], curveY_[i + 1]);
        MvQ::safeDrawLine(p11, p22, painter);
    }

    // Last segment
    QPointF p22(curveX_[endIndex], curveY_[endIndex]);
    MvQ::safeDrawLine(p22, p2, painter);
}

void MvQFeatureMetCurveItem::rotateSymbol(const QPointF& p1, const QPointF& p2,
                                          bool face, QVector<QPointF>& symb)
{
    // get the curve gradient at the middle point
    float an = atan2(p2.y() - p1.y(), p2.x() - p1.x());

    // rotate symbol around first point
    float dirFactor = (face) ? 1.0 : -1.0;
    for (auto& i : symb) {
        float xp = i.x();
        float yp = i.y();
        i.setX(cos(an) * xp - dirFactor * sin(an) * yp);
        i.setY(sin(an) * xp + dirFactor * cos(an) * yp);
    }
}

void MvQFeatureMetCurveItem::translateSymbol(const QPointF& point,
                                             QVector<QPointF>& symb)
{
    for (int i = 0; i < symb.count(); i++)
        symb[i] = point + symb[i];
}

void MvQFeatureMetCurveItem::translateSymbol(const QPointF& p1, const QPointF& p2,
                                             QVector<QPointF>& symb)
{
    QPointF point = (p1 + p2) / 2.;

    for (int i = 0; i < symb.count(); i++)
        symb[i] = point + symb[i];
}

void MvQFeatureMetCurveItem::translateSymbol(const QPointF& p1, const QPointF& p2,
                                             QVector<QPointF>& symb1,
                                             QVector<QPointF>& symb2)
{
    // Interval point
    QPointF pint = (p2 - p1) / 3.;

    // First symbol
    QPointF point = p1 + pint;
    for (int i = 0; i < symb1.count(); i++)
        symb1[i] = point + symb1[i];

    // Second symbol
    point = p1 + 2 * pint;
    for (int i = 0; i < symb2.count(); i++)
        symb2[i] = point + symb2[i];
}

void MvQFeatureMetCurveItem::linearInterpolation(int i, float dist, QPointF& point)
{
    Q_ASSERT(static_cast<int>(curveX_.size()) > i + 1);

    point.setX(curveX_[i] + (curveX_[i + 1] - curveX_[i]) * dist);
    point.setY(curveY_[i] + (curveY_[i + 1] - curveY_[i]) * dist);
}

void MvQFeatureMetCurveItem::init_line_segment()
{
    ds_ = 0.;
    ind_ = 0;
}

bool MvQFeatureMetCurveItem::compute_next_line_segment(QPointF& p1, QPointF& p2,
                                                       float symbolWidth,
                                                       float symbolGap)
{
    float arclen, dist;
    startIndex_ = -1;
    endIndex_ = -1;

    while (ind_ < (static_cast<int>(curveX_.size()) - 1)) {
        // get the arc length
        arclen = sqrt(pow(curveX_[ind_ + 1] - curveX_[ind_], 2) +
                      pow(curveY_[ind_ + 1] - curveY_[ind_], 2));
        ds_ += arclen;

        if (startIndex_ == -1) {  //&& endIndex == -1) {
            // see if a symbol segment should start
            if (ds_ >= symbolGap) {
                dist = 1. - ((ds_ - symbolGap) / arclen);
                linearInterpolation(ind_, dist, p1);
                startIndex_ = ind_;
                ds_ = ds_ - symbolGap;
            }
        }

        if (startIndex_ != -1) {
            // see if reached the end of a symbol segment
            if (ds_ >= symbolWidth) {
                dist = 1. - ((ds_ - symbolWidth) / arclen);
                linearInterpolation(ind_, dist, p2);
                endIndex_ = ind_;
                ds_ = ds_ - symbolWidth;
            }
        }

        ind_ += 1;

        if (startIndex_ != -1 && endIndex_ != -1)
            return true;
    }

    return false;
}

/*
void MvQFeatureMetCurveItem::getPointsAlongCurve(QVector<QPointF>& in,
                                              float gap,
                                              QVector<QPointF>& out)
{
    QPointF pp;
    size_t ind = 0;
    float arclen;
    float dist = 0.;
    float ds = 0.;
    while (ind < (in.size()-1)) {
        // get the arc length
        arclen = sqrt(pow(in[ind+1].x() - in[ind].x(), 2) +
                      pow(in[ind+1].y() - in[ind].y(), 2));
        ds += arclen;

        // see if a point should be taken symbol
        if (ds >= gap) {
            // linear interpolation
            dist = 1. - ((ds - gap) / arclen);
            pp.setX(in[ind].x() + (in[ind+1].x() - in[ind].x()) * dist);
            pp.setY(in[ind].y() + (in[ind+1].y() - in[ind].y()) * dist);
            out << pp;
            ds = ds - gap;
        }

        ind++;
    }
}
*/

void MvQFeatureMetCurveItem::paint_normal(QPainter* painter)
{
    generateCurvePath();

    // Draw line
    painter->setPen(linePen_);
    // MvQ::drawPolyline(curveX_, curveY_, painter);
    painter->drawPath(curvePath_);

    // Set paint parameters
    if (front_type_ == FCI_ABOVE_THE_SURFACE)
        painter->setBrush(Qt::NoBrush);
    else
        painter->setBrush(symbolBrush_);

    // Draw symbols along the curve
    QPointF p1, p2;
    QVector<QPointF> symb;
    init_line_segment();
    while (compute_next_line_segment(p1, p2, symbolWidth_, symbolGap_)) {
        // create symbol
        makeSymbol(p1, p2, symb);

        // draw symbol
        painter->drawPolygon(QPolygonF(symb));
    }
}

void MvQFeatureMetCurveItem::paint_fronto(QPainter* painter)
{
    // Draw curves, symbols and separators
    bool first_symbol = true;
    int iStart1 = 0, iEnd1 = 0;
    float segSize = 1.5;  // small segment before semi-circle and after triangle
    QPointF p1, p2, pStart1, pEnd1, pStart2, pEnd2;
    QPointF pSep1, pSep2;
    QVector<QPointF> symb;

    // Main loop
    init_line_segment();
    while (true) {
        // Separators will be drawn only after first symbol
        if (!first_symbol) {
            if (!compute_next_line_segment(pSep1, pSep2, symbolGap_, 0.))
                return;
        }

        // Create a starting space
        if (!compute_next_line_segment(pStart1, pEnd1, segSize, 0.))
            return;

        iStart1 = startIndex_;
        iEnd1 = endIndex_;

        // Create symbol
        if (!compute_next_line_segment(p1, p2, symbolWidth_, 0.))
            return;

        makeSymbol(p1, p2, symb);
        pEnd1 = p1;  // adjust point

        // Create an ending space
        if (!compute_next_line_segment(pStart2, pEnd2, segSize, 0.))
            return;

        pStart2 = p2;  // adjust point

        // Draw separator between symbols, only for the second symbol onwards
        if (first_symbol)
            first_symbol = false;
        else
            paintSeparator(painter, linePen_, symbolBrush_, pSep1, pSep2);

        // Draw curves, semi-circle and triangle
        painter->setPen(linePen_);
        painter->setBrush(symbolBrush_);
        paintLineSegment(painter, iStart1, iEnd1, pStart1, pEnd1);
        painter->drawPolygon(QPolygonF(symb));
        paintLineSegment(painter, startIndex_, endIndex_, pStart2, pEnd2);
    }
}


//========================================
//
// MvQFeatureColdFrontItem
//
//========================================

void MvQFeatureColdFrontItem::updateSymbols()
{
    symbolSize_ = QSizeF(symbolWidth_, symbolWidth_ * 1.71 / 2.);

    if (front_type_ == FCI_FRONTOGENESIS)
        makeCircle(symbolWidth_ / 4., 0., 8., symbolSep_);
    else if (front_type_ == FCI_FRONTOLYSIS)
        makeCross(symbolWidth_ / 4., symbolSep_);
}

void MvQFeatureColdFrontItem::makeSymbol(const QPointF& p1,
                                         const QPointF& p2,
                                         QVector<QPointF>& symb)
{
    makeTriangle(p1, p2, symbolWidth_, true, symb);
}

void MvQFeatureColdFrontItem::paint(QPainter* painter,
                                    const QStyleOptionGraphicsItem*, QWidget*)
{
    if (points_.size() < 2)
        return;

    painter->save();
    painter->setRenderHint(QPainter::Antialiasing, true);

    // clip to map boundaries
    setClipping(painter);

    if (type_normal_)
        paint_normal(painter);
    else
        paint_fronto(painter);

    painter->restore();
}


//========================================
//
// MvQFeatureWarmFrontItem
//
//========================================

void MvQFeatureWarmFrontItem::updateSymbols()
{
    symbolSize_ = makeSemiCircle(symbolWidth_, 0., 8., symb_);

    if (front_type_ == FCI_FRONTOGENESIS)
        makeCircle(symbolWidth_ / 4., 0., 8., symbolSep_);
    else if (front_type_ == FCI_FRONTOLYSIS)
        makeCross(symbolWidth_ / 4., symbolSep_);
}

void MvQFeatureWarmFrontItem::makeSymbol(const QPointF& p1,
                                         const QPointF& p2,
                                         QVector<QPointF>& symb)
{
    makeHalfCircle(p1, p2, true, symb);
}

void MvQFeatureWarmFrontItem::paint(QPainter* painter,
                                    const QStyleOptionGraphicsItem*, QWidget*)
{
    if (points_.size() < 2)
        return;

    painter->save();
    painter->setRenderHint(QPainter::Antialiasing, true);

    // clip to map boundaries
    setClipping(painter);

    if (type_normal_)
        paint_normal(painter);
    else
        paint_fronto(painter);

    painter->restore();
}

//========================================
//
// MvQFeatureOccludedItem
//
//========================================

void MvQFeatureOccludedItem::updateSymbols()
{
    symbolSize_ = QSizeF(2 * symbolWidth_, symbolWidth_ * 1.71 / 2.);

    if (front_type_ == FCI_FRONTOGENESIS)
        makeCircle(symbolWidth_ / 4., 0., 8., symbolSep_);
    else if (front_type_ == FCI_FRONTOLYSIS)
        makeCross(symbolWidth_ / 4., symbolSep_);
}

void MvQFeatureOccludedItem::paint(QPainter* painter,
                                   const QStyleOptionGraphicsItem*, QWidget*)
{
    if (points_.size() < 2)
        return;

    painter->save();
    painter->setRenderHint(QPainter::Antialiasing, true);

    // clip to map boundaries
    setClipping(painter);

    if (type_normal_)
        paint_normal(painter);
    else
        paint_fronto(painter);

    painter->restore();
}

void MvQFeatureOccludedItem::paint_normal(QPainter* painter)
{
    generateCurvePath();

    // Draw line
    painter->setPen(linePen_);
    painter->drawPath(curvePath_);

    // Set paint parameters
    if (front_type_ == FCI_ABOVE_THE_SURFACE)
        painter->setBrush(Qt::NoBrush);
    else
        painter->setBrush(symbolBrush_);

    // Draw symbols along the curve
    QPointF p1, p2;
    QVector<QPointF> hs, tr;
    init_line_segment();
    while (true) {
        // create space between symbols
        if (!compute_next_line_segment(p1, p2, symbolGap_, 0.))
            return;

        // create half-circle
        if (!compute_next_line_segment(p1, p2, symbolWidth_, 0.))
            return;

        makeHalfCircle(p1, p2, true, hs);
        QPointF p22 = p2;  // to adjust the first point of the triangle

        // create triangle
        if (!compute_next_line_segment(p1, p2, symbolWidth_, 0.))
            return;

        p1 = p22;  // adjust point
        makeTriangle(p1, p2, symbolWidth_, true, tr);

        // draw symbols
        painter->drawPolygon(QPolygonF(hs));
        painter->drawPolygon(QPolygonF(tr));
    }
}

void MvQFeatureOccludedItem::paint_fronto(QPainter* painter)
{
    // Draw curves, symbols and separators
    bool first_symbol = true;
    int iStart1 = 0, iEnd1 = 0;
    float segSize = 1.5;  // small segment before semi-circle and after triangle
    QPointF p1, p2, pStart1, pEnd1, pStart2, pEnd2;
    QPointF pSep1, pSep2;
    QVector<QPointF> hs, tr;
    init_line_segment();
    while (true) {
        // Separators will be drawn only after first symbol
        if (!first_symbol) {
            if (!compute_next_line_segment(pSep1, pSep2, symbolGap_, 0.))
                return;
        }

        // Create a starting space
        if (!compute_next_line_segment(pStart1, pEnd1, segSize, 0.))
            return;

        iStart1 = startIndex_;
        iEnd1 = endIndex_;

        // Create half-circle
        if (!compute_next_line_segment(p1, p2, symbolWidth_, 0.))
            return;

        makeHalfCircle(p1, p2, true, hs);
        pEnd1 = p1;        // adjust point
        QPointF p22 = p2;  // to adjust the first point of the triangle

        // Create triangle
        if (!compute_next_line_segment(p1, p2, symbolWidth_, 0.))
            return;

        p1 = p22;  // adjust point
        makeTriangle(p1, p2, symbolWidth_, true, tr);

        // Create an ending space
        if (!compute_next_line_segment(pStart2, pEnd2, segSize, 0.))
            return;

        pStart2 = p2;  // adjust point

        // Draw separator between symbols, only for the second symbol onwards
        if (first_symbol)
            first_symbol = false;
        else
            paintSeparator(painter, linePen_, symbolBrush_, pSep1, pSep2);

        // Draw curves, semi-circle and triangle
        painter->setPen(linePen_);
        painter->setBrush(symbolBrush_);
        paintLineSegment(painter, iStart1, iEnd1, pStart1, pEnd1);
        painter->drawPolygon(QPolygonF(hs));
        painter->drawPolygon(QPolygonF(tr));
        paintLineSegment(painter, startIndex_, endIndex_, pStart2, pEnd2);
    }
}

//========================================
//
// MvQFeatureQuasiStationaryItem
//
//========================================

bool MvQFeatureQuasiStationaryItem::getRequestParameters()
{
    if (!req_)
        return false;

    expandStyleRequest();

    std::string val;

    // Get input parameters
    req_.getValue("TYPE", front_type_, true);
    type_normal_ = (front_type_ == FCI_AT_THE_SURFACE ||
                    front_type_ == FCI_ABOVE_THE_SURFACE);

    // Set line parameters
    linePenTr_ = MvQ::makePen("SOLID",
                              (int)req_("LINE_THICKNESS"),
                              (const char*)req_("COLD_COLOUR"));

    linePenSc_ = MvQ::makePen("SOLID",
                              (int)req_("LINE_THICKNESS"),
                              (const char*)req_("WARM_COLOUR"));

    // Set symbol parameters
    symbolGap_ = (int)req_("SYMBOL_GAP");
    symbolWidth_ = (int)req_("SYMBOL_WIDTH");
    symbolFacesLeft_ = (req_.getValue("SYMBOL_ORIENTATION", val, true) && val == "LEFT");

    symbolBrushTr_ = MvQ::makeBrush("SOLID", (const char*)req_("COLD_COLOUR"));
    symbolBrushSc_ = MvQ::makeBrush("SOLID", (const char*)req_("WARM_COLOUR"));

    return true;
}

void MvQFeatureQuasiStationaryItem::updateSymbols()
{
    symbolSize_ = QSizeF(symbolWidth_, symbolWidth_ * 1.71 / 2.);

    if (front_type_ == FCI_FRONTOGENESIS)
        makeCircle(symbolWidth_ / 3., 0., 8., symbolSep_);
    else if (front_type_ == FCI_FRONTOLYSIS)
        makeCross(symbolWidth_ / 4., symbolSep_);
}

void MvQFeatureQuasiStationaryItem::paint(QPainter* painter,
                                          const QStyleOptionGraphicsItem*, QWidget*)
{
    if (points_.size() < 2)
        return;

    painter->save();
    painter->setRenderHint(QPainter::Antialiasing, true);

    // clip to map boundaries
    setClipping(painter);

    if (type_normal_)
        paint_normal(painter);
    else
        paint_fronto(painter);

    painter->restore();
}


void MvQFeatureQuasiStationaryItem::paint_normal(QPainter* painter)
{
    // Draw curves and symbols
    bool first_symbol = true;
    int iStart1 = 0, iEnd1 = 0;
    float segSize = symbolGap_ / 2.;  // half size each: semi-circle and triangle
    QPointF p1, p2, pStart1, pEnd1, pStart2, pEnd2;
    QVector<QPointF> hs, tr;
    init_line_segment();
    while (true) {
        // Create a starting space
        if (!compute_next_line_segment(pStart1, pEnd1, segSize, 0.))
            return;

        // Adjust initial point (it has to be equal to the previous point)
        iStart1 = startIndex_;
        iEnd1 = endIndex_;
        if (first_symbol)
            first_symbol = false;
        else
            pStart1 = pEnd2;

        // Create half-circle
        if (!compute_next_line_segment(p1, p2, symbolWidth_, 0.))
            return;

        makeHalfCircle(p1, p2, true, hs);
        pEnd1 = p1;        // adjust point
        QPointF p22 = p2;  // to adjust the first point of the triangle

        // Create triangle
        if (!compute_next_line_segment(p1, p2, symbolWidth_, 0.))
            return;

        p1 = p22;  // adjust point
        symbolFacesLeft_ = !symbolFacesLeft_;
        makeTriangle(p1, p2, symbolWidth_, true, tr);
        symbolFacesLeft_ = !symbolFacesLeft_;

        // Create an ending space
        if (!compute_next_line_segment(pStart2, pEnd2, segSize, 0.))
            return;

        pStart2 = p2;  // adjust point

        // Draw curve and semi-circle
        // Set paint parameters
        painter->setPen(linePenSc_);
        if (front_type_ == FCI_ABOVE_THE_SURFACE)
            painter->setBrush(Qt::NoBrush);
        else
            painter->setBrush(symbolBrushSc_);

        paintLineSegment(painter, iStart1, iEnd1, pStart1, pEnd1);
        painter->drawPolygon(QPolygonF(hs));

        // Draw curve and triangle
        // Set paint parameters
        painter->setPen(linePenTr_);
        if (front_type_ == FCI_ABOVE_THE_SURFACE)
            painter->setBrush(Qt::NoBrush);
        else
            painter->setBrush(symbolBrushTr_);

        painter->drawPolygon(QPolygonF(tr));
        paintLineSegment(painter, startIndex_, endIndex_, pStart2, pEnd2);
    }
}

void MvQFeatureQuasiStationaryItem::paint_fronto(QPainter* painter)
{
    // Draw curves, symbols and separators
    bool colourTag = true;
    bool first_symbol = true;
    int iStart1 = 0, iEnd1 = 0;
    float segSize = 1.5;  // small segment before semi-circle and after triangle
    QPointF p1, p2, pStart1, pEnd1, pStart2, pEnd2;
    QPointF pSep1, pSep2;
    QVector<QPointF> hs, tr;
    init_line_segment();
    while (true) {
        // Separators will be drawn only after first symbol
        if (!first_symbol) {
            if (!compute_next_line_segment(pSep1, pSep2, symbolGap_, 0.))
                return;
        }

        // Create a starting space
        if (!compute_next_line_segment(pStart1, pEnd1, segSize, 0.))
            return;

        iStart1 = startIndex_;
        iEnd1 = endIndex_;

        // Create half-circle
        if (!compute_next_line_segment(p1, p2, symbolWidth_, 0.))
            return;

        makeHalfCircle(p1, p2, true, hs);
        pEnd1 = p1;        // adjust point
        QPointF p22 = p2;  // to adjust the first point of the triangle

        // Create triangle
        if (!compute_next_line_segment(p1, p2, symbolWidth_, 0.))
            return;

        p1 = p22;  // adjust point
        symbolFacesLeft_ = !symbolFacesLeft_;
        makeTriangle(p1, p2, symbolWidth_, true, tr);
        symbolFacesLeft_ = !symbolFacesLeft_;

        // Create an ending space
        if (!compute_next_line_segment(pStart2, pEnd2, segSize, 0.))
            return;

        pStart2 = p2;  // adjust point

        // Draw separator between symbols, only for the second symbol onwards
        if (first_symbol)
            first_symbol = false;
        else {
            colourTag = !colourTag;
            if (colourTag)
                paintSeparator(painter, linePenTr_, symbolBrushTr_,
                               pSep1, pSep2);
            else
                paintSeparator(painter, linePenSc_, symbolBrushSc_,
                               pSep1, pSep2);
        }

        // Draw curve and semi-circle
        painter->setPen(linePenSc_);
        painter->setBrush(symbolBrushSc_);
        paintLineSegment(painter, iStart1, iEnd1, pStart1, pEnd1);
        painter->drawPolygon(QPolygonF(hs));

        // Draw curve and triangle
        painter->setPen(linePenTr_);
        painter->setBrush(symbolBrushTr_);
        painter->drawPolygon(QPolygonF(tr));
        paintLineSegment(painter, startIndex_, endIndex_, pStart2, pEnd2);
    }
}

//========================================
//
// MvQFeatureRidgeItem
//
//========================================

MvQFeatureRidgeItem::MvQFeatureRidgeItem(WsType* feature) :
    MvQFeatureMetCurveItem(feature)
{
    front_type_ = "RIDGE";
    type_normal_ = true;
}

bool MvQFeatureRidgeItem::getRequestParameters()
{
    if (!req_)
        return false;

    expandStyleRequest();

    std::string val;

    // Get input parameters
    // Set line parameters
    linePen_ = MvQ::makePen("SOLID",
                            (int)req_("LINE_THICKNESS"),
                            (const char*)req_("COLOUR"));

    // Set symbol parameters
    symbolGap_ = 0.;
    symbolFacesLeft_ = (req_.getValue("SYMBOL_ORIENTATION", val, true) && val == "LEFT");

    symbolBrush_ = MvQ::makeBrush("SOLID", (const char*)req_("COLOUR"));
    if ((int)req_("SYMBOL_WIDTH") <= MIN_SAMPLE_DS) {
        std::ostringstream oss;
        oss << "Parameter SYMBOL_WIDTH: Minimum value allowed = ";
        oss << MIN_SAMPLE_DS + 1;
        QMessageBox::warning(nullptr, QString("Metview"), oss.str().c_str());
        return false;
    }
    else
        symbolWidth_ = (int)req_("SYMBOL_WIDTH");

    return true;
}

void MvQFeatureRidgeItem::updateSymbols()
{
    symbolSize_ = QSizeF(symbolWidth_, symbolWidth_ / 2.);
}

void MvQFeatureRidgeItem::paint(QPainter* painter,
                                const QStyleOptionGraphicsItem*, QWidget*)
{
    if (points_.size() < 2)
        return;

    painter->save();
    painter->setRenderHint(QPainter::Antialiasing, true);

    // clip to map boundaries
    setClipping(painter);

    // draw symbols
    painter->setPen(linePen_);
    QPointF p1, p2;
    QVector<QPointF> symb;
    init_line_segment();
    while (compute_next_line_segment(p1, p2, symbolWidth_, symbolGap_)) {
        // create symbol
        makeTriangle(p1, p2, symbolWidth_, false, symb);
        // draw symbol
        painter->drawPolyline(QPolygonF(symb));
    }

    painter->restore();
}

//========================================
//
// MvQFeatureIttDiscontinuityItem
//
//========================================

bool MvQFeatureIttDiscontinuityItem::getRequestParameters()
{
    if (!req_)
        return false;

    expandStyleRequest();

    // Get input parameters
    // Set line parameters
    linePenC1_ = MvQ::makePen("SOLID",
                              (int)req_("LINE_THICKNESS"),
                              (const char*)req_("COLOUR_1"));

    linePenC2_ = MvQ::makePen("SOLID",
                              (int)req_("LINE_THICKNESS"),
                              (const char*)req_("COLOUR_2"));

    // Set symbol parameters
    symbolGap_ = (int)req_("SYMBOL_GAP");
    symbolWidth_ = (int)req_("SYMBOL_WIDTH");

    return true;
}

void MvQFeatureIttDiscontinuityItem::paint(QPainter* painter,
                                           const QStyleOptionGraphicsItem*, QWidget*)
{
    if (points_.size() < 2)
        return;

    painter->save();
    painter->setRenderHint(QPainter::Antialiasing, true);

    // clip to map boundaries
    setClipping(painter);

    // Draw curves
    bool swapColour = false;
    QPointF p1, p2;
    init_line_segment();
    while (compute_next_line_segment(p1, p2, symbolWidth_, symbolGap_)) {
        // select line segment colour
        swapColour = !swapColour;
        if (swapColour)
            painter->setPen(linePenC1_);
        else
            painter->setPen(linePenC2_);

        // draw line segment
        MvQ::safeDrawLine(p1, p2, painter);
    }

    painter->restore();
}

//========================================
//
// MvQFeatureConvergenceLineItem
//
//========================================

bool MvQFeatureConvergenceLineItem::getRequestParameters()
{
    if (!req_)
        return false;

    expandStyleRequest();

    std::string val;

    // Get input parameters
    // Set line parameters
    linePen_ = MvQ::makePen("SOLID",
                            (int)req_("LINE_THICKNESS"),
                            (const char*)req_("COLOUR"));

    // Set symbol parameters
    symbolGap_ = (int)req_("SYMBOL_GAP");
    symbolWidth_ = (int)req_("SYMBOL_WIDTH");
    symbolFacesLeft_ = (req_.getValue("SYMBOL_ORIENTATION", val, true) && val == "LEFT");

    return true;
}

void MvQFeatureConvergenceLineItem::updateSymbols()
{
    symb_.clear();
    symb_ << QPointF(0, 0);
    symb_ << QPointF(-symbolWidth_, symbolWidth_);
    symb_ << QPointF(0, 0);
    symb_ << QPointF(-symbolWidth_, -symbolWidth_);

    symbolSize_ = QSizeF(symbolWidth_, symbolWidth_);
}

void MvQFeatureConvergenceLineItem::paint(QPainter* painter,
                                          const QStyleOptionGraphicsItem*, QWidget*)
{
    if (points_.size() < 2)
        return;

    painter->save();
    painter->setRenderHint(QPainter::Antialiasing, true);

    // clip to map boundaries
    setClipping(painter);

    generateCurvePath();

    // Draw curve
    painter->setPen(linePen_);
    painter->drawPath(curvePath_);

    // Draw symbols along the curve
    bool symbSelector = false;
    QPointF p1, p2;
    init_line_segment();
    while (compute_next_line_segment(p1, p2, symbolWidth_, symbolGap_)) {
        // rotate symbol around first point
        auto p = symb_;
        rotateSymbol(p1, p2, symbolFacesLeft_, p);

        // translate symbol to middle point
        translateSymbol(p1, p2, p);

        // draw symbol
        symbSelector = !symbSelector;
        if (symbSelector)
            MvQ::safeDrawLine(p[0], p[1], painter);
        else
            MvQ::safeDrawLine(p[2], p[3], painter);
    }

    painter->restore();
}

//========================================
//
// MvQFeatureInstabilityShearLineItem
//
//========================================

MvQFeatureInstabilityShearLineItem::MvQFeatureInstabilityShearLineItem(
    WsType* feature,
    bool bShear) :
    MvQFeatureMetCurveItem(feature),
    bShear_(bShear)
{
}

// MvQFeatureInstabilityShearLineItem::MvQFeatureInstabilityShearLineItem(const MvQFeatureInstabilityShearLineItem& o) :
//     MvQFeatureMetCurveItem(o)
//{
//      bShear_ = o.bShear_;
// }

bool MvQFeatureInstabilityShearLineItem::getRequestParameters()
{
    if (!req_)
        return false;

    expandStyleRequest();

    // Get input parameters
    // Set line parameters
    linePen_ = MvQ::makePen("SOLID",
                            (int)req_("LINE_THICKNESS"),
                            (const char*)req_("COLOUR"));

    // Set symbol parameters
    symbolGap_ = (int)req_("LINE_GAP");
    symbolWidth_ = (int)req_("LINE_LENGTH");
    symbolBrush_ = MvQ::makeBrush("SOLID", (const char*)req_("COLOUR"));
    diameter_ = (double)req_("CIRCLE_DIAMETER");

    return true;
}

void MvQFeatureInstabilityShearLineItem::updateSymbols()
{
    symbolSize_ = QSizeF(symbolWidth_, 1);

    makeCircle(diameter_, 0., 8., symbolSep_);
}

void MvQFeatureInstabilityShearLineItem::paint(QPainter* painter,
                                               const QStyleOptionGraphicsItem*,
                                               QWidget*)
{
    if (points_.size() < 2)
        return;

    painter->save();
    painter->setRenderHint(QPainter::Antialiasing, true);

    // clip to map boundaries
    setClipping(painter);

    // Draw curves and symbols
    QPointF startGap, endGap;
    QPointF p1, p2;
    bool firstGap = true;

    // Main loop
    init_line_segment();
    while (compute_next_line_segment(p1, p2, symbolWidth_, symbolGap_)) {
        // Draw separator between symbols
        if (firstGap)
            firstGap = false;
        else {
            endGap = p1;
            if (bShear_)
                paintSeparatorPolygon(painter, symbolBrush_, startGap, endGap);
            else
                paintSeparatorPolygon2(painter, symbolBrush_, startGap, endGap);
        }

        // Draw curve
        painter->setPen(linePen_);
        MvQ::safeDrawLine(p1, p2, painter);

        // Start a new gap
        startGap = p2;
    }
    painter->restore();
}


//========================================
//
// MvQFeatureInstabilityLineItem
//
//========================================

MvQFeatureInstabilityLineItem::MvQFeatureInstabilityLineItem(
    WsType* feature) :
    MvQFeatureInstabilityShearLineItem(feature, false)
{
}

//========================================
//
// MvQFeatureShearLineItem
//
//========================================

MvQFeatureShearLineItem::MvQFeatureShearLineItem(WsType* feature) :
    MvQFeatureInstabilityShearLineItem(feature, true)
{
}

//========================================
//
// MvQFeatureItczItem
//
//========================================

bool MvQFeatureItczItem::getRequestParameters()
{
    if (!req_)
        return false;

    expandStyleRequest();

    std::string val;

    // Get input parameters
    // Set line parameters
    thickness_ = (int)req_("LINE_THICKNESS");
    linePen_ = MvQ::makePen("SOLID", thickness_,
                            (const char*)req_("COLOUR"));

    // Get other parameters
    width_ = (int)req_("WIDTH");
    intensity_ = (int)req_("INTENSITY");
    intensityGap_ = (int)req_("INTENSITY_GAP");

    return true;
}

void MvQFeatureItczItem::updateSymbols()
{
    symbolSize_ = QSizeF(width_, width_);
}

void MvQFeatureItczItem::buildShape()
{
    auto d = width_ / 2 + 4;
    buildShapeCore(curveX_, curveY_, d, d);
}

void MvQFeatureItczItem::paint(QPainter* painter,
                               const QStyleOptionGraphicsItem*, QWidget*)
{
    if (points_.size() < 2)
        return;

    painter->save();
    painter->setRenderHint(QPainter::Antialiasing, true);

    // clip to map boundaries
    setClipping(painter);

    // Create 2 parallels lines
    QVector<QPointF> pp1;
    QVector<QPointF> pp2;
    parallelCurves(curveX_, curveY_, pp1, pp2, width_, width_);

    // Draw parallels lines
    painter->setPen(linePen_);
    MvQ::drawPolyline(pp1, painter);
    MvQ::drawPolyline(pp2, painter);

    // Since the two offset curves can have a different number of points, the
    // in-between-curve lines rendering will be performed in three steps:
    // A) build orthogonal lines along the original input user curve
    // B) find the crossing points between the orthogonal lines and the two
    //    parallels lines
    // C) draw the in-between-curve lines
    int index1 = 0;
    int index2 = 0;
    float offset;
    QPointF p1, p2;
    init_line_segment();
    while (compute_next_line_segment(p1, p2, (float)intensityGap_, 0.)) {
        if (intensity_ == 1) {
            offset = 0.5;
            if (!drawInBetweenLine(painter, pp1, pp2, p1, p2, offset,
                                   index1, index2))
                break;
        }
        else if (intensity_ == 2) {
            offset = 0.4;
            if (!drawInBetweenLine(painter, pp1, pp2, p1, p2, offset,
                                   index1, index2))
                break;

            offset = 0.6;
            if (!drawInBetweenLine(painter, pp1, pp2, p1, p2, offset,
                                   index1, index2))
                break;
        }
        else if (intensity_ == 3) {
            offset = 0.3;
            if (!drawInBetweenLine(painter, pp1, pp2, p1, p2, offset,
                                   index1, index2))
                break;
            offset = 0.5;
            if (!drawInBetweenLine(painter, pp1, pp2, p1, p2, offset,
                                   index1, index2))
                break;
            offset = 0.7;
            if (!drawInBetweenLine(painter, pp1, pp2, p1, p2, offset,
                                   index1, index2))
                break;
        }
    }

    painter->restore();
}

bool MvQFeatureItczItem::drawInBetweenLine(QPainter* painter,
                                           const QVector<QPointF>& pp1, const QVector<QPointF>& pp2,
                                           const QPointF& p1, const QPointF& p2,
                                           float offset, int& index1, int& index2)
{
    // A) Build orthogonal line along the original input user curve
    // normalised vector from p1 to p2
    auto vec_d = QPointF(p2.x() - p1.x(), p2.y() - p1.y());
    vec_d /= sqrt(pow(vec_d.x(), 2) + pow(vec_d.y(), 2));

    // normal vectors
    auto vec_n_left = QPointF(-vec_d.y(), vec_d.x());
    auto vec_n_right = QPointF(vec_d.y(), -vec_d.x());

    // compute orthogonal line (line equation = y = mx + b)
    float delta = 0.01;  // to avoid division by 0
    auto p = p1 + ((p2 - p1) * offset);
    QPointF pleft = p + width_ * vec_n_left;
    QPointF pright = p + width_ * vec_n_right;
    float m = (pleft.y() - pright.y() + delta) / (pleft.x() - pright.x() + delta);
    float b = pright.y() - (m * pright.x());

    // B) Find the crossing points between the orthogonal lines and the
    //    two parallels lines
    // B1) find in the parallel line the two neighbour points that are
    //     below and above the orthogonal line
    // process first parallel line
    if (!neighbourPoints(pp1, index1, m, b))
        return false;

    // process second parallel line
    if (!neighbourPoints(pp2, index2, m, b))
        return false;

    // B2) compute line equations using the two neighbour points
    float m1 = (pp1[index1 + 1].y() - pp1[index1].y() + delta) /
               (pp1[index1 + 1].x() - pp1[index1].x() + delta);
    float b1 = pp1[index1].y() - (m1 * pp1[index1].x());

    float m2 = (pp2[index2 + 1].y() - pp2[index2].y() + delta) /
               (pp2[index2 + 1].x() - pp2[index2].x() + delta);
    float b2 = pp2[index2].y() - (m2 * pp2[index2].x());

    // B3) compute the two points of intersection between the orthogonal
    // line and the two parallel lines
    float x1 = (b1 - b) / (m - m1);
    float y1 = (m1 * x1) + b1;
    float x2 = (b2 - b) / (m - m2);
    float y2 = (m2 * x2) + b2;

    // C) draw the in-between-curve lines
    MvQ::safeDrawLine(QPointF(x1, y1), QPointF(x2, y2), painter);

    return true;
}

// Given a line and vector of points, find the two neighbour points that are
// below and above this line
// Input:
//        vp    : vector of points
//        index : starting search position at vp
//        slope : line slope (y = slope*x + b)
//        b     : line constant value
// Output:
//        index    : position of the first neighbour point
//        "return" : true/false - two neighbour points found/not found

bool MvQFeatureItczItem::neighbourPoints(const QVector<QPointF>& vp,
                                         int& index, float slope, float b)
{
    // Search the two neighbour points
    bool firsta = true;
    bool firstb = true;
    bool found = false;
    for (int j = index; j < vp.size(); j++) {
        if (vp[j].y() > ((vp[j].x() * slope) + b)) {
            if (firsta)
                firsta = false;
        }
        else {
            if (firstb)
                firstb = false;
        }

        if (!firsta && !firstb) {
            index = j - 1;
            found = true;
            break;
        }
    }

    return found;
}

static MvQFeatureMaker<MvQFeatureColdFrontItem> coldFrontMaker("cold_front");
static MvQFeatureMaker<MvQFeatureWarmFrontItem> warmFrontMaker("warm_front");
static MvQFeatureMaker<MvQFeatureOccludedItem> occludedMaker("occluded");
static MvQFeatureMaker<MvQFeatureQuasiStationaryItem> quasiStationaryMaker("quasi_stat");
static MvQFeatureMaker<MvQFeatureRidgeItem> ridgeMaker("ridge");
static MvQFeatureMaker<MvQFeatureIttDiscontinuityItem> ittDiscontinuityMaker("intertropical_discontinuity");
static MvQFeatureMaker<MvQFeatureConvergenceLineItem> convergenceLineMaker("convergence_line");
static MvQFeatureMaker<MvQFeatureInstabilityLineItem> instabilityLineMaker("instability_line");
static MvQFeatureMaker<MvQFeatureShearLineItem> shearLineMaker("shear_line");
static MvQFeatureMaker<MvQFeatureTropicalWaveItem> tropicalWaveMaker("tropical_wave");
static MvQFeatureMaker<MvQFeatureItczItem> itczMaker("itcz");
static MvQFeatureMaker<MvQFeatureTroughItem> troughMaker("trough");
