package com.xblasco.mavenprojects;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.*;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;

/**
 *
 * @author Xema
 *
 * Objetivos: generar un qr con foto
 */
public class CustomQr {

    public static void main(String[] args) throws WriterException, IOException {
        String texto = "https://bsky.app/profile/xblasco.com";
        int ancho = 600;
        int alto = 600;

        Map<EncodeHintType, Object> hints = new HashMap<>();
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // L, M, Q o H
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
        hints.put(EncodeHintType.MARGIN, 1); // opcional: grosor del margen blanco

        QRCodeWriter qrCodeWriter = new QRCodeWriter();
        BitMatrix bitMatrix = qrCodeWriter.encode(texto, BarcodeFormat.QR_CODE, ancho, alto, hints);

        Path path = FileSystems.getDefault().getPath("qr600.png");
        System.out.println(path.toAbsolutePath());
        MatrixToImageWriter.writeToPath(bitMatrix, "PNG", path);

        BufferedImage imagen = toBufferedImage(bitMatrix);
        File archivoSalida = new File("qr600r.png");
        ImageIO.write(imagen, "png", archivoSalida);
        
        //BufferedImage foto = leeFoto("avatarXBG.jpg", 600);
        BufferedImage foto = leeFoto("autumn.png", 600);
        
        BufferedImage fusion = fusiona(imagen, foto);
        File archivoFusionado = new File("salidaF.png");
        ImageIO.write(fusion, "png", archivoFusionado);
    }

