#pragma once

#include "opencv2/core/core.hpp"
#include <vector>

namespace maps {
namespace wiki {
namespace autocart {

// если явно не указано обратное, то
//     все угловые параметры в радианах
//     все длины в пикселях

// параметры выделения вершин из изображения с вероятностями
struct ExtractVerticesParams
{
    ExtractVerticesParams()
        : minThreshold(0.2)
        , nmsCellSz(6)
    {}
    // минимальное значение веса в изображении с вероятностями вершин. Всё что меньше - занулиться,
    // в остатках будем искать локальные максимумы
    double minThreshold;
    // размер ячейки для non maximum suppression вершин
    int nmsCellSz;
};

// параметры выделения рёбер из изображения с вероятностями
struct ExtractEdgesParams
{
    ExtractEdgesParams()
        : minWeightThreshold(-1.)
        , maxEdgesInConComponent(150)
        , minLength(5.)
        , maxLength(100.)
    {}

    // ребра с весом меньше minWeightThreshold не рассматриваем,
    // если параметр отрицательный, то подбирается автоматически, начиная
    // c 1.5 * cv::mean(карта вероятностей для рёбер)
    // увеличивая пока максимальное число рёбер в компонентах связности
    // не станет меньше
    double minWeightThreshold;
    // применяется только если minWeightThreshold < 0.0
    // в этом случае если в компоненте связности оказывается рёбер больше чем
    // maxEdgesInConComponent, то переимпортируем данные с увеличенным
    // порогом веса рёбер, иначе за счёт комбинаторной сложности алгоритма
    // мы просто не дождёмся окончания разметки
    int maxEdgesInConComponent;
    // рассматриваем только рёбра с длинами от minLength до maxLength
    double minLength;
    double maxLength;
};

// параметры взвешивания циклов (см. функцию штрафа
//    double CoordGraph::weightCycle(const EdgesPath &cycle, const cv::Mat &segm, const WeightCycleParams &param) const)
struct WeightCycleParams
{
    WeightCycleParams()
        : smallAngleMax(30. / 180. * CV_PI)
        , smallAnglePenaltyEps(1e-2)
        , nonQuadPenalty(0.0)
        , angleDeltaCoefPenalty(10.0)
        , segmAreaCoeff(0.3)
        , minArea(500)
    {}

    // если цикл содержит вершину с углом ребер меньше smallAngleMax
    // то вес такого цикла штрафуется используя величину smallAnglePenalty
    double smallAngleMax;
    double smallAnglePenaltyEps;
    // снижаем вес цикла на каждое ребро после первых 4-х
    double nonQuadPenalty;

    // величина используется как коэффициент штрафа отличия углов от 90 градусов
    double angleDeltaCoefPenalty;

    // вес складывается из веса ребер (с учетом штрафа за углы) +
    // относительная площадь внутренности цикла, которая размечена как здание при семантической сегментации
    //      segmAreaCoeff - коэффициент с которым добавляем относительную площадь
    double segmAreaCoeff;

    // минимальная площадь внутренности цикла (в пикселях), если меньше зануляем вес
    int minArea;
};

//параметры выбора "лучших" циклов из набора возможных
struct BestCycleParams
{
    BestCycleParams()
        : minCycleWeight(0.3)
        , minIntersectionArea(300)
        , minRelativeIntersectionArea(0.2)
        , newEdgesPercent(0.5)
    {}
    // все циклы с весами меньшими minCycleWeight на выход подаваться не будут
    double minCycleWeight;

    // если цикл пересекается с уже выбраными больше чем на minIntersectionArea (в пикселях),
    // то в Best цикл не отправляется
    int minIntersectionArea;

    // если площадь пересечения цикла с уже выбраными делённая на площадь цикла, больше чем
    // minRelativeIntersectionArea, то в Best цикл не отправляется
    double minRelativeIntersectionArea;

    //если процент "новых" ребер в цикле меньше, то цикл не добавляем
    double newEdgesPercent;
};

struct ThinPolygonsParams
{
    ThinPolygonsParams()
        : maxVertEdgeDist(6.)
        , leftRange(0.1)
        , rightRange(0.9)
    {}
    // если больше 0.0 то удаляем узкие многоугольники (т.е. если точка лежит от ребра на расстоянии меньше
    // maxVertEdgeDist, то ребро удаляется)
    double maxVertEdgeDist;

    // мы проверяем куда проектируется точка на ребро, мы хотим удалять ребра на которые близкая
    // вершина проектируется примерно по центру ребра, а не на его концы
    double leftRange;
    double rightRange;
};

struct ExtractPolygonsParams
{
    ExtractPolygonsParams()
        : extractLevelsCount(4)
        , keenVerticesMaxAngle(15. / 180. * CV_PI)
        , removeIntersectEdges(true)
    {}
    ExtractVerticesParams vertsParams;
    ExtractEdgesParams edgesParams;

    int extractLevelsCount;

    // если больше 0.0 удаляем острые вершины (с двумя ребрами угол между которыми меньше keenVerticesMaxAngle)
    // если меньше или равен 0.0 то удаляем только изолированные вершины и листья (т.е. вершины с 0 или 1 ребром)
    double keenVerticesMaxAngle;
    // удалять или нет пересекающиеся ребра
    bool removeIntersectEdges;

    ThinPolygonsParams thinPolygonsParams;
    WeightCycleParams weightCycleParams;
    BestCycleParams bestCycleParams;
};

std::vector< std::vector<cv::Point> >
extractPolygons(const cv::Mat &vertData,
                const cv::Mat &edgeData,
                const cv::Mat &segm_data,
                const ExtractPolygonsParams &params);

} //namespace autocart
} //namespace wiki
} //namespace maps
