package com.foxx.androidcast.sender;

/*********************************************************************
 * SenderCapturePreview.java
 * Created at: Sun 17 May 2026 18:17:14 +0200
 * Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
 * Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
 * class SenderCapturePreview
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (4 commits, 308 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	f929bdc17ad56e989460b0d6d090ee734db470b5b08d7931d22f5ece19040058
**********************************************************************/
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Canvas;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.view.TextureView;
import android.widget.TextView;

import androidx.core.content.ContextCompat;

import com.foxx.androidcast.CastActiveState;
import com.foxx.androidcast.CastSettings;
import com.foxx.androidcast.R;
import com.foxx.androidcast.sender.calibration.TvCalibrationGenerator;

import java.util.Collections;

/** Live preview of the configured capture source on the sender screen (not the cast pipeline). */
public final class SenderCapturePreview {
    private static final String TAG = "SenderCapturePreview";

    private final TextureView textureView;
    private final TextView labelView;
    private final SenderScreenPreview screenPreview;
    private HandlerThread thread;
    private Handler handler;
    private CameraDevice camera;
    private android.hardware.camera2.CameraCaptureSession session;
    private boolean useFront;
    private CastSettings.CaptureMode activeMode = CastSettings.CaptureMode.FULL_SCREEN;

    public SenderCapturePreview(TextureView texture, TextView label, SenderScreenPreview screen) {
        this.textureView = texture;
        this.labelView = label;
        this.screenPreview = screen;
        textureView.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
            if (right - left > 0 && bottom - top > 0
                    && screenPreview != null
                    && screenPreview.needsProjection(activeMode)
                    && screenPreview.hasConsent()) {
                labelView.setVisibility(android.view.View.GONE);
                textureView.setVisibility(android.view.View.VISIBLE);
                screenPreview.attach(textureView, activeMode);
            }
        });
    }

    public SenderScreenPreview getScreenPreview() {
        return screenPreview;
    }

    public void updateForMode(CastSettings.CaptureMode mode) {
        activeMode = mode;
        stopCamera();
        if (CastActiveState.isSenderCasting()) {
            showCastingPlaceholder();
            return;
        }
        if (mode == CastSettings.CaptureMode.CAMERA) {
            useFront = false;
            startCameraPreview();
        } else if (mode == CastSettings.CaptureMode.CAMERA_FRONT) {
            useFront = true;
            startCameraPreview();
        } else if (mode == CastSettings.CaptureMode.CALIBRATION_TEST) {
            showCalibrationPreview();
        } else if (screenPreview != null && screenPreview.needsProjection(mode)) {
            if (screenPreview.hasConsent()) {
                labelView.setVisibility(android.view.View.GONE);
                textureView.setVisibility(android.view.View.VISIBLE);
                screenPreview.attach(textureView, mode);
            } else {
                showLabel(R.string.preview_tap_for_screen);
            }
        } else {
            showLabel(R.string.preview_placeholder);
        }
    }

    private void showLabel(int textRes) {
        if (screenPreview != null) {
            screenPreview.stop();
        }
        textureView.setVisibility(android.view.View.GONE);
        labelView.setVisibility(android.view.View.VISIBLE);
        labelView.setText(labelView.getContext().getString(textRes));
    }

    public void showCastingPlaceholder() {
        if (screenPreview != null) {
            screenPreview.pausePreview();
        }
        stopCamera();
        textureView.setVisibility(android.view.View.GONE);
        labelView.setVisibility(android.view.View.VISIBLE);
        labelView.setText(labelView.getContext().getString(R.string.preview_casting_active));
    }

    private void showCalibrationPreview() {
        if (screenPreview != null) {
            screenPreview.stop();
        }
        labelView.setVisibility(android.view.View.GONE);
        textureView.setVisibility(android.view.View.VISIBLE);
        textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                drawCalibrationFrame();
            }

            @Override
            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
                drawCalibrationFrame();
            }

            @Override
            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                return true;
            }

            @Override
            public void onSurfaceTextureUpdated(SurfaceTexture surface) {}
        });
        if (textureView.isAvailable()) {
            drawCalibrationFrame();
        }
    }

    private void drawCalibrationFrame() {
        int w = Math.max(320, textureView.getWidth());
        int h = Math.max(240, textureView.getHeight());
        Canvas canvas = textureView.lockCanvas();
        if (canvas == null) {
            return;
        }
        try {
            TvCalibrationGenerator.drawChart(canvas, w, h, textureView.getContext());
        } finally {
            textureView.unlockCanvasAndPost(canvas);
        }
    }

    private void startCameraPreview() {
        if (screenPreview != null) {
            screenPreview.stop();
        }
        Context ctx = textureView.getContext();
        if (ContextCompat.checkSelfPermission(ctx, Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            showLabel(R.string.capture_camera_permission_denied);
            return;
        }
        labelView.setVisibility(android.view.View.GONE);
        textureView.setVisibility(android.view.View.VISIBLE);
        if (textureView.isAvailable()) {
            openCamera(textureView.getSurfaceTexture());
        } else {
            textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
                @Override
                public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                    openCamera(surface);
                }

                @Override
                public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}

                @Override
                public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                    stop();
                    return true;
                }

                @Override
                public void onSurfaceTextureUpdated(SurfaceTexture surface) {}
            });
        }
    }

    private void openCamera(SurfaceTexture surface) {
        Context ctx = textureView.getContext().getApplicationContext();
        thread = new HandlerThread("SenderPreviewCam");
        thread.start();
        handler = new Handler(thread.getLooper());
        try {
            CameraManager mgr = ctx.getSystemService(CameraManager.class);
            String id = chooseCamera(mgr, useFront);
            if (id == null) {
                showLabel(R.string.capture_camera_open_failed);
                return;
            }
            surface.setDefaultBufferSize(640, 480);
            mgr.openCamera(id, new CameraDevice.StateCallback() {
                @Override
                public void onOpened(CameraDevice device) {
                    camera = device;
                    try {
                        android.view.Surface previewSurface = new android.view.Surface(surface);
                        CaptureRequest.Builder b =
                                device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                        b.addTarget(previewSurface);
                        device.createCaptureSession(Collections.singletonList(previewSurface),
                                new android.hardware.camera2.CameraCaptureSession.StateCallback() {
                                    @Override
                                    public void onConfigured(
                                            android.hardware.camera2.CameraCaptureSession s) {
                                        session = s;
                                        try {
                                            s.setRepeatingRequest(b.build(), null, handler);
                                        } catch (CameraAccessException e) {
                                            Log.w(TAG, "Preview request failed", e);
                                        }
                                    }

                                    @Override
                                    public void onConfigureFailed(
                                            android.hardware.camera2.CameraCaptureSession s) {
                                        Log.w(TAG, "Preview session failed");
                                    }
                                }, handler);
                    } catch (CameraAccessException e) {
                        Log.w(TAG, "Preview session", e);
                    }
                }

                @Override
                public void onDisconnected(CameraDevice device) {
                    device.close();
                }

                @Override
                public void onError(CameraDevice device, int error) {
                    device.close();
                }
            }, handler);
        } catch (CameraAccessException | SecurityException e) {
            Log.w(TAG, "Open camera failed", e);
            showLabel(R.string.capture_camera_open_failed);
        }
    }

    private static String chooseCamera(CameraManager mgr, boolean front) throws CameraAccessException {
        int want = front ? CameraCharacteristics.LENS_FACING_FRONT
                : CameraCharacteristics.LENS_FACING_BACK;
        for (String id : mgr.getCameraIdList()) {
            Integer facing = mgr.getCameraCharacteristics(id)
                    .get(CameraCharacteristics.LENS_FACING);
            if (facing != null && facing == want) {
                return id;
            }
        }
        String[] ids = mgr.getCameraIdList();
        return ids.length > 0 ? ids[0] : null;
    }

    public void pause() {
        stopCamera();
        if (screenPreview != null) {
            screenPreview.pausePreview();
        }
    }

    public void stop() {
        stopCamera();
        if (screenPreview != null) {
            screenPreview.stop();
        }
    }

    private void stopCamera() {
        if (session != null) {
            try {
                session.close();
            } catch (Exception ignored) {
            }
            session = null;
        }
        if (camera != null) {
            try {
                camera.close();
            } catch (Exception ignored) {
            }
            camera = null;
        }
        if (thread != null) {
            thread.quitSafely();
            thread = null;
        }
        handler = null;
    }
}
