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

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

import org.springframework.stereotype.Service;

import com.knecon.fforesight.service.layoutparser.internal.api.queue.LayoutParsingType;
import com.knecon.fforesight.service.layoutparser.processor.docstrum.DocstrumSegmentationService;
import com.knecon.fforesight.service.layoutparser.processor.docstrum.model.Zone;
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.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;
import com.knecon.fforesight.service.layoutparser.processor.visualization.LayoutDebugLayer;

import lombok.RequiredArgsConstructor;

@SuppressWarnings("all")
@Service
@RequiredArgsConstructor
public class DocstrumBlockificationService {

    public static final float Y_THRESHOLD = 5f;
    private final DocstrumSegmentationService docstrumSegmentationService;

    static final float THRESHOLD = 1f;


    public ClassificationPage blockify(List<Word> textPositions,
                                       CleanRulings rulings,
                                       boolean xyOrder,
                                       LayoutDebugLayer visualizations,
                                       LayoutParsingType layoutParsingType) {

        CleanRulings usedRulings = rulings.withoutTextRulings();

        List<Zone> zones = docstrumSegmentationService.segmentPage(textPositions, xyOrder, usedRulings, visualizations);

        if (!textPositions.isEmpty()) {
            visualizations.addZoneVisualizations(zones, textPositions.get(0).getPage());
            visualizations.addLineVisualizationsFromZones(zones, textPositions.get(0).getPage());
            visualizations.addCharactersWithNeighbours(zones, textPositions.get(0).getPage());
        }

        var pageBlocks = toAbstractPageBlocks(zones);

        var classificationPage = new ClassificationPage(pageBlocks);
        classificationPage.setCleanRulings(rulings);

        mergeIntersectingBlocks(classificationPage, usedRulings, 0, 0);

        if (layoutParsingType == LayoutParsingType.DOCUMINE
            || layoutParsingType == LayoutParsingType.REDACT_MANAGER
            || layoutParsingType == LayoutParsingType.REDACT_MANAGER_WITHOUT_DUPLICATE_PARAGRAPH) {
            combineBlocks(classificationPage, layoutParsingType);
        }

        if (layoutParsingType == LayoutParsingType.CLARIFYND) {
            mergeIntersectingBlocks(classificationPage, usedRulings, 0, 0);
        }

        return classificationPage;
    }


    private List<AbstractPageBlock> toAbstractPageBlocks(List<Zone> zones) {

        List<AbstractPageBlock> abstractPageBlocks = new ArrayList<>();
        zones.forEach(zone -> {

            List<Word> words = new ArrayList<>();
            zone.getLines()
                    .forEach(line -> {
                        line.getWords()
                                .forEach(word -> {
                                    words.add(new Word(word.getCharacters(), word.getPage()));
                                });
                    });

            abstractPageBlocks.add(buildTextBlock(words, 0));
        });

        return abstractPageBlocks;
    }


    public void combineBlocks(ClassificationPage page, LayoutParsingType layoutParsingType) {

        TextPageBlock previous = new TextPageBlock();
        ListIterator<AbstractPageBlock> itty = page.getTextBlocks().listIterator();
        CleanRulings usedRulings = page.getCleanRulings().withoutTextRulings();
        while (itty.hasNext()) {

            AbstractPageBlock block = itty.next();
            if (block instanceof TablePageBlock) {
                previous = new TextPageBlock();
                continue;
            }
            TextPageBlock current = (TextPageBlock) block;

            if (previous != null && !previous.getWords().isEmpty()) {

                if (current.getDir() != previous.getDir() || usedRulings.lineBetween(current, previous)) {
                    previous = current;
                    continue;
                }

                if (current.isHeadline() || previous.isHeadline()) {
                    if (intersectsYWithPreviousHavingMaxOneLine(previous, current, page)) {
                        previous = combineBlocksAndResetIterator(previous, current, itty, false);
                    } else {
                        previous = current;
                    }

                    continue;
                }

                if (areTheOnlyTwoBlocksOnHeightsWithBothMoreThanTwoLines(previous, current, page)) {
//                    previous = combineBlocksAndResetIterator(previous, current, itty, true);
                    previous = combineBlocksAndResetIterator(previous, current, itty, layoutParsingType != LayoutParsingType.REDACT_MANAGER_WITHOUT_DUPLICATE_PARAGRAPH);
                    continue;
                }

                if (previous.intersects(current)) {
                    previous = combineBlocksAndResetIterator(previous, current, itty, previous.isToDuplicate());
                    continue;
                }

                if (isSameTopOrBottomWithPreviousHavingMaxTwoLinesAndCurrentThanOneAndMax4OtherBlocksOnHeight(previous, current, page)) {
                    previous = combineBlocksAndResetIterator(previous, current, itty, previous.isToDuplicate());
                    continue;
                }

                if (isOnlyIntersectingYAndOnelineOrPrevoiusTwoLines(previous, current, page)) {
                    previous = combineBlocksAndResetIterator(previous, current, itty, previous.isToDuplicate());
                    continue;
                }

            }
            previous = current;
        }

        mergeIntersectingBlocks(page, usedRulings, 0, Y_THRESHOLD);
    }


