#include "topology_fixer.h"
#include "topology_data.h"

#include "fixers/edges_overlapper.h"
#include "fixers/face_gap_remover.h"
#include "fixers/edges_intersector.h"
#include "fixers/nodes_merger.h"
#include "fixers/edges_merger.h"
#include "fixers/self_overlapped_faces_remover.h"
#include "fixers/features_merger.h"
#include "fixers/faces_merger.h"
#include "fixers/simple_edge_fixers.h"

#include "utils/face_validator.cpp"
#include "utils/edges_overlaps_fixer.h"

namespace geolib = maps::geolib3;

namespace maps {
namespace wiki {
namespace topology_fixer {

void
TopologyFixer::operator () ()
{
    validateFaces(data_);
    removeDegenerateFaces();
    mergeFaces();
    fixJunctionsMismatch();
    splitHeavyEdges();
    simplifyEdges();
    mergeCloseNodes();
    processEdgeInteractions();
    simplifyEdges();
    mergeCloseNodes();
    removeFaceGaps();
    mergeFeatures();
    mergeEdges();
}


void
TopologyFixer::removeDegenerateFaces()
{
    if (data_.ftGroup()->topologyType() == TopologyType::Contour) {
        const auto& params = restrictions_.degenerateFace();
        INFO() << "Removing degenerate (self-overlapping) faces"
            << ", face max width " << params.maxFaceWidth()
            << ", max angle between overlapping face segments " << params.maxAngleBetweenSegments()
            << ", min ad face area " << params.minAdFaceArea();
        SelfOverlappedFacesRemover rm(
            params.maxFaceWidth(),
            params.maxAngleBetweenSegments(),
            params.minAdFaceArea());
        rm(data_);
        INFO() << "Removing degenerate (self-overlapping) faces: [Done]";
    }
}

void
TopologyFixer::mergeFaces()
{
    if (data_.ftGroup()->topologyType() == TopologyType::Contour) {
        const auto& params = restrictions_.faceMerging();
        INFO() << "Merging equivalent faces, tolerance " << params.tolerance()
            << ", max angle between corresponding segments " << params.maxAngleBetweenSegments()
            << ", max translation factor " << params.maxTranslationFactor()
            << ", max area-to-perimeter ratio factor " << params.maxAPRatioFactor();
        FacesMerger fmerger(params, data_, locker_);
        fmerger(pool_);
        INFO() << "Merging equivalent faces: [Done]";

        validateFaces(data_);
    }
}

void
TopologyFixer::fixJunctionsMismatch()
{
    INFO() << "Fixing junctions mismatch";
    JunctionsMismatchFixer junctionsFixer;
    junctionsFixer(data_);
    INFO() << "Fixing junctions mismatch: [Done]";

    validateFaces(data_);
}

void
TopologyFixer::splitHeavyEdges()
{
    const size_t maxEdgeVertices = restrictions_.edgeGeometry().maxEdgeVertices();
    HeavyEdgesSplitter splitter(maxEdgeVertices);
    INFO() << "Splitting heavy edges, max edge vertices " << maxEdgeVertices;
    splitter(data_);
    INFO() << "Splitting heavy edges: [Done]";

    validateFaces(data_);
}

void
TopologyFixer::simplifyEdges()
{
    const double minSegmentLength = restrictions_.edgeGeometry().minSegmentLength();
    EdgesSimplifier simplifier(minSegmentLength);
    INFO() << "Simplifying edges, min segment length " << minSegmentLength;
    simplifier(data_, locker_, pool_);
    INFO() << "Simplifying edges: [Done]";

    validateFaces(data_);
}

void
TopologyFixer::mergeCloseNodes()
{
    const double minNodesDistance = restrictions_.nodeMerging().minNodesDistance();
    const double maxRemovableEdgeLength =
        restrictions_.nodeMerging().maxRemovableEdgeLength();
    NodesMerger nodesMerger(minNodesDistance, maxRemovableEdgeLength);
    INFO() << "Merging close nodes"
        << ", min nodes distance " << minNodesDistance
        << ", max removable edge length " << maxRemovableEdgeLength;
    nodesMerger(data_);
    INFO() << "Merging close nodes: [Done]";

    validateFaces(data_);
}

void
TopologyFixer::processEdgeInteractions()
{
    if (data_.ftGroup()->topologyType() == TopologyType::Contour) {
        EdgesOverlapper overlapper(
            restrictions_.edgeOverlap().tolerance(),
            restrictions_.edgeOverlap().maxAngleBetweenSegments());
        INFO() << "Fixing overlapped edges"
               << ", tolerance " << restrictions_.edgeOverlap().tolerance()
               << ", max angle between overlapping segments " << restrictions_.edgeOverlap().maxAngleBetweenSegments();
        overlapper(data_, locker_, pool_);
        INFO() << "Fixing overlapped edges: [Done]";

        validateFaces(data_);
    }
    if (data_.ftGroup()->topologyType() == TopologyType::Linear) {
        utils::LinearEdgesIntersectionsFixer::Params params = {
            restrictions_.edgeOverlap().tolerance(),
            restrictions_.edgeOverlap().maxAngleBetweenSegments(),
            restrictions_.edgeGeometry().minSegmentLength(),
            restrictions_.edgeIntersection().tolerance(),
            restrictions_.edgeIntersection().tolerance()
        };
        EdgesIntersector intersector(params);
        INFO() << "Fixing overlapped and intersected edges"
               << ", tolerance " << restrictions_.edgeOverlap().tolerance()
               << ", max angle between overlapping segments " << restrictions_.edgeOverlap().maxAngleBetweenSegments()
               << ", min segment length " << restrictions_.edgeGeometry().minSegmentLength()
               << ", max vertex snap distance " << restrictions_.edgeIntersection().tolerance()
               << ", vertex snap tolerance " << restrictions_.edgeIntersection().tolerance();
        intersector(data_, locker_, pool_);
        INFO() << "Fixing overlappedand intersected edges: [Done]";
    }
}

void
TopologyFixer::removeFaceGaps()
{
    if (data_.ftGroup()->topologyType() == TopologyType::Contour) {
        const double maxGapWidth = restrictions_.faceGap().maxGapWidth();
        const double maxGapLengthFactor = restrictions_.faceGap().maxGapLengthFactor();
        FaceGapRemover gapRemover(maxGapWidth, maxGapLengthFactor);
        INFO() << "Removing face gaps"
               << ", max gap width " << maxGapWidth
               << ", max gap sides length factor " << maxGapLengthFactor;
        gapRemover(data_, locker_, pool_);
        INFO() << "Removing face gaps: [Done]";

        validateFaces(data_);
    }
}

void
TopologyFixer::mergeEdges()
{
    const size_t maxVertices = restrictions_.edgeGeometry().maxEdgeVertices();
    INFO() << "Merging edges"
           << ", max edge vertices " << maxVertices;
    EdgesMerger edgesMerger(maxVertices);
    edgesMerger(data_);
    INFO() << "Merging edges: [Done]";

    validateFaces(data_);
}

void
TopologyFixer::mergeFeatures()
{
    if (data_.ftGroup()->topologyType() == TopologyType::Contour &&
        (data_.ftGroup()->id() == TopologyGroupId::Vegetation ||
         data_.ftGroup()->id() == TopologyGroupId::Relief))
    {
        const size_t maxMasterFaces = restrictions_.ftMerging().maxMasterFaces();
        const size_t maxFaceEdges = restrictions_.ftMerging().maxFaceEdges();
        const double maxFaceBBoxSize = restrictions_.ftMerging().maxFaceBBoxSize();
        FeaturesMerger merger(
            maxMasterFaces, maxFaceEdges, maxFaceBBoxSize,
            threads_);
        INFO() << "Merging features with shared edges"
           << ", max master faces count " << maxMasterFaces
           << ", max face edges count " << maxFaceEdges
           << ", max face bbox size " << maxFaceBBoxSize;
        merger(data_);
        INFO() << "Merging features with shared edges: [Done]";

        validateFaces(data_);
    }
}

} // namespace topology_fixer
} // namespace wiki
} // namespace maps
