package com.knecon.fforesight.service.layoutparser.server.utils.visualizations;

import java.awt.Color;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;

import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.apache.pdfbox.util.Matrix;
import org.springframework.core.io.ClassPathResource;

import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.NodeType;
import com.knecon.fforesight.service.layoutparser.processor.model.graph.DocumentTree;
import com.knecon.fforesight.service.layoutparser.processor.model.graph.nodes.Document;
import com.knecon.fforesight.service.layoutparser.processor.model.graph.nodes.Page;
import com.knecon.fforesight.service.layoutparser.processor.model.graph.textblock.AtomicTextBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.graph.textblock.TextBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.table.Ruling;
import com.knecon.fforesight.service.layoutparser.processor.utils.DrawingOptions;
import com.knecon.fforesight.service.layoutparser.processor.utils.PdfVisualisationUtility;
import com.knecon.fforesight.service.layoutparser.processor.utils.RectangleTransformations;

import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;

@UtilityClass
public class PdfDraw {

    public static void drawRectanglesPerPage(String filename, List<List<Rectangle2D>> rectanglesPerPage, String tmpFileName, DrawingOptions options) throws IOException {

        ClassPathResource pdfResource = new ClassPathResource(filename);
        try (PDDocument pdDocument = Loader.loadPDF(pdfResource.getFile()); var out = new FileOutputStream(tmpFileName)) {
            for (int pageNumber = 1; pageNumber < pdDocument.getNumberOfPages() + 1; pageNumber++) {
                PdfVisualisationUtility.drawRectangle2DList(pdDocument,
                        pageNumber,
                        rectanglesPerPage.get(pageNumber - 1),
                        options);
            }
            pdDocument.save(out);
        }

    }

    public static void drawRectanglesPerPage(String filename, List<List<Rectangle2D>> rectanglesPerPage, String tmpFileName) throws IOException {

        ClassPathResource pdfResource = new ClassPathResource(filename);
        try (PDDocument pdDocument = Loader.loadPDF(pdfResource.getFile()); var out = new FileOutputStream(tmpFileName)) {
            for (int pageNumber = 1; pageNumber < pdDocument.getNumberOfPages() + 1; pageNumber++) {
                PdfVisualisationUtility.drawRectangle2DList(pdDocument,
                        pageNumber,
                        rectanglesPerPage.get(pageNumber - 1),
                        DrawingOptions.builder().stroke(true).build());
            }
            pdDocument.save(out);
        }

    }


    public static void drawRectanglesPerPageNumberedByLine(String filename, List<List<List<Rectangle2D>>> rectanglesPerPage, String tmpFileName) throws IOException {

        ClassPathResource pdfResource = new ClassPathResource(filename);
        try (PDDocument pdDocument = Loader.loadPDF(pdfResource.getFile()); var out = new FileOutputStream(tmpFileName)) {
            for (int pageNumber = 1; pageNumber < pdDocument.getNumberOfPages() + 1; pageNumber++) {
                var rectanglesOnPage = rectanglesPerPage.get(pageNumber - 1);
                for (int lineNumber = 0; lineNumber < rectanglesOnPage.size(); lineNumber++) {
                    var rectanglesInLine = rectanglesOnPage.get(lineNumber);
                    PdfVisualisationUtility.drawRectangle2DList(pdDocument, pageNumber, rectanglesInLine, DrawingOptions.builder().stroke(true).build());
                    double y = Math.min(rectanglesInLine.get(0).getMinY(), rectanglesInLine.get(0).getMaxY());
                    PdfVisualisationUtility.drawText(String.format("%d", lineNumber),
                            pdDocument,
                            new Point2D.Double(rectanglesInLine.get(0).getX() - (5 + (5 * countNumberOfDigits(lineNumber))), y + 2),
                            pageNumber,
                            DrawingOptions.builder().stroke(true).build());
                }
            }
            pdDocument.save(out);
        }

    }


    private static int countNumberOfDigits(int num) {

        int final_num = num;
        if (final_num == 0) {
            return 1;
        }
        int count = 0;
        for (; final_num != 0; final_num /= 10) {
            count++;
        }
        return count;
    }


    public static void drawDocumentGraph(PDDocument document, Document documentGraph) {

        documentGraph.getDocumentTree().allEntriesInOrder().forEach(entry -> drawNode(document, entry));
    }


    public static void drawNode(PDDocument document, DocumentTree.Entry entry) {

        DrawingOptions options = buildStandardOptionsForNodes(entry);

        drawBBoxAndLabelAndNumberOnPage(document, entry, options);

    }


    public static void drawTextBlock(PDDocument document, TextBlock textBlock, DrawingOptions options) {

        textBlock.getAtomicTextBlocks().forEach(atb -> drawAtomicTextBlock(document, atb, options));
    }


    public static void drawAtomicTextBlock(PDDocument document, AtomicTextBlock atomicTextBlock, DrawingOptions options) {

        drawRectangle2DList(document, atomicTextBlock.getPage().getNumber(), atomicTextBlock.getPositions().stream().toList(), options);

    }


