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

import java.awt.Color;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

import org.apache.pdfbox.pdmodel.PDPage;
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.table.CleanRulings;
import com.knecon.fforesight.service.layoutparser.processor.model.table.Ruling;
import com.knecon.fforesight.service.layoutparser.processor.model.text.RedTextPosition;
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.utils.MarkedContentUtils;
import com.knecon.fforesight.service.layoutparser.processor.utils.RectangleTransformations;
import com.knecon.fforesight.service.viewerdoc.ContentStreams;
import com.knecon.fforesight.service.viewerdoc.model.ColoredLine;
import com.knecon.fforesight.service.viewerdoc.model.ColoredRectangle;
import com.knecon.fforesight.service.viewerdoc.model.PlacedText;
import com.knecon.fforesight.service.viewerdoc.model.Standard14EmbeddableFont;
import com.knecon.fforesight.service.viewerdoc.model.Visualizations;
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;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class LayoutparsingVisualizations {

    static final Standard14EmbeddableFont FONT = Standard14EmbeddableFont.helvetica();

    static final Color WORDS_COLOR = new Color(68, 84, 147);
    static final Color LINES_COLOR = new Color(152, 45, 179);
    static final Color ZONES_COLOR = new Color(131, 38, 38);

    static final Color RULINGS_COLOR = new Color(21, 221, 174);
    static final Color TABLE_RULINGS_COLOR = new Color(255, 175, 175);
    static final Color HEADER_RULING_COLOR = new Color(171, 131, 6);
    static final Color FOOTER_RULING_COLOR = new Color(106, 82, 2);
    static final Color UNDERLINE_RULING_COLOR = new Color(6, 39, 171);
    static final Color STRIKETROUGH_RULING_COLOR = new Color(171, 6, 6);

    static final Color CELLS_COLOR = new Color(31, 214, 27);

    static final Color MAIN_BODY_COLOR = new Color(171, 131, 6);
    static final Color MARKED_CONTENT_COLOR = new Color(171, 131, 6);

    static final List<Color> ROTATING_CHARACTER_COLOR = List.of(new Color(255, 87, 51),
                                                                new Color(255, 195, 0),
                                                                new Color(76, 175, 80),
                                                                new Color(33, 150, 243),
                                                                new Color(155, 89, 182),
                                                                new Color(233, 30, 99),
                                                                new Color(0, 188, 212),
                                                                new Color(121, 85, 72));

    @Setter
    boolean active = false;

    final Visualizations words = Visualizations.builder().layer(ContentStreams.WORDS).build();
    final Visualizations lines = Visualizations.builder().layer(ContentStreams.LINES).build();
    final Visualizations zones = Visualizations.builder().layer(ContentStreams.ZONES).build();
    final Visualizations mainBody = Visualizations.builder().layer(ContentStreams.MAIN_BODY).build();
    final Visualizations rulings = Visualizations.builder().layer(ContentStreams.RULINGS).build();
    final Visualizations cells = Visualizations.builder().layer(ContentStreams.CELLS).build();
    final Visualizations markedContent = Visualizations.builder().layer(ContentStreams.MARKED_CONTENT).build();
    final Visualizations neighbours = Visualizations.builder().layer(ContentStreams.NEIGHBOURS).build();
    final Visualizations characters = Visualizations.builder().layer(ContentStreams.CHARACTERS).build();


    public Stream<Visualizations> streamAll() {

        if (!active) {
            return Stream.empty();
        }
        return Stream.of(characters, //
                         neighbours,//
                         words, //
                         lines, //
                         zones, //
                         rulings, //
                         cells, //
                         mainBody, //
                         markedContent //
        );
    }


    public void addTextVisualizations(List<TextPositionSequence> textPositionSequences, int pageNumber) {

        if (!active) {
            return;
        }
        VisualizationsOnPage visualizationsOnPage = getOrCreateVisualizationsOnPage(pageNumber, this.words);
        visualizationsOnPage.getColoredRectangles()
                .addAll(textPositionSequences.stream()
                                .map(textPositionSequence -> textPositionSequence.getTextPositions()
                                        .stream()
                                        .map(RedTextPosition::getInitialUserSpacePosition)
                                        .collect(RectangleTransformations.collectBBox()))
                                .map(rect -> new ColoredRectangle(rect, WORDS_COLOR, 1))
                                .toList());
    }


    public void addCleanRulingVisualization(CleanRulings cleanRulings, int pageNumber) {

        if (!active) {
            return;
        }
        VisualizationsOnPage visualizationsOnPage = getOrCreateVisualizationsOnPage(pageNumber, this.rulings);
        visualizationsOnPage.getColoredLines()
                .addAll(Stream.of(cleanRulings.getHorizontals(), cleanRulings.getVerticals())
                                .flatMap(Collection::stream)
                                .map(ruling -> new ColoredLine(ruling, decideOnRulingColor(ruling), 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 Rectangle2D> cells, int pageNumber) {

        if (!active) {
            return;
        }
        VisualizationsOnPage visualizationsOnPage = getOrCreateVisualizationsOnPage(pageNumber, this.cells);
        visualizationsOnPage.getColoredRectangles()
                .addAll(cells.stream()
                                .map(ruling -> new ColoredRectangle(ruling, 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::getBBox)
                                .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::getBBox)
                                .map(line -> new ColoredRectangle(line, LINES_COLOR, 0.3f))
                                .toList());
    }


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

        if (!active) {
            return;
        }
        VisualizationsOnPage visualizationsOnPage = getOrCreateVisualizationsOnPage(page, zones);
        visualizationsOnPage.getColoredRectangles()
                .addAll(textPageBlocks.stream()
                                .map(rect -> new ColoredRectangle(rect.getBBox(), 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, PDPage pdPage) {

        if (!active) {
            return;
        }

        VisualizationsOnPage visualizationsOnPage = getOrCreateVisualizationsOnPage(pageNumber, markedContent);

        List<MarkedContentUtils.MarkedContentPosition> markedContentBBoxMapBySubType = MarkedContentUtils.getMarkedContentPositions(markedContents, pdPage);

        markedContentBBoxMapBySubType.forEach(markedContentPosition -> {

                                                  var bbox = markedContentPosition.textPositions()
                                                          .stream()
                                                          .collect(RectangleTransformations.collectBBox());
                                                  String type = markedContentPosition.formattedType();
                                                  float translationAmount = ((FONT.getStringWidth(type) / 1000) * 10 + (2 * 1) + 4);
                                                  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 = ROTATING_CHARACTER_COLOR.get(index.getAndIncrement() % ROTATING_CHARACTER_COLOR.size());
                    Rectangle2D charBBox = character.getTextPosition().getInitialUserSpacePosition();
                    characterVisualizations.getColoredRectangles().add(new ColoredRectangle(charBBox, color, 1));
                    character.getNeighbors()
                            .forEach(neighbor -> {
                                Rectangle2D neighborBBox = neighbor.getCharacter().getTextPosition().getInitialUserSpacePosition();
                                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));
                            });
                }));

    }


    private VisualizationsOnPage getOrCreateVisualizationsOnPage(int page, Visualizations visualizations) {

        if (visualizations.getVisualizationsOnPages().containsKey(page - 1)) {
            return visualizations.getVisualizationsOnPages().get(page - 1);
        }
        VisualizationsOnPage visualizationsOnPage = VisualizationsOnPage.builder().build();
        visualizations.getVisualizationsOnPages().put(page - 1, visualizationsOnPage);
        return visualizationsOnPage;
    }

}
