package com.knecon.fforesight.service.layoutparser.processor.docstrum.service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;

import com.knecon.fforesight.service.layoutparser.processor.docstrum.model.Line;
import com.knecon.fforesight.service.layoutparser.processor.docstrum.model.UnionFind;
import com.knecon.fforesight.service.layoutparser.processor.docstrum.model.Zone;
import com.knecon.fforesight.service.layoutparser.processor.model.table.CleanRulings;
import com.knecon.fforesight.service.layoutparser.processor.model.text.Word;
import com.knecon.fforesight.service.layoutparser.processor.utils.TextPositionOperations;

@Service
public class ZoneBuilderService {

    private static final double MIN_HORIZONTAL_DISTANCE_MULTIPLIER = -0.5;
    private static final double MAX_VERTICAL_DISTANCE_MULTIPLIER = 1.2;

    private static final double MIN_HORIZONTAL_MERGE_DISTANCE_MULTIPLIER = -7;

    private static final double MAX_VERTICAL_MERGE_DISTANCE_MULTIPLIER = 0.5;

    private static final double MIN_LINE_SIZE_SCALE = 0.9;

    private static final double MAX_LINE_SIZE_SCALE = 2.5;

    private static final double ANGLE_TOLERANCE = Math.toRadians(5);

    private static final double MAX_VERTICAL_MERGE_DISTANCE = 0.5;


    public List<Zone> buildZones(List<Line> lines, double characterSpacing, double lineSpacing, CleanRulings rulings) {

        double minHorizontalDistance = characterSpacing * MIN_HORIZONTAL_DISTANCE_MULTIPLIER;
        double maxVerticalDistance = lineSpacing * MAX_VERTICAL_DISTANCE_MULTIPLIER;
        double minHorizontalMergeDistance = lineSpacing * MIN_HORIZONTAL_MERGE_DISTANCE_MULTIPLIER;
        double maxVerticalMergeDistance = lineSpacing * MAX_VERTICAL_MERGE_DISTANCE_MULTIPLIER;

        UnionFind<Line> unionFind = new UnionFind<>(new HashSet<>(lines));

        double meanHeight = calculateMeanHeight(lines);

        lines.forEach(outerLine -> {
            lines.forEach(innerLine -> {

                if (innerLine == outerLine //
                    || unionFind.inSameSet(outerLine, innerLine)//
                    || outerLine.angularDifference(innerLine) > ANGLE_TOLERANCE) {
                    return;
                }

//                if (!innerLine.getFontStyle().equals(outerLine.getFontStyle()) //
//                    && !outerLine.intersectsY(innerLine, -2f)) {
//                    return;
//                }

                double horizontalScale = Math.min(outerLine.getHeightDirAdj(), innerLine.getHeightDirAdj()) / meanHeight;
                horizontalScale = Math.max(MIN_LINE_SIZE_SCALE, Math.min(horizontalScale, MAX_LINE_SIZE_SCALE));
                double verticalScale = horizontalScale;

//                if (innerLine.toString().endsWith(":")
//                    || outerLine.toString().endsWith(":")
//                    || numericalIdentifierPattern.matcher(innerLine.toString()).matches()
//                    || numericalIdentifierPattern.matcher(outerLine.toString()).matches()) {
//
//                    horizontalScale *= 5;
//                    verticalScale /= 10;
//                }

                double horizontalDistance = outerLine.horizontalDistance(innerLine) / horizontalScale;
                double verticalDistance = outerLine.verticalDistance(innerLine) / verticalScale;

                if ((!(minHorizontalDistance <= horizontalDistance) || !(verticalDistance <= maxVerticalDistance)) //
                    && (!(minHorizontalMergeDistance <= horizontalDistance) || !(verticalDistance <= maxVerticalMergeDistance))) {
                    return;
                }

                if (rulings.lineBetween(outerLine, innerLine)) {
                    return;
                }

                unionFind.union(outerLine, innerLine);

            });
        });

        return unionFind.getGroups()
                .stream()
                .map(group -> mergeLinesInZone(new ArrayList<>(group), characterSpacing, lineSpacing))
                .toList();
    }


    private double calculateMeanHeight(List<Line> lines) {

        double meanHeight = 0.0;
        double weights = 0.0;
        for (Line line : lines) {
            double weight = line.getLength();
            meanHeight += line.getHeightDirAdj() * weight;
            weights += weight;
        }
        meanHeight /= weights;
        return meanHeight;
    }


    private Zone mergeLinesInZone(List<Line> lines, double characterSpacing, double lineSpacing) {

        Set<Word> words = lines.stream()
                .map(Line::getWords)
                .flatMap(Collection::stream)
                .collect(Collectors.toSet());
        Collection<Set<Word>> groupedLines = TextPositionOperations.groupByLine(words);

        List<Line> sortedLines = TextPositionOperations.sortLines(groupedLines);
        return new Zone(sortedLines);
    }

}
