package com.knecon.fforesight.service.layoutparser.server.graph;

import static org.junit.jupiter.api.Assertions.assertTrue;

import java.awt.Color;
import java.awt.geom.Rectangle2D;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.text.similarity.LevenshteinDistance;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

import com.iqser.red.storage.commons.service.StorageService;
import com.iqser.red.storage.commons.utils.FileSystemBackedStorageService;
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.graph.nodes.Document;
import com.knecon.fforesight.service.layoutparser.processor.model.graph.textblock.AtomicTextBlock;
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.services.factory.DocumentGraphFactory;
import com.knecon.fforesight.service.layoutparser.processor.utils.TextPositionOperations;
import com.knecon.fforesight.service.layoutparser.server.PDFNetInitializer;
import com.knecon.fforesight.service.layoutparser.server.utils.BuildDocumentTest;
import com.knecon.fforesight.service.viewerdoc.model.Standard14EmbeddableFont;
import com.pdftron.common.Matrix2D;
import com.pdftron.common.PDFNetException;
import com.pdftron.pdf.ColorPt;
import com.pdftron.pdf.ColorSpace;
import com.pdftron.pdf.Element;
import com.pdftron.pdf.ElementBuilder;
import com.pdftron.pdf.ElementWriter;
import com.pdftron.pdf.Font;
import com.pdftron.pdf.GState;
import com.pdftron.pdf.PDFDoc;
import com.pdftron.pdf.Page;
import com.pdftron.sdf.SDFDoc;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class DocumentReadingOrderTest extends BuildDocumentTest {

    private static final boolean DRAW_DIR_ADJ_COORDS = false;
    public static final List<LayoutParsingType> LAYOUT_PARSING_TYPES = List.of(LayoutParsingType.DOCUMINE,
                                                                               LayoutParsingType.DOCUMINE_OLD,
                                                                               LayoutParsingType.REDACT_MANAGER,
                                                                               LayoutParsingType.REDACT_MANAGER_WITHOUT_DUPLICATE_PARAGRAPH);

    @Autowired
    PDFNetInitializer pdfNetInitializer;

    @Autowired
    StorageService storageService;


    @BeforeEach
    public void before() {

        pdfNetInitializer.init();
    }


    @AfterEach
    public void cleanUp() {

        ((FileSystemBackedStorageService) storageService).clearStorage();
    }


    @Test
    public void readingOrderTestSeite14() {

        String pdfFile = "files/syngenta/CustomerFiles/SinglePages/Seite14.pdf";
        String expectedText = """
                27
                26 APPENDICES SECTION
                APPENDIX 1 Analytical Report
                syngenta
                A16148F
                Batch ID 533158 (GP-080305)
                Batch Identification 533158
                Product Design Code A16148F
                Product Denomination SYN524464 FS (500)
                Product by Common Name SYN524464 FS (500)
                Other Product Code(s) GP-080305
                Source Technology & Projects, Syngenta Crop Protection, Inc.
                Chemical Analysis
                (Active Ingredient Content)
                Identity of the Active Ingredient* Confirmed
                Content of SYN524464* 45.6% (wt/wt) or 534 g/L
                Methodology Used for Characterization HPLC
                The Active Ingredient content is within the FAO limits.
                Physical Analysis
                Appearance* pink opaque liquid
                Density* 1171 g/L
                Stability:
                Storage Temperature <30°℃
                Expiration date March 2009
                The stability of this test substance will be determined concurrently through reanalysis of material held
                in inventory under GLP conditions at Syngenta Crop Protection, Inc., Greensboro, NC
                This Certificate of Analysis is summarizing data (marked with an asterisk) from a study that has been
                performed in compliance with Good Laboratory Practices per 40 CFR Part 160 Raw data,
                documentation, protocols, any amendments to study protocols and reports pertaining to this study are
                maintained in the Syngenta Crop Protection Archives in Greensboro, NC.
                Authorization'
                26 Mar 2008
                Dorothea Jeffery Date
                Group Leader I
                Analytical & Product Chemistry Department
                Document 10350420.doc Certificate of Analysis
                Page 1 of 1 Study T000973-08
                Report Number: 11813-08 Page 14 of 14
                """;

        assertSimilarReadingOrder(expectedText, pdfFile);
    }


    @Test
    public void readingOrderTestTiltedText() {

        String pdfFile = "files/syngenta/CustomerFiles/SinglePages/tiltedText.pdf";
        String expectedText = """
                However there was no consistency in the areas affected either between sexes or at different
                ages, in general other measurements for the same structures at other levels showed no
                differences, all were within the historical control range of mean values and none of these
                differences is considered to be related to treatment (Appendix K).
                7. DISCUSSION
                The purpose of this study, which was to determine the potential for developmental
                neurotoxicity in the assessment and evaluation of the toxic characteristics of lambda-
                cyhalothrin in rats, was successfully accomplished.
                There was evidence of toxicity characterised by lower bodyweights and food consumption in
                dams receiving 60 or 150 ppm lambda-cyhalothrin during gestation and also post partum in
                the 150 ppm group only.
                There were no treatment-related effects of administration of lambda-cyhalothrin on
                reproductive parameters: there were no effects on gestation length, mean litter size or on pup
                bodyweight at birth.
                There was evidence of toxicity in F1 animals receiving 150 ppm. This was seen as slightly
                higher pup mortality up to day 5 and lower bodyweights from day 5, reaching a maximum of
                approximately 8-9% below control on day 22.
                There was a small difference in the age at which male rats in the 150 ppm group reached
                preputial separation, but this was too small to be of toxicological significance.
                No effects were seen on motor activity or response to auditory startle.
                There was no clear evidence of any effects in the learning and memory assessment in
                weanling (age 21-24 days) or young adult animals (age 59-62 days). However, at day 21
                swimming speeds of females receiving 150 ppm were slightly slower than controls. The
                difference is considered to reflect a difference in swimming performance rather than an effect
                on learning or memory.
                No neuropathological effect of treatment with lambda-cyhalothrin was detected from a
                detailed microscopic examination of the selected F1 animals post mortem on day 12 or 63.
                LAMBDA-CYHALOTHRIN: DEVELOPMENTAL NEUROTOXICITY STUDY IN RATS
                CTL/RR0969/REGULATORY/REPORT - 34
                """;

        assertSimilarReadingOrder(expectedText, pdfFile);
    }


    @Test
    public void readingOrderTest402Study() {

        String pdfFile = "files/SinglePages/402StudyPage5.pdf";
        String expectedText = """
                2.0 INTRODUCTION
                2.1 Purpose
                Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor
                invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et
                accusam et justo duo dolores et ea rebum.
                Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem
                ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt
                ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et
                justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est
                Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
                diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
                voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd
                gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
                2.2 Guidelines
                Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor
                invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
                At vero eos et accusam et justo duo dolores et ea rebum.
                Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem
                ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt
                ut labore et dolore magna aliquyam erat, sed diam voluptua.
                At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
                sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
                consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore
                magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et
                ea rebum.
                Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
                Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel
                illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui
                blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem
                ipsum dolor sit amet.
                2.3 Test Facility
                Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor
                invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et
                accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata
                sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur
                sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna
                aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea
                rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
                Report Number: 20/080-002P 5
                """;

        assertSimilarReadingOrder(expectedText, pdfFile);
    }


    @Test
    public void readingOrderTest402StudyRotated() {

        String pdfFile = "files/SinglePages/402StudyPage5_rotated.pdf";
        String expectedText = """
                2.0 INTRODUCTION
                2.1 Purpose
                Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor
                invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et
                accusam et justo duo dolores et ea rebum.
                Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem
                ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt
                ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et
                justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est
                Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
                diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
                voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd
                gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
                2.2 Guidelines
                Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor
                invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
                At vero eos et accusam et justo duo dolores et ea rebum.
                Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem
                ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt
                ut labore et dolore magna aliquyam erat, sed diam voluptua.
                At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
                sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
                consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore
                magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et
                ea rebum.
                Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
                Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel
                illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui
                blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem
                ipsum dolor sit amet.
                2.3 Test Facility
                Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor
                invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et
                accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata
                sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur
                sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna
                aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea
                rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
                Report Number: 20/080-002P 5
                """;

        assertSimilarReadingOrder(expectedText, pdfFile);
    }


    private void assertSimilarReadingOrder(String expectedText, String pdfFile) {

        List<String> expectedLines = List.of(expectedText.split("\n"));
        for (LayoutParsingType layoutParsingType : LAYOUT_PARSING_TYPES) {

            log.info("Evaluating for {}", layoutParsingType);

            ClassificationDocument classificationDocument = parseLayout(pdfFile, layoutParsingType);

            if (DRAW_DIR_ADJ_COORDS) {
                drawDirAdjCoords(pdfFile, classificationDocument, layoutParsingType);
            }

            Document document = DocumentGraphFactory.buildDocumentGraph(layoutParsingType, classificationDocument);
            List<String> readLines = getTextAsLines(document);
            readLines.forEach(log::info);

            int correctCount = 0;
            int maxLineOffset = 0;
            for (int i = 0; i < expectedLines.size(); i++) {
                String expectedLine = expectedLines.get(i);
                int mostSimilarLine = 0;
                double maxSimilarity = 0;
                for (int j = 0; j < readLines.size(); j++) {
                    String readLine = readLines.get(j);
                    double similarity = similarity(expectedLine, readLine);
                    if (similarity > maxSimilarity) {
                        maxSimilarity = similarity;
                        mostSimilarLine = j;
                    }
                }
                if (readLines.get(mostSimilarLine).trim().equals(expectedLine.trim())) {
                    correctCount++;
                    int lineOffset = Math.abs(mostSimilarLine - i);
                    if (lineOffset > 0) {
                        log.info("Line {} offset by {}", readLines.get(mostSimilarLine), lineOffset);
                    }
                    if (lineOffset > maxLineOffset) {
                        maxLineOffset = lineOffset;
                    }
                } else {
                    log.error("Lines {}-{} do not match: \n Expected: {}\n Actual: {}", i, mostSimilarLine, expectedLine, readLines.get(mostSimilarLine));
                }
            }
            double correctLinesFactor = (double) correctCount / (double) readLines.size();
            double averageLineOffset = (double) maxLineOffset / (double) readLines.size();

            log.info("Difference in number of lines: {}", Math.abs(expectedLines.size() - readLines.size()));
            log.info("Correct lines factor: {}", correctLinesFactor);
            log.info("Max order offset: {}, avg: {}", maxLineOffset, averageLineOffset);
            // In the rotated document one line is read as two

            assertTrue(Math.abs(expectedLines.size() - readLines.size()) <= 1);
            // Most of the errors come from the similarity metric finding different lines in 402 study, as the lines are too similar, or a miss classification of Footers
            assertTrue(averageLineOffset < 1);
            assertTrue(correctLinesFactor > 0.9);
        }
    }


    public List<String> getTextAsLines(Document document) {

        return document.getTextBlock().getAtomicTextBlocks()
                .stream()
                .filter(atb -> !atb.isEmpty())
                .map(DocumentReadingOrderTest::getLines)
                .flatMap(List::stream)
                .toList();
    }


    private static List<String> getLines(AtomicTextBlock atomicTextBlock) {

        int numberOfLines = atomicTextBlock.numberOfLines();
        List<String> lines = new ArrayList<>(numberOfLines);
        for (int line = 0; line < numberOfLines; line++) {
            lines.add(atomicTextBlock.getLine(line).toString());
        }
        return lines;
    }


    private static double similarity(String s1, String s2) {

        LevenshteinDistance levenshteinDistance = new LevenshteinDistance();

        int max = Math.max(s1.length(), s2.length());
        int dist = levenshteinDistance.apply(s1, s2);
        return 1 - (double) dist / (double) max;
    }


    @SneakyThrows
    private void drawDirAdjCoords(String filename, ClassificationDocument classificationDocument, LayoutParsingType layoutParsingType) {

        try (PDFDoc pdfDoc = new PDFDoc(); ElementWriter writer = new ElementWriter(); ElementBuilder builder = new ElementBuilder()) {

            Standard14EmbeddableFont font = Standard14EmbeddableFont.helvetica();
            Font helvetica = Font.create(pdfDoc, Font.e_helvetica);
            for (ClassificationPage classificationDocumentPage : classificationDocument.getPages()) {
                int count = 0;
                Page page = pdfDoc.pageCreate();
                writer.begin(page);
                for (AbstractPageBlock abstractBlock : classificationDocumentPage.getTextBlocks()) {

                    if (abstractBlock instanceof TextPageBlock textBlock) {
                        for (TextPositionSequence sequence : TextPositionOperations.mergeAndSort(List.of(textBlock))) {

                            float stringWidth;
                            try {
                                stringWidth = font.getStringWidth(sequence.toString());
                            } catch (Exception e) {
                                stringWidth = font.getFont().getAverageFontWidth() * sequence.toString().length();
                            }
                            double fontSize = (sequence.getBBoxDirAdj().getWidth() / stringWidth) * 1000;
                            try (Matrix2D textMatrix = new Matrix2D(1,
                                                                    0,
                                                                    0,
                                                                    1,
                                                                    sequence.getXDirAdj(),
                                                                    page.getCropBox().getHeight() - sequence.getYDirAdj() - sequence.getHeightDirAdj())) {
                                writeText(sequence.toString(), textMatrix, builder, helvetica, fontSize, writer, Color.BLACK);
                                writeText(String.valueOf(count), textMatrix.translate(-(2 + (5 * String.valueOf(count).length())), 0), builder, helvetica, 8, writer, Color.RED);
                                count++;
                            }

                            writeBBox(sequence.getBBoxDirAdj(), builder, page, writer, Color.BLACK);
                        }
                        writeBBox(textBlock.getBBoxDirAdj(), builder, page, writer, Color.BLUE);
                    }
                }
                writer.end();
                pdfDoc.pagePushBack(page);

            }

            Path stem = Path.of("/tmp/READING_ORDER_TEST/");
            Files.createDirectories(stem);
            try (var out = new FileOutputStream(stem.resolve(layoutParsingType.name() + "_" + Path.of(filename).getFileName()).toFile() + "_dirAdjCoordinates.pdf")) {
                pdfDoc.save(out, SDFDoc.SaveMode.LINEARIZED, null);
            }
        }
    }


    private static void writeBBox(Rectangle2D r, ElementBuilder builder, Page page, ElementWriter writer, Color color) throws PDFNetException {

        Element rect = builder.createRect(r.getX(), page.getCropBox().getHeight() - r.getY(), r.getWidth(), -r.getHeight());
        float[] comp = color.getColorComponents(null);
        rect.getGState().setStrokeColorSpace(ColorSpace.createDeviceRGB());
        try (ColorPt colorpt = new ColorPt(comp[0], comp[1], comp[2])) {
            rect.getGState().setStrokeColor(colorpt);
        }
        rect.setPathStroke(true);
        writer.writeElement(rect);
    }


    private static void writeText(String string,
                                  Matrix2D matrix2D,
                                  ElementBuilder builder,
                                  Font helvetica,
                                  double fontSize,
                                  ElementWriter writer,
                                  Color color) throws PDFNetException {

        Element text = builder.createTextBegin(helvetica, fontSize);
        text.getGState().setFillColorSpace(ColorSpace.createDeviceRGB());
        float[] colorComponents = color.getColorComponents(null);
        try (ColorPt colorpt = new ColorPt(colorComponents[0], colorComponents[1], colorComponents[2])) {
            text.getGState().setFillColor(colorpt);
        }
        text.setTextMatrix(matrix2D);
        text.getGState().setTextRenderMode(GState.e_fill_text);
        writer.writeElement(text);

        text = builder.createTextRun(string);
        writer.writeElement(text);
        text = builder.createTextEnd();
        writer.writeElement(text);

    }

}
