package hr.com.port.ips.eracun.hub3;

import java.awt.image.BufferedImage;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Locale;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import org.krysalis.barcode4j.impl.pdf417.PDF417Bean;
import org.krysalis.barcode4j.output.bitmap.BitmapCanvasProvider;

// HUB-3A PDF417 payload builder + image renderer.
// Implements the official spec (ver. 6): UTF-8, 14 fields separated by LF, fixed max lengths, amount in cents, etc.

public final class Hub3Pdf417 {

    private static final Logger logger = Logger.getLogger(Hub3Pdf417.class);

    private static final Pattern ALLOWED = Pattern.compile("[0-9A-Za-zČčĆćĐđŠšŽžQWXYq wxy ,\\.\\:\\-\\+\\?'\\(\\)/]*");
    private static final char LF = '\n';

    private Hub3Pdf417() {}

    // Build payload string (UTF-8 later) according to HUB-3A spec.
    public static String buildPayload(Hub3Pdf417Data d) {
        try {
            d.requireNonNulls();

            final String header = "HRVHUB30"; // fixed value
            final String currency = "EUR";     // fixed value

            String amountCents = toCents15(d.getAmountEur()); // 15 chars, zero-padded

            String payerName        = limitAndSanitize(d.getPayerName(), 30);
            String payerAddress     = limitAndSanitize(d.getPayerAddress(), 27);
            String payerCity        = limitAndSanitize(d.getPayerCity(), 27);

            String payeeName        = limitAndSanitize(d.getPayeeName(), 25);
            String payeeAddress     = limitAndSanitize(d.getPayeeAddress(), 25);
            String payeeCity        = limitAndSanitize(d.getPayeeCity(), 27);

            String iban             = limitAndSanitize(d.getIban(), 21);
            String model            = limitAndSanitize(d.getModel(), 4);
            String reference        = limitAndSanitize(d.getReference(), 22);
            String purpose          = limitAndSanitize(d.getPurposeCode(), 4).toUpperCase(Locale.ROOT);
            String description      = limitAndSanitize(d.getDescription(), 35);

            StringBuilder sb = new StringBuilder(400);
            // 14 lines, each ends with LF
            sb.append(header).append(LF);
            sb.append(currency).append(LF);
            sb.append(amountCents).append(LF);
            sb.append(payerName).append(LF);
            sb.append(payerAddress).append(LF);
            sb.append(payerCity).append(LF);
            sb.append(payeeName).append(LF);
            sb.append(payeeAddress).append(LF);
            sb.append(payeeCity).append(LF);
            sb.append(iban).append(LF);
            sb.append(model).append(LF);
            sb.append(reference).append(LF);
            sb.append(purpose).append(LF);
            sb.append(description).append(LF);
            // UTF-8 note: diacritics Č,č,Ć,ć,Đ,đ,Š,š,Ž,ž are allowed by spec and will be 2 bytes each.

            return sb.toString();
        } catch (Exception ex) {
            // logger.error(new Functions().logging(ex));
            logger.error("Failed to build HUB3A payload", ex);
            throw ex;
        }
    }

    // Render a PDF417 as monochrome BufferedImage (TYPE_BYTE_BINARY) at given DPI. 
    public static BufferedImage renderPdf417(String payload, int dpi) {
        try {
            // Configure PDF417 bean per spec:
            PDF417Bean bean = new PDF417Bean();
            bean.doQuietZone(true);
            bean.setModuleWidth(0.254); // mm (10 mil)
            bean.setMinCols(9);
            bean.setMaxCols(9);
            bean.setErrorCorrectionLevel(4);
            // Default row-height factor = 3 (height:width ratio 3:1), per Barcode4J defaults.

            // For BitmapCanvasProvider variant that gives a BufferedImage directly:
            BitmapCanvasProvider canvas = new BitmapCanvasProvider(
                    dpi,
                    BufferedImage.TYPE_BYTE_BINARY,
                    false, // antiAlias off for crisp edges
                    0      // orientation
            );

            // Ensure UTF-8 by passing the exact Java String (Java string is Unicode; Barcode4J will encode as UTF-8 for PDF417)
            bean.generateBarcode(canvas, payload);
            canvas.finish();
            return canvas.getBufferedImage();
        } catch (Exception ex) {
            // logger.error(new Functions().logging(ex));
            logger.error("Failed to render HUB3A PDF417 image", ex);
            throw new RuntimeException("PDF417 rendering failed", ex);
        }
    }

    // ==== helpers ====

    private static String toCents15(BigDecimal amountEur) {
        BigDecimal cents = amountEur.setScale(2, RoundingMode.HALF_UP).movePointRight(2).abs();
        String s = cents.toBigIntegerExact().toString();
        if (s.length() > 15) {
            throw new IllegalArgumentException("Iznos u centima prelazi 15 znamenaka: " + s.length());
        }
        return leftPadZeros(s, 15);
    }

    private static String leftPadZeros(String s, int len) {
        if (s.length() >= len) return s;
        StringBuilder sb = new StringBuilder(len);
        for (int i = s.length(); i < len; i++) sb.append('0');
        sb.append(s);
        return sb.toString();
    }

    private static String limitAndSanitize(String s, int maxLen) {
        if (s == null) return "";
        String trimmed = s.trim();
        // Remove disallowed characters per spec
        if (!ALLOWED.matcher(trimmed).matches()) {
            StringBuilder ok = new StringBuilder(trimmed.length());
            for (int i = 0; i < trimmed.length(); i++) {
                char c = trimmed.charAt(i);
                if (isAllowed(c)) ok.append(c);
                // else drop
            }
            trimmed = ok.toString();
        }
        // Enforce max length in characters (not bytes)
        if (trimmed.length() > maxLen) {
            trimmed = trimmed.substring(0, maxLen);
        }
        return trimmed;
    }

    private static boolean isAllowed(char c) {
        // Digits and letters are fine
        if (Character.isDigit(c) || Character.isLetter(c)) return true;
        // Croatian diacritics explicitly allowed even if not matched by isLetter in some locales
        switch (c) {
            case 'Č': case 'č': case 'Ć': case 'ć':
            case 'Đ': case 'đ': case 'Š': case 'š':
            case 'Ž': case 'ž':
            case ' ': case ',': case '.': case ':':
            case '-': case '+': case '?': case '\'':
            case '/': case '(': case ')':
                return true;
            default: return false;
        }
    }
}