    @SneakyThrows
    private static void drawText(String string, PDDocument document, Point2D location, Integer pageNumber, DrawingOptions options, boolean rotate) {

        var pdPage = document.getPage(pageNumber - 1);
        var contentStream = new PDPageContentStream(document, pdPage, PDPageContentStream.AppendMode.APPEND, true);

        contentStream.setNonStrokingColor(options.getStrokeColor());
        contentStream.setLineWidth(options.getStrokeWidth());

        contentStream.beginText();
        if (rotate) {
            contentStream.setTextMatrix(Matrix.getRotateInstance(Math.toRadians(15), (float) location.getX(), (float) location.getY()));
        } else {
            contentStream.newLineAtOffset((float) location.getX(), (float) location.getY());
        }
        contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 10);
        contentStream.showText(string);
        contentStream.endText();
        contentStream.close();
    }


    @SneakyThrows
    public static void drawRectangle2DList(PDDocument document, int pageNumber, List<Rectangle2D> rectCollection, DrawingOptions options) {

        var pdPage = document.getPage(pageNumber - 1);
        drawRectangle2DList(document, rectCollection, options, pdPage);
    }


    private static void drawRectangle2DList(PDDocument document, List<Rectangle2D> rectCollection, DrawingOptions options, PDPage pdPage) throws IOException {

        var contentStream = new PDPageContentStream(document, pdPage, PDPageContentStream.AppendMode.APPEND, true);

        contentStream.setStrokingColor(options.getStrokeColor());
        contentStream.setNonStrokingColor(options.getFillColor());
        contentStream.setLineWidth(options.getStrokeWidth());

        for (var r : rectCollection) {
            contentStream.addRect((float) r.getMinX(), (float) r.getMinY(), (float) r.getWidth(), (float) r.getHeight());

            if (options.isStroke() && options.isFill()) {
                contentStream.fillAndStroke();
            } else if (options.isStroke()) {
                contentStream.stroke();
            } else if (options.isFill()) {
                contentStream.fill();
            }
        }
        contentStream.close();
    }


    @SneakyThrows
    public static void drawRectanglesAndLinesPerPage(String filename, List<List<Rectangle2D>> list, List<List<Rectangle2D>> rectanglesPerPage, String tmpFileName) {

        ClassPathResource pdfResource = new ClassPathResource(filename);
        try (PDDocument pdDocument = Loader.loadPDF(pdfResource.getFile()); var out = new FileOutputStream(tmpFileName)) {
            for (int pageNumber = 1; pageNumber < pdDocument.getNumberOfPages() + 1; pageNumber++) {
//                PdfVisualisationUtility.drawLine2DList(pdDocument,
//                        pageNumber,
//                        list.get(pageNumber - 1),
//                        PdfVisualisationUtility.DrawingOptions.builder().stroke(true).build());
                PdfVisualisationUtility.drawRectangle2DList(pdDocument,
                        pageNumber,
                        rectanglesPerPage.get(pageNumber - 1),
                        DrawingOptions.builder().stroke(true).build());
                PdfVisualisationUtility.drawRectangle2DList(pdDocument, pageNumber, list.get(pageNumber - 1), DrawingOptions.builder().stroke(true).build());
            }
            pdDocument.save(out);
        }
    }


    @SneakyThrows
    public static void drawLinesPerPage(String filename, List<List<Ruling>> linesPerPage, String tmpFileName) {

        ClassPathResource pdfResource = new ClassPathResource(filename);
        try (PDDocument pdDocument = Loader.loadPDF(pdfResource.getFile()); var out = new FileOutputStream(tmpFileName)) {
            for (int pageNumber = 1; pageNumber < pdDocument.getNumberOfPages() + 1; pageNumber++) {
                PdfVisualisationUtility.drawLine2DList(pdDocument,
                        pageNumber,
                        linesPerPage.get(pageNumber - 1),
                        DrawingOptions.builder().strokeColor(Color.RED).stroke(true).build());
            }
            pdDocument.save(out);
        }
    }




    private static DrawingOptions buildStandardOptionsForNodes(DocumentTree.Entry entry) {

        return DrawingOptions.builder().stroke(true).strokeColor(switch (entry.getType()) {
            case DOCUMENT -> Color.LIGHT_GRAY;
            case HEADER, FOOTER -> Color.GREEN;
            case PARAGRAPH -> Color.BLUE;
            case HEADLINE -> Color.RED;
            case SECTION -> Color.BLACK;
            case TABLE -> Color.ORANGE;
            case TABLE_CELL -> Color.GRAY;
            case IMAGE -> Color.MAGENTA;
        }).build();
    }


    private static void drawBBoxAndLabelAndNumberOnPage(PDDocument document, DocumentTree.Entry entry, DrawingOptions options) {

        Map<Page, Rectangle2D> rectanglesPerPage = entry.getNode().getBBox();
        for (Page page : rectanglesPerPage.keySet()) {
            Rectangle2D rectangle2D = rectanglesPerPage.get(page);
            if (entry.getType() == NodeType.SECTION) {
                rectangle2D = RectangleTransformations.pad(rectangle2D, 10, 10);
            }
            drawRectangle2DList(document, page.getNumber(), List.of(rectangle2D), options);
            drawText(buildString(entry),
                    document,
                    new Point2D.Double(rectangle2D.getMinX(), rectangle2D.getMaxY() + 2),
                    page.getNumber(),
                    options,
                    entry.getType() == NodeType.TABLE_CELL);
        }
    }


    private static String buildString(DocumentTree.Entry entry) {

        return entry.getNode().getNumberOnPage() + ": " + entry.getTreeId() + ": " + entry.getType();
    }

}