package com.knecon.fforesight.service.layoutparser.processor.services.blockification;

import static java.util.stream.Collectors.toSet;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;

import com.knecon.fforesight.service.layoutparser.processor.model.AbstractPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.ClassificationPage;
import com.knecon.fforesight.service.layoutparser.processor.model.FloatFrequencyCounter;
import com.knecon.fforesight.service.layoutparser.processor.model.Orientation;
import com.knecon.fforesight.service.layoutparser.processor.model.table.CleanRulings;
import com.knecon.fforesight.service.layoutparser.processor.model.text.StringFrequencyCounter;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPositionSequence;

@Service
public class DocuMineBlockificationService {

    static final float THRESHOLD = 1f;

    Pattern pattern = Pattern.compile("^(\\d{1,2}\\.){1,3}\\d{1,2}\\.?\\s[0-9A-Za-z ()-]{2,50}", Pattern.CASE_INSENSITIVE);


    /**
     * This method is building blocks by expanding the minX/maxX and minY/maxY value on each word that is not split by the conditions.
     * This method must use text direction adjusted postions (DirAdj). Where {0,0} is on the upper left. Never try to change this!
     * Rulings (Table lines) must be adjusted to the text directions as well, when checking if a block is split by a ruling.
     *
     * @param textPositions The textPositions of a page.
     * @param cleanRulings  All rulings on a page
     * @return Page object that contains the Textblock and text statistics.
     */
    public ClassificationPage blockify(List<TextPositionSequence> textPositions, CleanRulings cleanRulings) {

        List<TextPositionSequence> chunkWords = new ArrayList<>();
        List<AbstractPageBlock> chunkBlockList1 = new ArrayList<>();

        CleanRulings usedRulings = cleanRulings.withoutTextRulings();

        float minX = 1000;
        float maxX = 0;
        float minY = 1000;
        float maxY = 0;
        TextPositionSequence prev = null;

        boolean wasSplitted = false;
        Float splitX1 = null;
        for (TextPositionSequence word : textPositions) {

            boolean lineSeparation = prev != null && word.getMinYDirAdj() - maxY > Math.min(word.getHeight(), prev.getHeight()) * 1.25;
            boolean startFromTop = prev != null && word.getMinYDirAdj() < prev.getMinYDirAdj() - prev.getTextHeight();
            boolean splitByX = prev != null && maxX + 50 < word.getMinXDirAdj() && prev.getMinYDirAdj() == word.getMinYDirAdj();
            boolean negativeXGap = prev != null && word.getMinXDirAdj() - minX < -5;
            boolean newLineAfterSplit = prev != null && word.getMinYDirAdj() != prev.getMinYDirAdj() && wasSplitted && splitX1 != word.getMinXDirAdj();
            boolean isSplitByRuling = prev != null && usedRulings.lineBetween(prev.getBoundingBox(), word.getBoundingBox());
            boolean splitByDir = prev != null && !prev.getDir().equals(word.getDir());
            boolean splitByOtherFontAndOtherY = prev != null && Math.abs(prev.getMaxYDirAdj() - word.getMaxYDirAdj()) > word.getTextHeight() * 0.2 && (word.getFontStyle()
                                                                                                                                                               .contains("bold")
                                                                                                                                                       && !prev.getFontStyle()
                    .contains("bold") || prev.getFontStyle().contains("bold") && !word.getFontStyle().contains("bold"));

            Matcher matcher = pattern.matcher(chunkWords.stream()
                                                      .collect(Collectors.joining(" ")).toString());
            boolean startsOnSameX = Math.abs(minX - word.getMinXDirAdj()) < 5 && matcher.matches();

            if (prev != null && (lineSeparation || startFromTop || splitByDir || isSplitByRuling || splitByOtherFontAndOtherY || negativeXGap || startsOnSameX)) {

                Orientation prevOrientation = null;
                if (!chunkBlockList1.isEmpty()) {
                    prevOrientation = chunkBlockList1.get(chunkBlockList1.size() - 1).getOrientation();
                }

                TextPageBlock cb1 = buildTextBlock(chunkWords);
                chunkBlockList1.add(cb1);
                chunkWords = new ArrayList<>();

                if (splitByX && !isSplitByRuling) {
                    wasSplitted = true;
                    cb1.setOrientation(Orientation.LEFT);
                    splitX1 = word.getMinXDirAdj();
                } else if (newLineAfterSplit && !isSplitByRuling) {
                    wasSplitted = false;
                    cb1.setOrientation(Orientation.RIGHT);
                    splitX1 = null;
                } else if (prevOrientation != null && prevOrientation.equals(Orientation.RIGHT) && (lineSeparation
                                                                                                    || !startFromTop
                                                                                                    || !splitByX
                                                                                                    || !newLineAfterSplit
                                                                                                    || !isSplitByRuling)) {
                    cb1.setOrientation(Orientation.LEFT);
                }

                minX = 1000;
                maxX = 0;
                minY = 1000;
                maxY = 0;
                prev = null;
            }

            chunkWords.add(word);

            prev = word;
            if (word.getMinXDirAdj() < minX) {
                minX = word.getMinXDirAdj();
            }
            if (word.getMaxXDirAdj() > maxX) {
                maxX = word.getMaxXDirAdj();
            }
            if (word.getMinYDirAdj() < minY) {
                minY = word.getMinYDirAdj();
            }
            if (word.getMaxYDirAdj() > maxY) {
                maxY = word.getMaxYDirAdj();
            }
        }

        TextPageBlock cb1 = buildTextBlock(chunkWords);
        if (cb1 != null) {
            chunkBlockList1.add(cb1);
        }

        return new ClassificationPage(chunkBlockList1);
    }


