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.Iterator;
import java.util.List;

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;
import com.knecon.fforesight.service.layoutparser.processor.visualization.LayoutparsingVisualizations;

@SuppressWarnings("all")
@Service
public class RedactManagerBlockificationService {

    static final float THRESHOLD = 1f;


    /**
     * 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 (TablePageBlock lines) must be adjusted to the text directions as well, when checking if a block is split by a ruling.
     *
     * @param textPositions  The words of a page.
     * @param visualizations
     * @return Page object that contains the Textblock and text statistics.
     */
    public ClassificationPage blockify(List<TextPositionSequence> textPositions, CleanRulings cleanRulings, LayoutparsingVisualizations visualizations) {

        CleanRulings usedRulings = cleanRulings.withoutTextRulings();

        int indexOnPage = 0;
        List<TextPositionSequence> chunkWords = new ArrayList<>();
        List<AbstractPageBlock> chunkBlockList = new ArrayList<>();

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

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

            boolean lineSeparation = word.getMinYDirAdj() - maxY > word.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 xIsBeforeFirstX = prev != null && word.getMinXDirAdj() < minX;
            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());

            if (prev != null && (lineSeparation || startFromTop || splitByX || splitByDir || isSplitByRuling)) {

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

                TextPageBlock cb1 = buildTextBlock(chunkWords, indexOnPage);
                indexOnPage++;

                chunkBlockList.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, indexOnPage);
        if (cb1 != null) {
            chunkBlockList.add(cb1);
        }

        Iterator<AbstractPageBlock> itty = chunkBlockList.iterator();

        TextPageBlock previousLeft = null;
        TextPageBlock previousRight = null;
        while (itty.hasNext()) {
            TextPageBlock block = (TextPageBlock) itty.next();

            if (previousLeft != null && block.getOrientation().equals(Orientation.LEFT)) {
                if (previousLeft.getMinY() > block.getMinY() && block.getMaxY() + block.getMostPopularWordHeight() > previousLeft.getMinY()) {
                    previousLeft.add(block);
                    itty.remove();
                    continue;
                }
            }

            if (previousRight != null && block.getOrientation().equals(Orientation.RIGHT)) {
                if (previousRight.getMinY() > block.getMinY() && block.getMaxY() + block.getMostPopularWordHeight() > previousRight.getMinY()) {
                    previousRight.add(block);
                    itty.remove();
                    continue;
                }
            }

            if (block.getOrientation().equals(Orientation.LEFT)) {
                previousLeft = block;
            } else if (block.getOrientation().equals(Orientation.RIGHT)) {
                previousRight = block;
            }
        }

        itty = chunkBlockList.iterator();
        TextPageBlock previous = null;
        while (itty.hasNext()) {
            TextPageBlock block = (TextPageBlock) itty.next();

            if (previous != null && previous.getOrientation().equals(Orientation.LEFT) && block.getOrientation().equals(Orientation.LEFT) && equalsWithThreshold(block.getMaxY(),
                                                                                                                                                                 previous.getMaxY())
                || previous != null
                   && previous.getOrientation().equals(Orientation.LEFT)
                   && block.getOrientation().equals(Orientation.RIGHT)
                   && equalsWithThreshold(block.getMaxY(), previous.getMaxY())) {
                previous.add(block);
                itty.remove();
                continue;
            }

            previous = block;
        }

        visualizations.addTextBlockVisualizations(chunkBlockList.stream()
                                                          .map(tb -> (TextPageBlock) tb)
                                                          .toList(), textPositions.get(0).getPage());

        return new ClassificationPage(chunkBlockList);
    }


    private boolean equalsWithThreshold(float f1, float f2) {

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


    private TextPageBlock buildTextBlock(List<TextPositionSequence> wordBlockList, int indexOnPage) {

        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;
    }

}
