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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.knecon.fforesight.service.layoutparser.processor.docstrum.utils.FastAtan2;
import com.knecon.fforesight.service.layoutparser.processor.model.text.RedTextPosition;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPositionSequence;
import com.knecon.fforesight.service.layoutparser.processor.utils.RectangleTransformations;

import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Line extends BoundingBox {

    private static final double WORD_DISTANCE_MULTIPLIER = 0.18;

    @EqualsAndHashCode.Include
    private final double x0;
    @EqualsAndHashCode.Include
    private final double y0;

    @EqualsAndHashCode.Include
    private final double x1;
    @EqualsAndHashCode.Include
    private final double y1;

    private final double height;

    private final List<Character> characters;
    private final List<TextPositionSequence> words = new ArrayList<>();


    public Line(List<Character> characters, double wordSpacing) {

        this.characters = characters;

        if (characters.size() >= 2) {
            // linear regression
            double sx = 0.0;
            double sxx = 0.0;
            double sxy = 0.0;
            double sy = 0.0;
            for (Character character : characters) {
                sx += character.getX();
                sxx += character.getX() * character.getX();
                sxy += character.getX() * character.getY();
                sy += character.getY();
            }
            double b = (characters.size() * sxy - sx * sy) / (characters.size() * sxx - sx * sx);
            double a = (sy - b * sx) / characters.size();

            this.x0 = characters.get(0).getX();
            this.y0 = a + b * this.x0;
            this.x1 = characters.get(characters.size() - 1).getX();
            this.y1 = a + b * this.x1;
        } else {
            Character character = characters.get(0);
            double dx = character.getTextPosition().getWidthDirAdj() / 3;
            double dy = dx * Math.tan(0);
            this.x0 = character.getX() - dx;
            this.x1 = character.getX() + dx;
            this.y0 = character.getY() - dy;
            this.y1 = character.getY() + dy;
        }
        height = computeHeight();
        computeWords(wordSpacing * WORD_DISTANCE_MULTIPLIER);
        buildBBox();
    }


    public double getAngle() {

        return FastAtan2.fastAtan2(y1 - y0, x1 - x0);
    }


    public double getLength() {

        return Math.sqrt((x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1));
    }


    private double computeHeight() {

        return characters.stream()
                       .map(Character::getHeight)
                       .reduce(0d, Double::sum) / characters.size();
    }


    public double angularDifference(Line j) {

        double diff = Math.abs(getAngle() - j.getAngle());
        if (diff <= Math.PI / 2) {
            return diff;
        } else {
            return Math.PI - diff;
        }
    }


    public double horizontalDistance(Line other) {

        double[] xs = new double[4];
        xs[0] = x0;
        xs[1] = x1;
        xs[2] = other.x0;
        xs[3] = other.x1;
        boolean overlapping = xs[1] >= xs[2] && xs[3] >= xs[0];
        Arrays.sort(xs);
        return Math.abs(xs[2] - xs[1]) * (overlapping ? 1 : -1);
    }


    public double verticalDistance(Line other) {

        double ym = (y0 + y1) / 2;
        double yn = (other.y0 + other.y1) / 2;
        return Math.abs(ym - yn);
    }


    private void computeWords(double wordSpacing) {

        TextPositionSequence word = new TextPositionSequence();
        Character previous = null;
        for (Character current : characters) {
            if (previous != null) {
                double dist = current.getTextPosition().getXDirAdj() - previous.getTextPosition().getXDirAdj() - previous.getTextPosition().getWidthDirAdj();
                if (dist > wordSpacing) {
                    words.add(word);
                    word = new TextPositionSequence();
                }
            }
            word.getTextPositions().add(current.getTextPosition());
            previous = current;
        }
        words.add(word);
    }


    private void buildBBox() {

        this.setBBox(characters.stream()
                             .map(Character::getTextPosition)
                             .map(RedTextPosition::getInitialUserSpacePosition)
                             .collect(RectangleTransformations.collectBBox()));
    }


    public String toString() {

        StringBuilder sb = new StringBuilder();
        words.forEach(word -> sb.append(word.toString()).append(" "));
        return sb.toString().trim();
    }

}

