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

import java.util.Comparator;
import java.util.HashSet;
import java.util.List;

import org.springframework.stereotype.Service;

import com.knecon.fforesight.service.layoutparser.processor.docstrum.model.AngleFilter;
import com.knecon.fforesight.service.layoutparser.processor.docstrum.model.Character;
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.model.table.CleanRulings;

@Service
public class LineBuilderService {

    private static final double CHARACTER_SPACING_DISTANCE_MULTIPLIER = 3.5;
    private static final double LINE_SPACING_THRESHOLD_MULTIPLIER = 0.67;
    private static final double ANGLE_TOLERANCE = Math.PI / 6;


    public List<Line> buildLines(List<Character> characters, double characterSpacing, double lineSpacing, CleanRulings rulings) {

        double maxHorizontalDistance = characterSpacing * CHARACTER_SPACING_DISTANCE_MULTIPLIER;
        double maxVerticalDistance = lineSpacing * LINE_SPACING_THRESHOLD_MULTIPLIER;

        UnionFind<Character> unionFind = new UnionFind<>(new HashSet<>(characters));

        AngleFilter angleFilter = new AngleFilter(-ANGLE_TOLERANCE, ANGLE_TOLERANCE);

        characters.forEach(character -> {
            character.getNeighbors()
                    .forEach(neighbor -> {
                        double normalizedHorizontalDistance = neighbor.getHorizontalDistance() / maxHorizontalDistance;
                        double normalizedVerticalDistance = neighbor.getVerticalDistance() / maxVerticalDistance;

                        if (character.getTextPosition().getDir() != neighbor.getCharacter().getTextPosition().getDir() //
                            || !angleFilter.matches(neighbor) //
                            || Math.pow(normalizedHorizontalDistance, 2) + Math.pow(normalizedVerticalDistance, 2) > 1 //
                            || rulings.lineBetween(character, neighbor.getCharacter())) {
                            return;
                        }

                        unionFind.union(character, neighbor.getCharacter());
                    });
        });

        return unionFind.getGroups()
                .stream()
                .map(lineCharacters -> lineCharacters.stream()
                        .sorted(Comparator.comparingDouble(Character::getX))
                        .toList())
                .map(lineCharacters -> new Line(lineCharacters, characterSpacing))
                .toList();
    }

}
