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

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

import org.springframework.stereotype.Service;

import com.iqser.red.service.redaction.v1.server.model.document.nodes.LayoutEngine;
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.Orientation;
import com.knecon.fforesight.service.layoutparser.processor.model.table.CleanRulings;
import com.knecon.fforesight.service.layoutparser.processor.model.table.TablePageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.text.Word;

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

    static final float THRESHOLD = 1f;
    public static final double FONT_SIZE_CHANGE_RATIO = 0.15;

    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<Word> textPositions, CleanRulings cleanRulings) {

        List<Word> chunkWords = new ArrayList<>();
        List<AbstractPageBlock> textPageBlocks = new ArrayList<>();

        CleanRulings usedRulings = cleanRulings.withoutTextRulings();

        double minX = 1000;
        double maxX = 0;
        double minY = 1000;
        double maxY = 0;
        Word prev = null;

        boolean wasSplitted = false;
        Double splitX1 = null;
        for (Word word : textPositions) {

            boolean lineSeparation = prev != null && word.getYDirAdj() - prev.getMaxYDirAdj() > Math.min(word.getHeight(), prev.getHeight()) * 1.1;
            boolean startFromTop = prev != null && word.getYDirAdj() < prev.getYDirAdj() - prev.getTextHeight();
            boolean splitByX = prev != null && maxX + 50 < word.getXDirAdj() && prev.getYDirAdj() == word.getYDirAdj();
            boolean negativeXGap = prev != null && word.getXDirAdj() - minX < -5;
            boolean newLineAfterSplit = prev != null && word.getYDirAdj() != prev.getYDirAdj() && wasSplitted && splitX1 != word.getXDirAdj();
            boolean isSplitByRuling = prev != null && usedRulings.lineBetween(prev, word);
            boolean splitByDir = prev != null && !prev.getDir().equals(word.getDir());
            boolean splitByOtherFontAndOtherY = prev != null && Math.abs(prev.getMaxYDirAdj() - word.getMaxYDirAdj()) > word.getTextHeight() * 0.2 && isFontChange(word, prev);

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

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

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

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

                if (splitByX && !isSplitByRuling) {
                    wasSplitted = true;
                    cb1.setOrientation(Orientation.LEFT);
                    splitX1 = word.getXDirAdj();
                } 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.getXDirAdj() < minX) {
                minX = word.getXDirAdj();
            }
            if (word.getMaxXDirAdj() > maxX) {
                maxX = word.getMaxXDirAdj();
            }
            if (word.getYDirAdj() < minY) {
                minY = word.getYDirAdj();
            }
            if (word.getMaxYDirAdj() > maxY) {
                maxY = word.getMaxYDirAdj();
            }
        }

        textPageBlocks.add(new TextPageBlock(chunkWords));

        return new ClassificationPage(textPageBlocks);
    }


    private static boolean isFontChange(Word word, Word prev) {

        return word.getFontStyle().contains("bold") && !prev.getFontStyle().contains("bold")
               || prev.getFontStyle().contains("bold") && !word.getFontStyle().contains("bold")
               || Math.abs(prev.getFontSize() - word.getFontSize()) >= FONT_SIZE_CHANGE_RATIO * Math.min(prev.getFontSize(), word.getFontSize())
               || Math.abs(word.getTextHeight() - prev.getTextHeight()) >= FONT_SIZE_CHANGE_RATIO * Math.min(prev.getTextHeight(), word.getTextHeight());
    }


    public void mergeblocks(ClassificationPage page, CleanRulings usedRulings, float xThreshold, float yThreshold) {

        var blocks = page.getTextBlocks();
        ListIterator<AbstractPageBlock> itty = blocks.listIterator();
        while (itty.hasNext()) {
            AbstractPageBlock block = itty.next();
            if (block == null) {
                continue;
            }
            if (block instanceof TablePageBlock) {
                continue;
            }

            TextPageBlock current = (TextPageBlock) block;

            for (int i = 0; i < blocks.size(); i++) {

                AbstractPageBlock abstractPageBlock = blocks.get(i);
                if (abstractPageBlock == null) {
                    continue;
                }
                if (abstractPageBlock == current) {
                    continue;
                }
                if (abstractPageBlock instanceof TablePageBlock) {
                    continue;
                }

                if (isHeadlineFromOutline(current) || isHeadlineFromOutline(abstractPageBlock)) {
                    continue;
                }

                TextPageBlock inner = (TextPageBlock) abstractPageBlock;

                if (usedRulings.lineBetween(current, blocks.get(i))) {
                    continue;
                }

                if (current.getDir() == inner.getDir() && current.intersects(inner, yThreshold, xThreshold) && (current.getClassification() == null || current.getClassification()
                        .equals(inner.getClassification()))) {

                    boolean toDuplicate = current.isToDuplicate() || inner.isToDuplicate();
                    current.addAll(inner.getWords());
                    current = buildTextBlock(current.getWords(), 0);
                    current.setClassification(inner.getClassification());
                    current.setToDuplicate(toDuplicate);
                    blocks.set(i, null);
                    itty.set(current);
                }
            }
        }
        var blocksIterator = blocks.iterator();
        while (blocksIterator.hasNext()) {
            if (blocksIterator.next() == null) {
                blocksIterator.remove();
            }
        }
    }


    private boolean isHeadlineFromOutline(AbstractPageBlock abstractPageBlock) {

        return abstractPageBlock.getEngines().contains(LayoutEngine.OUTLINE) && abstractPageBlock.getClassification() != null && abstractPageBlock.getClassification().isHeadline();
    }


    public static TextPageBlock buildTextBlock(List<Word> wordBlockList, int indexOnPage) {

        return new TextPageBlock(wordBlockList);
    }

}

