package com.server.api.service.flaechendruck;

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 com.server.api.models.Coordinates;
import com.server.api.models.VorlageFlaechendruck;
import java.awt.image.BufferedImage;
import java.io.File;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class VorlagenFlaechendruckPDFGeneratorService {
  @Autowired
  private VorlageFlaechendruckService vorlageFlaechendruckService;

  public void generateFlaechendruckPDFs(long vorlageId, String outputDir, String uploadDir)
      throws Exception {
    VorlageFlaechendruck vorlage = vorlageFlaechendruckService.getVorlageById(vorlageId);

    String imagePath = uploadDir;

    Path outputFolder = Paths.get(outputDir);
    if (!Files.exists(outputFolder)) {
      Files.createDirectories(outputFolder);
    }
    PdfGenerator pdfGenerator = new PdfGenerator(imagePath, outputDir);
    pdfGenerator.generatePdf(vorlage);
  }

  public class PdfGenerator {
    private String imagePath;
    private String pdfPath;

      public class Article {
      private String imagePath;
      private String color;
      private String productType;

      public Article(String imagePath, String color, String productType) {
        this.imagePath = imagePath;
        this.color = color;
        this.productType = productType;
      }

      public String getImagePath() {
        return imagePath;
      }

      public String getColor() {
        return color;
      }

      public String getProductType() {
        return productType;
      }
    }

    public class ColorCount {
      private String color;
      private int count;

      public ColorCount(String color, int count) {
        this.color = color;
        this.count = count;
      }

      public String getColor() {
        return color;
      }

      public int getCount() {
        return count;
      }

      public void incrementCount() {
        this.count++;
      }

      public void decrementCount() {
        this.count--;
      }
    }

    public PdfGenerator(String imagePath, String pdfPath) {
      this.imagePath = imagePath;
      this.pdfPath = pdfPath;
    }

    public void generatePdf(VorlageFlaechendruck vorlage) throws Exception {

      // Bild hinzufügen
      File folder = new File(imagePath);
      File[] files = folder.listFiles();
      files = getAllFilePaths(imagePath);

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

      // only keep files with the correct product type
      files = Arrays.stream(files)
          .filter(file -> file.getName().startsWith(vorlage.getProduct_type().toLowerCase()))
          .toArray(File[]::new);

      // Sort files by color
      Article[] articles = createArticlesOrder(files, vorlage.getCoordinates().size());

      Coordinates[] coordinates = vorlage.getCoordinates().toArray(new Coordinates[0]);
      float height = vorlage.getHeight();
      float width = vorlage.getWidth();
      int count = 0;
      for (int i = 0; i < articles.length; i += coordinates.length) {
        count++;

        ArrayList<ColorCount> colorCounts = new ArrayList<>();
          float pointsPerMm = 2.83465f;
          try (PdfWriter writer = new PdfWriter(pdfPath + count + ".pdf");
               PdfDocument pdf = new PdfDocument(writer);
               Document document = new Document(
                pdf, new PageSize(width * pointsPerMm, height * pointsPerMm))) {
          for (int j = 0; j < coordinates.length; j++) {
            if (i + j >= articles.length) {
              break;
            }
            boolean found = false;
            for (ColorCount colorCount : colorCounts) {
              if (colorCount.getColor().equals(articles[i + j].getColor())) {
                colorCount.incrementCount();
                found = true;
                break;
              }
            }
            if (!found) {
              colorCounts.add(new ColorCount(articles[i + j].getColor(), 1));
            }

            String imagePathTemp = articles[i + j].getImagePath();
            ImageData imageData = ImageDataFactory.create(imagePathTemp);
            Image image = new Image(imageData);

            double x = coordinates[j].getX() * pointsPerMm;
            double y = coordinates[j].getY() * pointsPerMm;
            double rotation = coordinates[j].getRotation();

            float[] imageSize = getImageSize(imagePathTemp);

            float imageWidth = pixelsToMm(imageSize[0]);
            float imageHeight = pixelsToMm(imageSize[1]);

            if (rotation != 0) {
              image.setRotationAngle(Math.toRadians(rotation));
              // Swap width and height if the image is rotated
            }

            image.scaleToFit(imageWidth * pointsPerMm, imageHeight * pointsPerMm);

            // Adjust x and y to center the image at the given coordinates
            if (rotation != 0) {
              float temp = imageWidth;
              imageWidth = imageHeight;
              imageHeight = temp;
            }

            double adjustedX = x - (imageWidth * pointsPerMm) / 2;
            double adjustedY = y - (imageHeight * pointsPerMm) / 2;

            image.setFixedPosition((float) adjustedX, (float) adjustedY);
            document.add(image);
          }
        }
        String saveName = compressString(colorCounts, articles[0].getProductType());

        // Datei umbenennen
        String tempFileName = pdfPath + count + ".pdf";
        File tempFile = new File(tempFileName);
        File renamedFile = new File(pdfPath + count + ". " + saveName + ".pdf");
        if (!tempFile.renameTo(renamedFile)) {
          throw new Exception("Internal Server Error");
        }
      }
    }

    private Article[] createArticlesOrder(File[] files, int cors_len) throws Exception {
      ArrayList<Article> articles = new ArrayList<>();
      ArrayList<ColorCount> colorCounts = new ArrayList<>();

      for (int i = 0; i < files.length; i++) {
        articles.add(getArticleProperties(files[i].getAbsolutePath()));
        boolean found = false;
        for (ColorCount colorCount : colorCounts) {
          if (colorCount.getColor().equals(articles.get(i).getColor())) {
            colorCount.incrementCount();
            found = true;
            break;
          }
        }
        if (!found) {
          colorCounts.add(new ColorCount(articles.get(i).getColor(), 1));
        }
      }
      ArrayList<Article> sortedArticles = new ArrayList<>();

      int index = 0;
      while (index < colorCounts.size()) {
        if (colorCounts.get(index).getCount() >= cors_len) {
          int num_added = 0;
          ArrayList<Article> removeArticles = new ArrayList<>();
          for (Article artc : articles) {
            if (artc.getColor().equals(colorCounts.get(index).getColor())) {
              sortedArticles.add(artc);
              removeArticles.add(artc);
              colorCounts.get(index).decrementCount();
              num_added++;
            }
            if (num_added >= cors_len) {
              break;
            }
          }
          articles.removeAll(removeArticles);
        } else {
          index++;
        }
      }
      // sort articles by color
      articles.sort((a, b) -> a.getColor().compareTo(b.getColor()));

      for (Article artc : articles) {

        sortedArticles.add(artc);

      }

      // transform ArrayList to Array
      Article[] sortedArticlesArray = new Article[sortedArticles.size()];
      for (int i = 0; i < sortedArticles.size(); i++) {
        sortedArticlesArray[i] = sortedArticles.get(i);
      }
      return sortedArticlesArray;
    }

    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 String compressString(ArrayList<ColorCount> colorCounts, String productType) {
      StringBuilder result = new StringBuilder();
      for (ColorCount colorCount : colorCounts) {
        result.append(colorCount.getCount()); // Häufigkeit
        result.append(productType); // Produkttyp
        result.append(" ");
        result.append(colorCount.getColor()); // Farbe
        result.append(" ");
      }
      // Entferne das letzte Leerzeichen
      if (result.length() > 0) {
        result.setLength(result.length() - 1);
      }

      return result.toString();
    }

    private Article getArticleProperties(String imagePath) throws Exception {
      String filename = new File(imagePath).getName();
      String farben = "abcdefghijklmnopqrstuvwxyz";

      String productType = "";
      int i = 0;

      while (true) {
        if (Character.isDigit(filename.charAt(i))) {
          break;
        }
        productType += filename.charAt(i);
        i++;
      }
      while (true) {
        if (!Character.isDigit(filename.charAt(i))) {
          break;
        }
        i++;
      }

      String color = String.valueOf(filename.charAt(i));

      // for colors with two letters
      if (farben.indexOf(filename.charAt(i + 1)) != -1) {
        color += String.valueOf(filename.charAt(i + 1));
      }

      // Prüfe, ob der Buchstabe in der Liste der Farben enthalten ist
      if (farben.indexOf(color) == -1 && color.length() != 2) {
        throw new Exception("Nicht alle Bilder enthalten Farbbuchstaben!");
      }

      return new Article(imagePath, color, productType);
    }

    private float[] getImageSize(String imagePath) {
      try {
        BufferedImage image = ImageIO.read(new File(imagePath));
        return new float[] { image.getWidth(), image.getHeight() };
      } catch (Exception e) {

        return new float[] { 0, 0 };
      }
    }

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