    // Convierte el BitMatrix de ZXing en una imagen manualmente (más control que MatrixToImageWriter)
    private static BufferedImage toBufferedImage(BitMatrix matrix) {
        int width = matrix.getWidth();
        int height = matrix.getHeight();
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

        boolean puntoNegro = false;
        boolean bloqueNegro = false;
        boolean primeraColumna = false;
        int primerPuntoNegro = -1;
        int ultimoPuntoNegro = -1;
        boolean enPrimerSegmento = false;
        boolean esNegro = false;
        int tamanyoSegmento = 0;
        int tamanyoPrimerSegmento = 0;
        int minTamanyoSegmento = 1000;
        int numSegmentos = 0;
        boolean enSegmento = false;
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                esNegro = matrix.get(x, y);
                if (primeraColumna == false) {
                    if (esNegro) {
                        //Ultimo punto negro                     
                        ultimoPuntoNegro = y;
                        //Detecto primer punto negro
                        if (primerPuntoNegro < 0) {
                            primerPuntoNegro = y;
                        }

                        //Detecto inicio de segmento
                        if (enSegmento == false) {
                            enSegmento = true;
                            tamanyoSegmento = 0;
                            numSegmentos++;
                        }
                        tamanyoSegmento++;

                    } else { //esBlanco
                        //Detecto fin de segmento
                        if (enSegmento) {
                            //primer segmento
                            if (numSegmentos == 1) {
                                tamanyoPrimerSegmento = tamanyoSegmento;
                            }
                            if (tamanyoSegmento < minTamanyoSegmento) {
                                minTamanyoSegmento = tamanyoSegmento;
                            }
                        }
                        enSegmento = false;
                    }
                }

                image.setRGB(x, y, esNegro ? Color.BLACK.getRGB() : Color.WHITE.getRGB());
            }
        }
        System.out.println("Primer punto negro = " + primerPuntoNegro);
        System.out.println("Ultimo punto negro = " + ultimoPuntoNegro);
        System.out.println("Tamanyo primer segmento = " + tamanyoPrimerSegmento);
        System.out.println("Tamanyo minimo segmento = " + minTamanyoSegmento);
        int dePuntaAPunta = 1 + ultimoPuntoNegro - primerPuntoNegro;
        System.out.println("de punta a punta = " + dePuntaAPunta);
        System.out.println("Numero de puntos totales = " + (1.0 * dePuntaAPunta / minTamanyoSegmento));
        int numPuntos = Math.round((dePuntaAPunta / minTamanyoSegmento));
        System.out.println("Numero de puntos estimado = " + numPuntos);

        System.out.println("Poner puntos rojos en centro");

        puntosRojos(image, primerPuntoNegro, ultimoPuntoNegro, tamanyoPrimerSegmento, minTamanyoSegmento);

        return image;
    }

    static void puntosRojos(BufferedImage image, int y0, int yMax, int sizeGordo, int sizePunto) {
        System.out.println("y0 = " + y0 + ", yMax = " + yMax + ", sizeGordo = " + sizeGordo + ", sizePunto = " + sizePunto);
        for (int x = y0; x < yMax; x += sizePunto) {
            for (int y = y0; y < yMax; y += sizePunto) {
                boolean ok;

                ok = isOk(x, y, y0, yMax, sizeGordo, sizePunto);

                if (ok) {
                    for (int i = 0; i < 5; i++) {
                        for (int j = 0; j < 15; j++) {
                            image.setRGB(x + i, y + j, Color.BLUE.getRGB());
                        }
                    }
                    for (int i = 10; i < 15; i++) {
                        for (int j = 0; j < 15; j++) {
                            image.setRGB(x + i, y + j, Color.BLUE.getRGB());
                        }
                    }
                    for (int i = 5; i < 10; i++) {
                        for (int j = 0; j < 5; j++) {
                            image.setRGB(x + i, y + j, Color.BLUE.getRGB());
                        }
                    }
                    for (int i = 5; i < 10; i++) {
                        for (int j = 10; j < 15; j++) {
                            image.setRGB(x + i, y + j, Color.BLUE.getRGB());
                        }
                    }
                    for (int i = 5; i < 10; i++) {
                        for (int j = 5; j < 10; j++) {
                            if (image.getRGB(x + i, y + j) == Color.BLACK.getRGB()) {
                                //image.setRGB(x + i, y + j, Color.RED.getRGB());
                                image.setRGB(x + i, y + j, Color.BLACK.getRGB());
                            }
                        }

                    }
                }
            }
        }
    }

    static boolean isOk(int x, int y, int y0, int yMax, int sizeGordo, int sizePunto) {
        boolean ok = true;

        //Comprueba cuadrado top left
        if ((x < (y0 + sizeGordo + 1)) && (y < (y0 + sizeGordo + 1))) {
            ok = false;
        }
        //Comprueba cuadrado top right
        if (x > (yMax - sizeGordo - sizePunto) && (y < (y0 + sizeGordo + 1))) {
            ok = false;
        }
        //Cuadrado bottom left
        if ((x < (y0 + sizeGordo + 1)) && (y > (yMax - sizeGordo - sizePunto))) {
            ok = false;
        }
        //Columna discontinua
        if (x == (y0 + sizeGordo - sizePunto)) {
            ok = false;
        }
        //Fila discontinua
        if (y == (y0 + sizeGordo - sizePunto)) {
            ok = false;
        }
        //Cuadrado chiquitin
        if ((x > (yMax - sizeGordo - 2 * sizePunto))
                && (x < (yMax - sizeGordo + 3 * sizePunto))
                && (y > (yMax - sizeGordo - 2 * sizePunto))
                && (y < (yMax - sizeGordo + 3 * sizePunto))) {
            ok = false;
        }

        return ok;
    }

    static BufferedImage leeFoto(String archivo, int size) {
        BufferedImage ditherizada = null;
        try {
            File archivoEntrada = new File(archivo);
            BufferedImage imagen = ImageIO.read(archivoEntrada);
            for(int x=0; x< imagen.getWidth(); x++){
                for(int y=0; y<imagen.getHeight(); y++){
                    imagen.setRGB(x, y, rgbAGris(imagen.getRGB(x, y)));
                }
            }
            File archivoSalida1 = new File("salida1.png");
            ImageIO.write(imagen, "png", archivoSalida1);
            
            //y redimensiono
            BufferedImage redimensionada = redimensionar(imagen, size, size);
            ImageIO.write(redimensionada, "png", new File("salida2.png"));
            
            //Y diterizo
            
             ditherizada = ditherFloydSteinberg(redimensionada);
            ImageIO.write(ditherizada, "png", new File("salida3.png"));
            
        } catch (IOException ex) {
            System.getLogger(CustomQr.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
        }

        return ditherizada;
    }
    
    private static int rgbAGris(int argb) {
    int a = (argb >> 24) & 0xFF;
    int r = (argb >> 16) & 0xFF;
    int g = (argb >> 8) & 0xFF;
    int b = argb & 0xFF;

    int gris = (int) (0.299 * r + 0.587 * g + 0.114 * b);

    return (a << 24) | (gris << 16) | (gris << 8) | gris;
}
    
    public static BufferedImage redimensionar(BufferedImage original, int nuevoAncho, int nuevoAlto) {
    BufferedImage redimensionada = new BufferedImage(nuevoAncho, nuevoAlto, original.getType() != 0 ? original.getType() : BufferedImage.TYPE_INT_ARGB);

    Graphics2D g2d = redimensionada.createGraphics();
    g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.drawImage(original, 0, 0, nuevoAncho, nuevoAlto, null);
    g2d.dispose();

    return redimensionada;
}
    
    private static BufferedImage ditherFloydSteinberg(BufferedImage original) {
    int ancho = original.getWidth();
    int alto = original.getHeight();

    // Trabajamos con un array de doubles para acumular el error sin
    // desbordar ni perder precisión con cada redondeo
    double[][] pixeles = new double[alto][ancho];

    // Pasamos la imagen a gris primero
    for (int y = 0; y < alto; y++) {
        for (int x = 0; x < ancho; x++) {
            int rgb = original.getRGB(x, y);
            int r = (rgb >> 16) & 0xFF;
            int g = (rgb >> 8) & 0xFF;
            int b = rgb & 0xFF;
            pixeles[y][x] = 0.299 * r + 0.587 * g + 0.114 * b;
        }
    }

    BufferedImage resultado = new BufferedImage(ancho, alto, BufferedImage.TYPE_INT_ARGB);

    for (int y = 0; y < alto; y++) {
        for (int x = 0; x < ancho; x++) {
            double valorAntiguo = pixeles[y][x];
            double valorNuevo = (valorAntiguo < 128) ? 0 : 255;
            double error = valorAntiguo - valorNuevo;

            int color = (valorNuevo == 0) ? 0xFF000000 : 0xFFFFFFFF;
            resultado.setRGB(x, y, color);

            // Difundimos el error a los vecinos (patrón Floyd-Steinberg):
            //         X    7/16
            //  3/16  5/16  1/16
            if (x + 1 < ancho) {
                pixeles[y][x + 1] += error * 7.0 / 16.0;
            }
            if (x - 1 >= 0 && y + 1 < alto) {
                pixeles[y + 1][x - 1] += error * 3.0 / 16.0;
            }
            if (y + 1 < alto) {
                pixeles[y + 1][x] += error * 5.0 / 16.0;
            }
            if (x + 1 < ancho && y + 1 < alto) {
                pixeles[y + 1][x + 1] += error * 1.0 / 16.0;
            }
        }
    }

    return resultado;
}
    
    private static BufferedImage fusiona(BufferedImage imagen, BufferedImage foto){
        int xMax = imagen.getWidth();
        int yMax = imagen.getHeight();
        BufferedImage img = new BufferedImage(xMax, yMax, imagen.getType());
        for(int x=0; x<xMax; x++){
            for(int y=0; y<yMax; y++){
                if (imagen.getRGB(x,y) == Color.BLUE.getRGB()){
                    img.setRGB(x, y, foto.getRGB(x, y));
                } else {
                    img.setRGB(x, y, imagen.getRGB(x, y));
                }
            }
        }
        return img;
    }

}