    private boolean equalsWithThreshold(float f1, float f2) {

        return Math.abs(f1 - f2) < THRESHOLD;
    }


    private TextPageBlock buildTextBlock(List<TextPositionSequence> wordBlockList) {

        TextPageBlock textBlock = null;

        FloatFrequencyCounter lineHeightFrequencyCounter = new FloatFrequencyCounter();
        FloatFrequencyCounter fontSizeFrequencyCounter = new FloatFrequencyCounter();
        FloatFrequencyCounter spaceFrequencyCounter = new FloatFrequencyCounter();
        StringFrequencyCounter fontFrequencyCounter = new StringFrequencyCounter();
        StringFrequencyCounter styleFrequencyCounter = new StringFrequencyCounter();

        for (TextPositionSequence wordBlock : wordBlockList) {

            lineHeightFrequencyCounter.add(wordBlock.getTextHeight());
            fontSizeFrequencyCounter.add(wordBlock.getFontSize());
            spaceFrequencyCounter.add(wordBlock.getSpaceWidth());
            fontFrequencyCounter.add(wordBlock.getFont());
            styleFrequencyCounter.add(wordBlock.getFontStyle());

            if (textBlock == null) {
                textBlock = new TextPageBlock(wordBlock.getMinXDirAdj(),
                                              wordBlock.getMaxXDirAdj(),
                                              wordBlock.getMinYDirAdj(),
                                              wordBlock.getMaxYDirAdj(),
                                              wordBlockList,
                                              wordBlock.getRotation());
            } else {
                TextPageBlock spatialEntity = textBlock.union(wordBlock);
                textBlock.resize(spatialEntity.getMinX(), spatialEntity.getMinY(), spatialEntity.getWidth(), spatialEntity.getHeight());
            }
        }

        if (textBlock != null) {
            textBlock.setMostPopularWordFont(fontFrequencyCounter.getMostPopular());
            textBlock.setMostPopularWordStyle(styleFrequencyCounter.getMostPopular());
            textBlock.setMostPopularWordFontSize(fontSizeFrequencyCounter.getMostPopular());
            textBlock.setMostPopularWordHeight(lineHeightFrequencyCounter.getMostPopular());
            textBlock.setMostPopularWordSpaceWidth(spaceFrequencyCounter.getMostPopular());
            textBlock.setHighestFontSize(fontSizeFrequencyCounter.getHighest());
        }

        if (textBlock != null
            && textBlock.getSequences() != null
            && textBlock.getSequences()
                       .stream()
                       .map(t -> round(t.getMinYDirAdj(), 3))
                       .collect(toSet()).size() == 1) {
            textBlock.getSequences().sort(Comparator.comparing(TextPositionSequence::getMinXDirAdj));
        }
        return textBlock;
    }

    private double round(float value, int decimalPoints) {

        var d = Math.pow(10, decimalPoints);
        return Math.round(value * d) / d;
    }

}

