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

import java.util.Comparator;
import java.util.List;

import org.springframework.stereotype.Service;

import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Point;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
import com.knecon.fforesight.service.layoutparser.internal.api.queue.LayoutParsingType;
import com.knecon.fforesight.service.layoutparser.processor.model.AbstractPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.ClassificationDocument;
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.table.Cell;
import com.knecon.fforesight.service.layoutparser.processor.model.table.Ruling;
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.utils.MarkedContentUtils;
import com.knecon.fforesight.service.layoutparser.processor.utils.PositionUtils;

@Service
public class BodyTextFrameService {

    private static final float RULING_HEIGHT_THRESHOLD = 0.15f; // multiplied with page height. Header/Footer Rulings must be within that border of the page.
    private static final float RULING_WIDTH_THRESHOLD = 0.75f; // multiplied with page width. Header/Footer Rulings must be at least that wide.


    public void setBodyTextFrames(ClassificationDocument classificationDocument, LayoutParsingType layoutParsingType) {

        Rectangle bodyTextFrame = calculateBodyTextFrame(classificationDocument.getPages(), classificationDocument.getFontSizeCounter(), false, layoutParsingType);
        Rectangle landscapeBodyTextFrame = calculateBodyTextFrame(classificationDocument.getPages(), classificationDocument.getFontSizeCounter(), true, layoutParsingType);
        for (ClassificationPage page : classificationDocument.getPages()) {
            var updatedBodyTextFrame = getBodyTextFrameFromRulings(page, bodyTextFrame, landscapeBodyTextFrame);
            setBodyTextFrameAdjustedToPage(page, bodyTextFrame, landscapeBodyTextFrame);
            classificationDocument.getVisualizations().addMainBodyVisualization(page.isLandscape() ? landscapeBodyTextFrame : bodyTextFrame, page.getPageNumber());
        }
    }


    private Rectangle getBodyTextFrameFromRulings(ClassificationPage page, Rectangle bodyTextFrame, Rectangle landscapeBodyTextFrame) {

        List<Ruling> potentialFooterRulings = getPotentialFooterRulings(page);
        List<Ruling> potentialHeaderRulings = getPotentialHeaderRulings(page);
        var x = bodyTextFrame.getTopLeft().getX();
        var y = bodyTextFrame.getTopLeft().getY();
        var w = bodyTextFrame.getWidth();
        var h = bodyTextFrame.getHeight();
        if (!potentialFooterRulings.isEmpty()) {
            h = y + h - potentialFooterRulings.get(0).getTop();
            y = potentialFooterRulings.get(0).getTop();
        }
        if (!potentialHeaderRulings.isEmpty()) {
            h = potentialHeaderRulings.get(0).getBottom() - bodyTextFrame.getTopLeft().getY();
        }
        return new Rectangle(new Point(x, y), w, h, page.getPageNumber());
    }


    private List<Ruling> getPotentialFooterRulings(ClassificationPage page) {

        return page.getCleanRulings().getHorizontals()
                .stream()
                .filter(ruling -> ruling.getClassification().equals(Ruling.Classification.OTHER))
                .filter(ruling -> ruling.getY1() < page.getPageHeight() * RULING_HEIGHT_THRESHOLD)
                .filter(ruling -> ruling.getWidth() > RULING_WIDTH_THRESHOLD * page.getPageWidth())
                .sorted(Comparator.comparingDouble(Ruling::getTop))
                .peek(ruling -> ruling.setClassification(Ruling.Classification.FOOTER_SEPARATOR))
                .toList();
    }


    private List<Ruling> getPotentialHeaderRulings(ClassificationPage page) {

        return page.getCleanRulings().getHorizontals()
                .stream()
                .filter(ruling -> ruling.getClassification().equals(Ruling.Classification.OTHER))
                .filter(ruling -> ruling.getY1() > page.getPageHeight() * (1 - RULING_HEIGHT_THRESHOLD))
                .filter(ruling -> ruling.getWidth() > RULING_WIDTH_THRESHOLD * page.getPageWidth())
                .sorted(Comparator.comparingDouble(Ruling::getBottom).reversed())
                .peek(ruling -> ruling.setClassification(Ruling.Classification.HEADER_SEPARATOR))
                .toList();
    }


    /**
     * Adjusts and sets the body text frame to a page.
     * Note: This needs to use Pdf Coordinate System where {0,0} rotated with the page rotation.
     * 0 -> LowerLeft
     * 90 -> UpperLeft
     * 180 -> UpperRight
     * 270 -> LowerRight
     * The aspect ratio of the page is also regarded.
     *
     * @param page                   The page
     * @param bodyTextFrame          frame that contains the main text on portrait pages
     * @param landscapeBodyTextFrame frame that contains the main text on landscape pages
     */
    private void setBodyTextFrameAdjustedToPage(ClassificationPage page, Rectangle bodyTextFrame, Rectangle landscapeBodyTextFrame) {

        Rectangle textFrame = page.isLandscape() ? landscapeBodyTextFrame : bodyTextFrame;

        if (page.getPageWidth() > page.getPageHeight() && page.getRotation() == 270) {
            textFrame = new Rectangle(new Point(textFrame.getTopLeft().getY(), page.getPageHeight() - textFrame.getTopLeft().getX() - textFrame.getWidth()),
                                      textFrame.getHeight(),
                                      textFrame.getWidth(),
                                      0);
        } else if (page.getPageWidth() > page.getPageHeight() && page.getRotation() != 0) {
            textFrame = new Rectangle(new Point(textFrame.getTopLeft().getY(), textFrame.getTopLeft().getX()), textFrame.getHeight(), textFrame.getWidth(), page.getPageNumber());
        } else if (page.getRotation() == 180) {
            textFrame = new Rectangle(new Point(textFrame.getTopLeft().getX(), page.getPageHeight() - textFrame.getTopLeft().getY() - textFrame.getHeight()),
                                      textFrame.getWidth(),
                                      textFrame.getHeight(),
                                      0);
        }
        page.setBodyTextFrame(textFrame);
    }