    private boolean isOnlyIntersectingYAndOnelineOrPrevoiusTwoLines(TextPageBlock previous, TextPageBlock current, ClassificationPage page) {

        return current.intersectsY(previous) //
               && (previous.getNumberOfLines() == 1 && current.getNumberOfLines() >= 1 || previous.getNumberOfLines() == 2 && current.getNumberOfLines() == 1) //
               && numberOfYIntersectionsOfSmallerBlocksWithOtherBlocks(current, previous, page.getTextBlocks()) <= 0;
    }


    private boolean isSameTopOrBottomWithPreviousHavingMaxTwoLinesAndCurrentThanOneAndMax4OtherBlocksOnHeight(TextPageBlock previous,
                                                                                                              TextPageBlock current,
                                                                                                              ClassificationPage page) {

        return (Math.abs(previous.getMaxY() - current.getMaxY()) < THRESHOLD || Math.abs(previous.getMinY() - current.getMinY()) < THRESHOLD) //
               && (previous.getNumberOfLines() == 1 && current.getNumberOfLines() >= 1 || previous.getNumberOfLines() >= 2 && current.getNumberOfLines() == 1) //
               && !hasBetween(current, previous, page.getTextBlocks()) && numberOfYIntersectionsOfSmallerBlocksWithOtherBlocks(current, previous, page.getTextBlocks()) <= 4;
    }


    private boolean intersectsYWithPreviousHavingMaxOneLine(TextPageBlock previous, TextPageBlock current, ClassificationPage page) {

        return previous.intersectsY(current) && (previous.getNumberOfLines() == 1 && current.getNumberOfLines() >= 1);
    }


    private boolean areTheOnlyTwoBlocksOnHeightsWithBothMoreThanTwoLines(TextPageBlock previous, TextPageBlock current, ClassificationPage page) {

        return previous.getNumberOfLines() >= 2 && current.getNumberOfLines() >= 2 //
               && previous.intersectsY(current) //
               && numberOfYIntersectionsOfSmallerBlocksWithOtherBlocks(current, previous, page.getTextBlocks()) == 0;
    }


    private TextPageBlock combineBlocksAndResetIterator(TextPageBlock previous, TextPageBlock current, ListIterator<AbstractPageBlock> itty, boolean toDuplicate) {

        previous.addAll(current.getWords());
        previous = buildTextBlock(previous.getWords(), 0);
        previous.setToDuplicate(toDuplicate);
        if (current.getClassification() != null && previous.getClassification() == null) {
            previous.setClassification(current.getClassification());
        }
        itty.remove();
        itty.previous();
        itty.set(previous);
        itty.next();
        return previous;
    }


    private boolean hasBetween(TextPageBlock block, TextPageBlock other, List<AbstractPageBlock> allBlocks) {

        for (AbstractPageBlock current : allBlocks) {

            if (current == other || current == block) {
                continue;
            }

            if (other.intersectsY(current) && other.getMaxX() <= current.getMinX() && current.getMaxX() <= block.getMinX()) {
                return true;
            }
        }

        return false;
    }


    private int numberOfYIntersectionsOfSmallerBlocksWithOtherBlocks(TextPageBlock block, TextPageBlock other, List<AbstractPageBlock> allBlocks) {

        double minY = Math.min(block.getMinY(), other.getMinY());
        double maxY = Math.min(block.getMaxY(), other.getMaxY());

        int numberOfYIntersections = 0;
        for (AbstractPageBlock current : allBlocks) {

            if (current == other || current == block) {
                continue;
            }

            if (minY <= current.getMaxY() && maxY >= current.getMinY()) {
                numberOfYIntersections++;
            }
        }

        return numberOfYIntersections;
    }


    public void mergeIntersectingBlocks(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;
            }

            if (block.getClassification() != null && block.getClassification().isHeadline()) {
                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 (abstractPageBlock.getClassification() != null && abstractPageBlock.getClassification().isHeadline()) {
                    continue;
                }

                TextPageBlock inner = (TextPageBlock) abstractPageBlock;

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

//                if (!current.getMostPopularWordStyle().equals(inner.getMostPopularWordStyle())) {
//                    continue;
//                }

                if (current.getDir() == inner.getDir() && current.intersects(inner, yThreshold, xThreshold)) {

                    boolean toDuplicate = current.isToDuplicate() || inner.isToDuplicate();
                    current.addAll(inner.getWords());
                    current = buildTextBlock(current.getWords(), 0);

                    current.setToDuplicate(toDuplicate);
                    blocks.set(i, null);
                    itty.set(current);
                }
            }
        }
        var blocksIterator = blocks.iterator();
        while (blocksIterator.hasNext()) {
            if (blocksIterator.next() == null) {
                blocksIterator.remove();
            }
        }
    }


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

        return new TextPageBlock(wordBlockList);
    }

}
