package com.foxx.androidcast.sender.calibration;

/*********************************************************************
 * TvCalibrationGenerator.java
 * Created at: Sat 16 May 2026 18:50:36 +0200
 * Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
 * Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
 * class TvCalibrationGenerator
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (3 commits, 173 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	bed472c91e11a096153068d8bc3fb9d7bf9582ca0f3b66ad6c84cb38b8a12b41
**********************************************************************/
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;

import com.foxx.androidcast.R;

/**
 * Draws a TV calibration chart (color bars + pluge) with gray reference areas transparent.
 */
public final class TvCalibrationGenerator {
    private static final int[] BAR_COLORS = {
            Color.WHITE, Color.YELLOW, Color.CYAN, Color.GREEN,
            Color.MAGENTA, Color.RED, Color.BLUE
    };

    private TvCalibrationGenerator() {}

    public static void drawChart(Canvas canvas, int width, int height, Context context) {
        if (context == null) {
            drawProceduralChart(canvas, width, height);
            return;
        }
        canvas.drawColor(Color.BLACK);
        Bitmap ref = loadReference(context);
        if (ref != null) {
            drawReferenceWithTransparentGray(canvas, ref, width, height);
            ref.recycle();
            return;
        }
        drawProceduralChart(canvas, width, height);
    }

    private static Bitmap loadReference(Context context) {
        try {
            return BitmapFactory.decodeResource(context.getResources(), R.drawable.calibration_chart_reference);
        } catch (Exception e) {
            return null;
        }
    }

    private static void drawReferenceWithTransparentGray(Canvas canvas, Bitmap ref, int w, int h) {
        int refW = ref.getWidth();
        int refH = ref.getHeight();
        float scale = Math.min((float) w / refW, (float) h / refH);
        int dw = Math.max(2, (int) (refW * scale)) & ~1;
        int dh = Math.max(2, (int) (refH * scale)) & ~1;
        Bitmap scaled = Bitmap.createScaledBitmap(ref, dw, dh, true);
        Bitmap keyed = scaled.copy(Bitmap.Config.ARGB_8888, true);
        scaled.recycle();
        int[] pixels = new int[dw * dh];
        keyed.getPixels(pixels, 0, dw, 0, 0, dw, dh);
        for (int i = 0; i < pixels.length; i++) {
            int p = pixels[i];
            int r = (p >> 16) & 0xff;
            int g = (p >> 8) & 0xff;
            int b = p & 0xff;
            if (isGray(r, g, b)) {
                pixels[i] = 0;
            }
        }
        keyed.setPixels(pixels, 0, dw, 0, 0, dw, dh);
        float left = (w - dw) / 2f;
        float top = (h - dh) / 2f;
        canvas.drawBitmap(keyed, left, top, null);
        keyed.recycle();
    }

    private static boolean isGray(int r, int g, int b) {
        return Math.abs(r - g) < 25 && Math.abs(g - b) < 25 && r > 40 && r < 200;
    }

    private static void drawProceduralChart(Canvas canvas, int width, int height) {
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        int barH = (int) (height * 0.55f);
        int barW = width / BAR_COLORS.length;
        for (int i = 0; i < BAR_COLORS.length; i++) {
            paint.setColor(BAR_COLORS[i]);
            canvas.drawRect(i * barW, 0, (i + 1) * barW, barH, paint);
        }
        int lowerTop = barH;
        int cols = 8;
        int cellW = width / cols;
        for (int c = 0; c < cols; c++) {
            int level = 255 - (c * 255 / (cols - 1));
            if (c == 0 || c == cols - 1) {
                continue;
            }
            paint.setColor(Color.rgb(level, level, level));
            canvas.drawRect(c * cellW, lowerTop, (c + 1) * cellW, height, paint);
        }
        paint.setColor(Color.WHITE);
        canvas.drawRect(0, lowerTop, cellW, height, paint);
        paint.setColor(Color.BLACK);
        canvas.drawRect((cols - 1) * cellW, lowerTop, width, height, paint);
    }

    /** Blue/white vertical stripe border with optional VU-driven stripe width (8dp border). */
    public static void drawAnimatedBorder(Canvas canvas, int width, int height, float density,
            int scrollPx, float vuLevel, Context context) {
        drawChart(canvas, width, height, context);
        drawBorderOverlay(canvas, width, height, density, scrollPx, vuLevel);
    }

    /** Draws only the animated border (chart must already be on the canvas / bitmap). */
    public static void drawBorderOverlay(Canvas canvas, int width, int height, float density,
            int scrollPx, float vuLevel) {
        int borderPx = Math.max(2, Math.round(8f * density));
        int stripe = Math.max(1, Math.round(1f * density));
        int maxScroll = width + height;
        int offset = scrollPx % Math.max(1, maxScroll);
        float vu = Math.max(0f, Math.min(1f, vuLevel));
        int blueWidth = Math.max(stripe, Math.round(stripe + vu * borderPx * 1.35f));
        drawStripeFrame(canvas, width, height, borderPx, stripe, blueWidth, offset);
    }

    private static void drawStripeFrame(Canvas canvas, int width, int height, int border,
            int stripe, int blueWidth, int offset) {
        Paint paint = new Paint();
        int cycle = stripe + blueWidth;
        RectF top = new RectF(0, 0, width, border);
        RectF bottom = new RectF(0, height - border, width, height);
        RectF left = new RectF(0, 0, border, height);
        RectF right = new RectF(width - border, 0, width, height);
        fillStripes(canvas, paint, top, true, offset, cycle, blueWidth);
        fillStripes(canvas, paint, bottom, true, offset, cycle, blueWidth);
        fillStripes(canvas, paint, left, false, offset, cycle, blueWidth);
        fillStripes(canvas, paint, right, false, offset, cycle, blueWidth);
    }

    private static void fillStripes(Canvas canvas, Paint paint, RectF area, boolean horizontal,
            int offset, int cycle, int blueWidth) {
        if (horizontal) {
            int h = Math.max(1, Math.round(area.height()));
            for (float x = area.left; x < area.right; ) {
                int pos = (int) (x + area.top + offset);
                int color = stripeColor(pos, cycle, blueWidth);
                float next = Math.min(area.right, x + 1f);
                paint.setColor(color);
                canvas.drawRect(x, area.top, next, area.top + h, paint);
                x = next;
            }
        } else {
            int w = Math.max(1, Math.round(area.width()));
            for (float y = area.top; y < area.bottom; ) {
                int pos = (int) (area.left + y + offset);
                int color = stripeColor(pos, cycle, blueWidth);
                float next = Math.min(area.bottom, y + 1f);
                paint.setColor(color);
                canvas.drawRect(area.left, y, area.left + w, next, paint);
                y = next;
            }
        }
    }

    private static int stripeColor(int pos, int cycle, int blueWidth) {
        int m = pos % cycle;
        return m < blueWidth ? Color.rgb(30, 144, 255) : Color.WHITE;
    }
}
