package com.server.api.service.rollendruck;

import java.io.File;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Stream;

import javax.imageio.ImageIO;

import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.element.Paragraph;
import com.server.api.models.VorlageRollendruck;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.itextpdf.io.image.ImageData;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Image;

import java.util.List;
import java.awt.image.BufferedImage;

@Service
public class VorlageRollendruckPDFGeneratorService {
    final float pointsPerMm = 2.83464567f;
    final float numberPointsForMm = 28.3465f;

    @Autowired VorlageRollendruckService vorlageRollendruckService;


    private static class ImageObject {
        float x1;
        float y1;
        float x2;
        float y2;
        String imageGroup;
        boolean isRotated;
        String imagePath;


        public ImageObject(float x1, float y1, float x2, float y2, String imageGroup, boolean isRotated,
                String imagePath) {
            this.x1 = x1;
            this.y1 = y1;
            this.x2 = x2;
            this.y2 = y2;
            this.imageGroup = imageGroup;
            this.isRotated = isRotated;

            this.imagePath = imagePath;
        }

        public float getX1() {
            return x1;
        }

        public float getY1() {
            return y1;
        }

        public float getX2() {
            return x2;
        }

        public float getY2() {
            return y2;
        }


        public boolean isRotated() {
            return isRotated;
        }

        public String getImagePath() {
            return imagePath;
        }

    }

    private static class ImageGroup {
        String name;
        List<String> images;
        float size;

        public ImageGroup(String name) {
            this.name = name;
            this.images = new ArrayList<>();
        }

        public String getName() {
            return name;
        }

        public List<String> getImages() {
            return images;
        }

        public float getSize() {
            return size;
        }

        public void setSize(float size) {
            this.size = size;
        }
    }
    
    private class Surface {
        float height;
        float width;
        List<ImageObject> placedImages;
        public Surface(float height, float width) {
            this.height = height;
            this.width = width;
            this.placedImages = new ArrayList<>();
        }
        private List<String> placeImageGroup(List<String> imagePaths, float width, float height, String imageGroupName) throws MalformedURLException {
            List<String> notPlacedImages = new ArrayList<>();
            float cursor_x = 0;
            float cursor_y = height * pointsPerMm;
            for (String singleImagePath : imagePaths) {
                ImageData imageData = ImageDataFactory.create(singleImagePath);
                Image image = new Image(imageData);
                float[] imageSize = getImageSize(singleImagePath);
                float imageWidth = pixelsToMm(imageSize[0]);
                float imageHeight = pixelsToMm(imageSize[1]);
                image.scaleToFit(imageWidth * pointsPerMm, imageHeight * pointsPerMm);
                boolean foundFreePos = false;
                boolean isRotated = false;
                float abstand = numberPointsForMm * pointsPerMm;
                float updated_x = 0;
                float updated_y = 0;
                boolean isEndOfPage = false;

                while (!foundFreePos) {
                    // cursor runs through the whole page
                    if (cursor_x + 1 > width * pointsPerMm) {
                        cursor_x = 0;
                        if (cursor_y - 1 < 0) {
                            isEndOfPage = true;
                            break;
                        } else {
                            cursor_y -= 1;
                        }
                    } else {
                        cursor_x += 1;
                    }
                    // check if cursor is not on an image
                    if (isOverlap(cursor_x, imageWidth, cursor_y - imageHeight * pointsPerMm, imageHeight)) {
                        continue;
                    }

                    updated_x = cursor_x + abstand;
                    updated_y = cursor_y - imageHeight * pointsPerMm - abstand;


                    boolean isLeftNeighbourOverlap = false;
                    for (ImageObject placedImage : placedImages) {
                       if(placedImage.y1 < cursor_y && placedImage.y2 > updated_y && placedImage.x2 >= cursor_x - 30 && placedImage.x1 < cursor_x - 30){
                           isLeftNeighbourOverlap = true;
                            break;
                       }

                    }
                    if (isLeftNeighbourOverlap) {
                        continue;
                    }


                    if (isOverlap(updated_x, imageWidth, updated_y, imageHeight)) {
                        continue;
                    }

                    if (updated_x + imageWidth * pointsPerMm < width * pointsPerMm
                            && updated_y> 0) {

                        foundFreePos = true;
                    }
                }

                if (isEndOfPage) {
                    notPlacedImages.add(singleImagePath);
                    continue;
                }

                if (isRotated) {
                    placedImages.add(new ImageObject(updated_x, updated_y,
                            updated_x + imageHeight * pointsPerMm,
                            updated_y + imageWidth * pointsPerMm, imageGroupName, true, singleImagePath));
                    image.setFixedPosition(updated_x, updated_y);
                    image.setRotationAngle(Math.toRadians(90));
                } else {
                    placedImages.add(new ImageObject(updated_x, updated_y,
                            updated_x + imageWidth * pointsPerMm,
                            updated_y + imageHeight * pointsPerMm, imageGroupName, false, singleImagePath));
                    image.setFixedPosition(updated_x, updated_y);
                }


            }
            return notPlacedImages;
        }