    /**
     * Calculates the frame that contains the main text, text outside the frame will be e.g. headers or footers.
     * Note: This needs to use Pdf Coordinate System where {0,0} rotated with the page rotation.
     * 0 -> LowerLeft
     * 90 -> UpperLeft
     * 180 -> UpperRight
     * 270 -> LowerRight
     * The aspect ratio of the page is also regarded.
     *
     * @param pages                   List of all pages
     * @param documentFontSizeCounter Statistics of the document
     * @param landscape               Calculate for landscape or portrait
     * @return Rectangle of the text frame
     */
    protected Rectangle calculateBodyTextFrame(List<ClassificationPage> pages,
                                               FloatFrequencyCounter documentFontSizeCounter,
                                               boolean landscape,
                                               LayoutParsingType layoutParsingType) {

        float approximateHeaderLineCount = 2.9f;

        BodyTextFrameExpansionsRectangle expansionsRectangle = new BodyTextFrameExpansionsRectangle();

        for (ClassificationPage page : pages) {

            if (page.getTextBlocks().isEmpty() || landscape != page.isLandscape()) {
                continue;
            }

            for (AbstractPageBlock container : page.getTextBlocks()) {

                if (container instanceof TextPageBlock) {
                    TextPageBlock textBlock = (TextPageBlock) container;
                    if (textBlock.getMostPopularWordFont() == null || textBlock.getMostPopularWordStyle() == null) {
                        continue;
                    }

                    if (MarkedContentUtils.intersects(textBlock, page.getMarkedContentBboxPerType(), MarkedContentUtils.HEADER) || MarkedContentUtils.intersects(textBlock,
                                                                                                                                                                 page.getMarkedContentBboxPerType(),
                                                                                                                                                                 MarkedContentUtils.FOOTER)) {
                        continue;
                    }

                    float approxLineCount = PositionUtils.getApproxLineCount(textBlock);
                    if (layoutParsingType.equals(LayoutParsingType.DOCUMINE_OLD)
                        && approxLineCount < approximateHeaderLineCount
                        && textBlock.getMaxY() >= page.getPageHeight() - (page.getPageHeight() / 10)
                        || !layoutParsingType.equals(LayoutParsingType.DOCUMINE_OLD) && approxLineCount < approximateHeaderLineCount) {
                        continue;
                    }

                    if (documentFontSizeCounter.getMostPopular() != null && textBlock.getMostPopularWordFontSize() >= documentFontSizeCounter.getMostPopular()) {

                        expandRectangle(textBlock, page, expansionsRectangle);
                    }
                }

                if (container instanceof TablePageBlock) {
                    TablePageBlock table = (TablePageBlock) container;
                    for (List<Cell> row : table.getRows()) {
                        for (Cell cell : row) {

                            if (cell == null || cell.getTextBlocks() == null) {
                                continue;
                            }
                            for (TextPageBlock textBlock : cell.getTextBlocks()) {
                                expandRectangle(textBlock, page, expansionsRectangle);
                            }
                        }
                    }
                }
            }
        }
        return new Rectangle(new Point(expansionsRectangle.minX, expansionsRectangle.minY),
                             expansionsRectangle.maxX - expansionsRectangle.minX,
                             expansionsRectangle.maxY - expansionsRectangle.minY,
                             0);
    }


    private void expandRectangle(TextPageBlock textBlock, ClassificationPage page, BodyTextFrameExpansionsRectangle expansionsRectangle) {

        if (page.getPageWidth() > page.getPageHeight() && page.getRotation() != 0) {
            if (textBlock.getPdfMinY() < expansionsRectangle.minX) {
                expansionsRectangle.minX = textBlock.getPdfMinY();
            }
            if (textBlock.getPdfMaxY() > expansionsRectangle.maxX) {
                expansionsRectangle.maxX = textBlock.getPdfMaxY();
            }
            if (textBlock.getPdfMinX() < expansionsRectangle.minY) {
                expansionsRectangle.minY = textBlock.getPdfMinX();
            }
            if (textBlock.getPdfMaxX() > expansionsRectangle.maxY) {
                expansionsRectangle.maxY = textBlock.getPdfMaxX();
            }
        } else {
            if (textBlock.getPdfMinX() < expansionsRectangle.minX) {
                expansionsRectangle.minX = textBlock.getPdfMinX();
            }
            if (textBlock.getPdfMaxX() > expansionsRectangle.maxX) {
                expansionsRectangle.maxX = textBlock.getPdfMaxX();
            }
            if (textBlock.getPdfMinY() < expansionsRectangle.minY) {
                expansionsRectangle.minY = textBlock.getPdfMinY();
            }
            if (textBlock.getPdfMaxY() > expansionsRectangle.maxY) {
                expansionsRectangle.maxY = textBlock.getPdfMaxY();
            }
        }
    }


    private class BodyTextFrameExpansionsRectangle {

        float minX = 10000;
        float maxX = -100;
        float minY = 10000;
        float maxY = -100;

    }

}