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

import java.awt.Color;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.text.BreakIterator;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.pdfbox.pdmodel.documentinterchange.markedcontent.PDMarkedContent;

import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
import com.knecon.fforesight.service.layoutparser.processor.docstrum.model.BoundingBox;
import com.knecon.fforesight.service.layoutparser.processor.docstrum.model.Line;
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.graph.TextRange;
import com.knecon.fforesight.service.layoutparser.processor.model.graph.textblock.TextBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.outline.OutlineObject;
import com.knecon.fforesight.service.layoutparser.processor.model.table.CleanRulings;
import com.knecon.fforesight.service.layoutparser.processor.model.table.Ruling;
import com.knecon.fforesight.service.layoutparser.processor.model.text.ListIdentifier;
import com.knecon.fforesight.service.layoutparser.processor.model.text.Word;
import com.knecon.fforesight.service.layoutparser.processor.utils.CoordinateTransforms;
import com.knecon.fforesight.service.layoutparser.processor.utils.MarkedContentUtils;
import com.knecon.fforesight.service.layoutparser.processor.utils.PageInformation;
import com.knecon.fforesight.service.layoutparser.processor.utils.RectangleTransformations;
import com.knecon.fforesight.service.viewerdoc.layers.LayoutDebugLayerConfig;
import com.knecon.fforesight.service.viewerdoc.model.ColoredLine;
import com.knecon.fforesight.service.viewerdoc.model.ColoredRectangle;
import com.knecon.fforesight.service.viewerdoc.model.FilledRectangle;
import com.knecon.fforesight.service.viewerdoc.model.PlacedText;
import com.knecon.fforesight.service.viewerdoc.model.VisualizationsOnPage;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.FieldDefaults;

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class LayoutDebugLayer extends LayoutDebugLayerConfig {

    boolean active;

    Map<Integer, AtomicInteger> outlineObjectsWithoutPointsPerPage = new HashMap<>();


    public void addTextVisualizations(List<Word> words, int pageNumber) {

        if (!active) {
            return;
        }
        VisualizationsOnPage visualizationsOnPage = getOrCreateVisualizationsOnPage(pageNumber, this.words);
        visualizationsOnPage.getColoredRectangles()
                .addAll(words.stream()
                                .map(BoundingBox::getBBoxPdf)
                                .map(rect -> new ColoredRectangle(rect, WORDS_COLOR, 1))
                                .toList());
    }


    public void addCleanRulingVisualization(CleanRulings cleanRulings, int pageNumber) {

        if (!active) {
            return;
        }
        VisualizationsOnPage visualizationsOnPage = getOrCreateVisualizationsOnPage(pageNumber, this.clean_rulings);
        visualizationsOnPage.getColoredLines()
                .addAll(cleanRulings.buildAll()
                                .stream()
                                .map(ruling -> new ColoredLine(ruling, decideOnRulingColor(ruling), LINE_WIDTH))
                                .toList());
    }


    public void addRulingVisualization(List<Ruling> rulings, int pageNumber) {

        if (!active) {
            return;
        }
        VisualizationsOnPage visualizationsOnPage = getOrCreateVisualizationsOnPage(pageNumber, this.rulings);
        visualizationsOnPage.getColoredLines()
                .addAll(rulings.stream()
                                .map(ruling -> new ColoredLine(ruling, decideOnRulingColor(ruling), LINE_WIDTH))
                                .toList());
    }


    public void addSentenceVisualization(TextBlock textBlock) {

        if (!active) {
            return;
        }
        AtomicInteger rotatingColorIdx = new AtomicInteger(0);
        String text = textBlock.getSearchText();
        BreakIterator sentenceIterator = BreakIterator.getSentenceInstance(Locale.ENGLISH);
        sentenceIterator.setText(text);
        int lastIdx = 0;
        while (sentenceIterator.next() != BreakIterator.DONE) {
            TextRange sentenceRange = new TextRange(lastIdx + textBlock.getTextRange().start(), sentenceIterator.current() + textBlock.getTextRange().start());
            lastIdx = sentenceIterator.current();
            Color color = getRotatingColor(rotatingColorIdx);
            textBlock.getPositionsPerPage(sentenceRange)
                    .forEach((page, bboxes) -> getOrCreateVisualizationsOnPage(page.getNumber(), this.sentences).getColoredRectangles()
                            .addAll(bboxes.stream()
                                            .map(bbox -> new ColoredRectangle(bbox, color, 1))
                                            .toList()));
        }
    }


    private Color decideOnRulingColor(Ruling ruling) {

        return switch (ruling.getClassification()) {
            case TABLE_LINE -> TABLE_RULINGS_COLOR;
            case HEADER_SEPARATOR -> HEADER_RULING_COLOR;
            case FOOTER_SEPARATOR -> FOOTER_RULING_COLOR;
            case UNDERLINE -> UNDERLINE_RULING_COLOR;
            case STRIKETROUGH -> STRIKETROUGH_RULING_COLOR;
            default -> RULINGS_COLOR;
        };
    }


    public void addCellVisualizations(List<? extends BoundingBox> cells, int pageNumber) {

        if (!active) {
            return;
        }
        VisualizationsOnPage visualizationsOnPage = getOrCreateVisualizationsOnPage(pageNumber, this.cells);
        visualizationsOnPage.getColoredRectangles()
                .addAll(cells.stream()
                                .map(cell -> new ColoredRectangle(cell.getBBoxPdf(), CELLS_COLOR, 1))
                                .toList());
    }


    public void addZoneVisualizations(List<Zone> zones, int page) {

        if (!active) {
            return;
        }

        VisualizationsOnPage visualizationsOnPage = getOrCreateVisualizationsOnPage(page, this.zones);
        visualizationsOnPage.getColoredRectangles()
                .addAll(zones.stream()
                                .map(BoundingBox::getBBoxPdf)
                                .map(zone -> new ColoredRectangle(zone, ZONES_COLOR, 1))
                                .toList());

    }


    public void addLineVisualizationsFromZones(List<Zone> zones, int page) {

        addLineVisualizations(zones.stream()
                                      .map(Zone::getLines)
                                      .flatMap(Collection::stream)
                                      .toList(), page);

    }


    public void addLineVisualizations(List<Line> lines, int page) {

        if (!active) {
            return;
        }
        VisualizationsOnPage visualizationsOnPage = getOrCreateVisualizationsOnPage(page, this.lines);
        visualizationsOnPage.getColoredRectangles()
                .addAll(lines.stream()
                                .map(BoundingBox::getBBoxPdf)
                                .map(line -> new ColoredRectangle(line, LINES_COLOR, LINE_WIDTH))
                                .toList());
    }


    public void addLineVisualizationsFromNestedTextPosition(Collection<Set<Word>> lines, int pageNumber) {

        if (!active) {
            return;
        }
        VisualizationsOnPage visualizationsOnPage = getOrCreateVisualizationsOnPage(pageNumber, this.lines);
        visualizationsOnPage.getColoredRectangles()
                .addAll(lines.stream()
                                .map(line -> line.stream()
                                        .map(BoundingBox::getBBoxPdf)
                                        .collect(RectangleTransformations.collectBBox()))
                                .map(line -> new ColoredRectangle(line, LINES_COLOR, LINE_WIDTH))
                                .toList());

    }


    public void addTextBlockVisualizations(List<AbstractPageBlock> textPageBlocks, int page) {

        if (!active) {
            return;
        }
        VisualizationsOnPage visualizationsOnPage = getOrCreateVisualizationsOnPage(page, zones);
        visualizationsOnPage.getColoredRectangles()
                .addAll(textPageBlocks.stream()
                                .map(rect -> new ColoredRectangle(rect.getBBoxPdf(), ZONES_COLOR, 1))
                                .toList());
    }


    public void addMainBodyVisualization(Rectangle rectangle, int pageNumber) {

        if (!active) {
            return;
        }
        VisualizationsOnPage visualizationsOnPage = getOrCreateVisualizationsOnPage(pageNumber, mainBody);
        visualizationsOnPage.getColoredRectangles()
                .add(new ColoredRectangle(new Rectangle2D.Double(rectangle.getTopLeft().getX(), rectangle.getTopLeft().getY(), rectangle.getWidth(), rectangle.getHeight()),
                                          MAIN_BODY_COLOR,
                                          1));
    }


    public void addMarkedContentVisualizations(List<PDMarkedContent> markedContents, int pageNumber) {

        if (!active) {
            return;
        }

        VisualizationsOnPage visualizationsOnPage = getOrCreateVisualizationsOnPage(pageNumber, markedContent);

        List<MarkedContentUtils.MarkedContentPosition> markedContentBBoxMapBySubType = MarkedContentUtils.getMarkedContentPositions(markedContents);
        AtomicInteger count = new AtomicInteger();
        markedContentBBoxMapBySubType.forEach(markedContentPosition -> {
                                                  var bbox = markedContentPosition.textPositions()
                                                          .stream()
                                                          .collect(RectangleTransformations.collectBBox());
                                                  String type = markedContentPosition.formattedType() + " " + count.getAndIncrement();

                                                  float translationAmount = ((FONT.getStringWidth(type) / 100) + 6);
                                                  // Pushes the string to the left of the box: calculate string width, divide by font units (1000), multiply with font size (10), add small offset (6).

                                                  visualizationsOnPage.getPlacedTexts()
                                                          .add(PlacedText.textFacingUp(type, new Point2D.Double(bbox.getX() - translationAmount, bbox.getY() + bbox.getHeight()), 10, Color.BLACK, FONT));

                                                  visualizationsOnPage.getColoredRectangles().add(new ColoredRectangle(bbox, MARKED_CONTENT_COLOR, 1));
                                              }

        );

    }


    public void addCharactersWithNeighbours(List<Zone> zones, int page) {

        if (!active) {
            return;
        }

        VisualizationsOnPage characterVisualizations = getOrCreateVisualizationsOnPage(page, characters);
        VisualizationsOnPage neighbourVisualizations = getOrCreateVisualizationsOnPage(page, neighbours);

        AtomicInteger index = new AtomicInteger(0);
        zones.forEach(zone -> zone.getLines()
                .stream()
                .map(Line::getCharacters)
                .flatMap(Collection::stream)
                .forEach(character -> {
                    Color color = getRotatingColor(index);
                    Rectangle2D charBBox = character.getTextPosition().getBBoxPdf();
                    characterVisualizations.getColoredRectangles().add(new ColoredRectangle(charBBox, color, 1));
                    character.getNeighbors()
                            .forEach(neighbor -> {
                                Rectangle2D neighborBBox = neighbor.getCharacter().getTextPosition().getBBoxPdf();
                                Line2D line = new Line2D.Double(new Point2D.Double(charBBox.getCenterX(), charBBox.getCenterY()),
                                                                new Point2D.Double(neighborBBox.getCenterX(), neighborBBox.getCenterY()));
                                neighbourVisualizations.getColoredLines().add(new ColoredLine(line, color, 1));
                            });
                }));

    }


    public void addTocPages(List<Word> numbers, int page) {

        if (!active) {
            return;
        }

        VisualizationsOnPage visualizationsOnPage = getOrCreateVisualizationsOnPage(page, this.tocPages);
        visualizationsOnPage.getColoredRectangles()
                .addAll(numbers.stream()
                                .map(BoundingBox::getBBoxPdf)
                                .map(line -> new ColoredRectangle(line, LINES_COLOR, LINE_WIDTH))
                                .toList());
        visualizationsOnPage.getColoredRectangles()
                .add(new ColoredRectangle(numbers.stream()
                                                  .map(BoundingBox::getBBoxPdf)
                                                  .collect(RectangleTransformations.collectBBox()), LINES_COLOR, LINE_WIDTH));
    }


    private static Color getRotatingColor(AtomicInteger index) {

        return ROTATING_CHARACTER_COLOR.get(index.getAndIncrement() % ROTATING_CHARACTER_COLOR.size());
    }


    public void addOutlineObjects(List<OutlineObject> outlineObjects, PageInformation pageInformation) {

        if (!active) {
            return;
        }

        for (OutlineObject outlineObject : outlineObjects) {
            addOutlineObject(outlineObject, pageInformation);
        }
    }


    private void addOutlineObject(OutlineObject outlineObject, PageInformation pageInformation) {

        int rectSize = 5;

        Point2D point2D;
        if (outlineObject.getPoint().isPresent()) {
            point2D = outlineObject.getPoint().get();
        } else {
            int numberOfOutlineObjectsWithoutPoints = outlineObjectsWithoutPointsPerPage.computeIfAbsent(outlineObject.getPageNumber(), a -> new AtomicInteger(0))
                    .getAndIncrement();
            point2D = new Point2D.Double(10, 10 + numberOfOutlineObjectsWithoutPoints * (10 + rectSize * 2));
        }

        Point2D textPoint = new Point2D.Double(point2D.getX() + 2 * rectSize, point2D.getY() + rectSize);
        AffineTransform pageToUserSpaceTransform = CoordinateTransforms.calculatePageCoordsToInitialUserSpaceCoords(pageInformation);
        pageToUserSpaceTransform.transform(point2D, point2D);
        pageToUserSpaceTransform.transform(textPoint, textPoint);

        VisualizationsOnPage visualizationsOnPage = getOrCreateVisualizationsOnPage(outlineObject.getPageNumber(), outlineObjects);
        visualizationsOnPage.getFilledRectangles()
                .add(new FilledRectangle(new Rectangle2D.Double(point2D.getX() - rectSize, point2D.getY() - rectSize, rectSize * 2, rectSize * 2), OUTLINE_OBJECT_COLOR, 1));
        visualizationsOnPage.getPlacedTexts().add(PlacedText.textFacingUp(outlineObject.getTitle(), textPoint, 10, outlineObject.isFound() ? Color.BLACK : Color.RED, FONT));
    }


    public void addListIdentifiers(List<ListIdentifier> listIdentifiers) {

        for (ListIdentifier listIdentifier : listIdentifiers) {
            getOrCreateVisualizationsOnPage(listIdentifier.getPage(), this.listIdentifiers).getColoredRectangles()
                    .add(new ColoredRectangle(listIdentifier.getWord().getBBoxPdf(), WORDS_COLOR, LINE_WIDTH));
        }
    }

}