        private boolean isOverlap(float cursor_x, float imageWidth, float cursor_y, float imageHeight) {
            for (ImageObject imageObject : placedImages) {
                if (imageObject.getX1() < cursor_x + imageWidth * pointsPerMm
                        && imageObject.getX2() > cursor_x
                        && imageObject.getY1() < cursor_y + imageHeight * pointsPerMm
                        && imageObject.getY2() > cursor_y) {
                    return true;
                }
            }
            return false;
        }

    }

    public void generatePdf(long vorlageId, String outputDir, String uploadPath) throws Exception {
        // get width and height from vorlage
        VorlageRollendruck vorlage = vorlageRollendruckService.getVorlageById(vorlageId);
        float width = vorlage.getWidth();
        float height = vorlage.getHeight();

        List<String> articleTypes = splitArticleTypes(vorlage.getArticleTypes());
        List<String> dupliStrings = vorlageRollendruckService.getAllDupliStrings();


        Path outputFolder = Paths.get(outputDir);
        if (!Files.exists(outputFolder)) {
            Files.createDirectories(outputFolder);
        }
        String imagePath = uploadPath;
        File folder = new File(imagePath);
        File[] files = folder.listFiles();
        files = getAllFilePaths(imagePath);

        // delete hidden files beginning with .
        files = Arrays.stream(getAllFilePaths(imagePath)).filter(file -> !file.getName().startsWith(".")).toArray(File[]::new);


        // create subgroups
        List<ImageGroup> imageGroups = new ArrayList<>();
        for (File file : files) {
            String name = file.getName();
            int i = 0;
            while (i < name.length() && !Character.isDigit(name.charAt(i)) && name.charAt(i) != ' ') {
                i++;
            }
            boolean isGroup = false;
            for (ImageGroup imageGroup : imageGroups) {
                if (imageGroup.getName().equals(name.substring(0, i).trim())) {
                    imageGroup.images.add(file.getAbsolutePath());
                    isGroup = true;
                    break;
                }
            }
            if (!isGroup) {
                String groupName = name.substring(0, i).trim();
                ImageGroup imageGroup = new ImageGroup(groupName);
                imageGroup.images.add(file.getAbsolutePath());
                float[] imageSizes = getImageSize(file.getAbsolutePath());
                float imageSize = pixelsToMm(imageSizes[0]) * pixelsToMm(imageSizes[1]);
                imageGroup.setSize(imageSize);
                imageGroups.add(imageGroup);
            }

        }

        // delete imageGroups that are not in articleTypes
        imageGroups.removeIf(imageGroup -> !articleTypes.contains(imageGroup.getName()));

        // sort groups by size
        imageGroups.sort((g1, g2) -> Float.compare(g2.getSize(), g1.getSize()));

        // if imageGroup contains dupliStrings double the entries
        for (ImageGroup imageGroup : imageGroups) {
            if (dupliStrings.contains(imageGroup.getName())) {
                List<String> images = new ArrayList<>(imageGroup.getImages());
                for (String image : images) {
                    imageGroup.getImages().add(image);
                }
            }
        }

        List<Surface> surfaces = new ArrayList<>();
        surfaces.add(new Surface(height, width));
        int surfaceIndex = 0;
        for (ImageGroup imageGroup : imageGroups) {
            List<String> notPlacedImages = surfaces.get(surfaceIndex).placeImageGroup(imageGroup.getImages(), width, height, imageGroup.getName());
            while (!notPlacedImages.isEmpty()) {
                surfaces.add(new Surface(height, width));
                surfaceIndex++;
                notPlacedImages = surfaces.get(surfaceIndex).placeImageGroup(notPlacedImages, width, height, imageGroup.getName());
            }

        }


        int count = 1;
        for (Surface surface: surfaces){
            try (PdfWriter writer = new PdfWriter(outputDir + count + ". " + vorlage.getPrinter() + " Rollendruck.pdf");
                 PdfDocument pdf = new PdfDocument(writer);
                 Document document = new Document(
                         pdf, new PageSize(width * pointsPerMm, height * pointsPerMm))) {
                for (ImageObject imageObject : surface.placedImages) {
                    ImageData imageData = ImageDataFactory.create(imageObject.getImagePath());
                    Image image = getImage(imageObject, imageData);

                    // Add image at fixed position
                    image.setFixedPosition(imageObject.getX1(), imageObject.getY1());
                    document.add(image);

                    // Add text under image
                    String caption = imageObject.getImagePath().substring(imageObject.getImagePath().lastIndexOf("/") + 1);


                    float textX = ((imageObject.getX2() - imageObject.getX1())/2) + imageObject.getX1() + caption.length()*2; // Centered
                    float textY = imageObject.getY1() - numberPointsForMm - 10; // Adjust to place below image

                    PdfDocument pdfDocument = document.getPdfDocument();
                    PdfCanvas pdfCanvas = new PdfCanvas(pdfDocument.getLastPage());
                    pdfCanvas.saveState();

                    // Apply a horizontal mirroring transformation
                    pdfCanvas.concatMatrix(-1, 0, 0, 1, textX * 2, 0);

                    Paragraph paragraph = new Paragraph(caption)
                            .setFontSize(11)
                            .setFixedPosition(textX, textY, 200);

                    // Add the mirrored paragraph to the document
                    document.add(paragraph);

                    pdfCanvas.restoreState();
                }

            }
            count++;
        }


    }

    private static List<String> splitArticleTypes(String articleTypes) {
        if (articleTypes == null || articleTypes.isEmpty()) {
            return new ArrayList<>();
        }
        // Split the string by commas and trim whitespace from each part
        articleTypes = articleTypes.replaceAll(" ", ""); // Remove all whitespace
        return Arrays.asList(articleTypes.split(","));
    }

    private static Image getImage(ImageObject imageObject, ImageData imageData) {
        Image image = new Image(imageData);
        if (imageObject.isRotated()) {
            image.setRotationAngle(Math.toRadians(90));
            image.setFixedPosition(imageObject.getX1(), imageObject.getY1());
            image.scaleToFit(imageObject.getY2() - imageObject.getY1(),
                    imageObject.getX2() - imageObject.getX1());
        } else {
            image.setFixedPosition(imageObject.getX1(), imageObject.getY1());
            image.scaleToFit(imageObject.getX2() - imageObject.getX1(),
                    imageObject.getY2() - imageObject.getY1());
        }
        return image;
    }

    private File[] getAllFilePaths(String directoryPath) throws Exception {
        try (Stream<Path> paths = Files.walk(Paths.get(directoryPath))) {
            return paths
                    .filter(Files::isRegularFile) // Behalte nur reguläre Dateien (keine Verzeichnisse)
                    .map(Path::toFile) // Konvertiere Path zu File
                    .toArray(File[]::new); // Sammle die Ergebnisse in ein Array
        }
    }

    private float[] getImageSize(String imagePath) {
        try {
            BufferedImage image = ImageIO.read(new File(imagePath));
            return new float[] { image.getWidth(), image.getHeight() };
        } catch (Exception e) {
            System.out.println("Error reading image: " + e.getMessage());
        }
        return new float[] { 0, 0 };
    }

    private float pixelsToMm(float pixels) {
        float dpi = 300;
        return pixels / dpi * 25.4f;
    }
}
