package com.zhongqu.vrviewer.demojava;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.net.Uri;
import android.os.Bundle;
import android.util.Patterns;
import android.view.Surface;
import android.view.TextureView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.google.mlkit.vision.barcode.BarcodeScanner;
import com.google.mlkit.vision.barcode.BarcodeScanning;
import com.google.mlkit.vision.barcode.BarcodeScannerOptions;
import com.google.mlkit.vision.barcode.common.Barcode;
import com.google.mlkit.vision.common.InputImage;

import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 旧版 {@link android.hardware.Camera} 预览 + ML Kit 只读 QR，兼容 minSdk 19+（不依赖 CameraX）。 NV21
 * 经 {@link InputImage#fromByteArray} 送入检测。
 */
@SuppressWarnings("deprecation")
public class QrScanActivity extends AppCompatActivity {

    public static final String EXTRA_TEXT = "extra_scanned_tour_url";

    @NonNull
    public static Intent createIntent(@NonNull Context ctx) {
        return new Intent(ctx, QrScanActivity.class);
    }

    private static final int REQ_CODE_CAMERA = 0x4c01;

    private final AtomicBoolean finished = new AtomicBoolean(false);
    private volatile boolean isProcessing;
    private volatile long lastSubmitMs;

    @Nullable
    private TextureView textureView;
    @Nullable
    private Camera camera;
    private int cameraId = -1;
    private int previewW;
    private int previewH;
    private int inputRotation;
    @Nullable
    private BarcodeScanner scanner;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_qr_scan);
        Toolbar toolbar = findViewById(R.id.scan_toolbar);
        setSupportActionBar(toolbar);
        if (getSupportActionBar() != null) {
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        }
        toolbar.setNavigationOnClickListener(v -> finish());
        textureView = findViewById(R.id.preview_view);
        scanner =
                BarcodeScanning.getClient(
                        new BarcodeScannerOptions.Builder()
                                .setBarcodeFormats(Barcode.FORMAT_QR_CODE)
                                .build());

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(
                    this, new String[] {Manifest.permission.CAMERA}, REQ_CODE_CAMERA);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                        == PackageManager.PERMISSION_GRANTED
                && !finished.get()
                && camera == null) {
            maybeOpenCamera();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (!finished.get()) {
            releaseCamera();
        }
    }

    private void maybeOpenCamera() {
        if (isFinishing() || (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1 && isDestroyed()) || camera != null) {
            return;
        }
        TextureView tv = textureView;
        if (tv == null) {
            return;
        }
        if (tv.isAvailable()) {
            openCameraInternal(tv);
        } else if (tv.getSurfaceTextureListener() == null) {
            tv.setSurfaceTextureListener(
                    new TextureView.SurfaceTextureListener() {
                        @Override
                        public void onSurfaceTextureAvailable(
                                @NonNull SurfaceTexture surface, int w, int h) {
                            openCameraInternal(tv);
                        }

                        @Override
                        public void onSurfaceTextureSizeChanged(
                                @NonNull SurfaceTexture surface, int w, int h) {}

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

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

    private void openCameraInternal(@NonNull TextureView tv) {
        if (isFinishing() || (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1 && isDestroyed()) || camera != null
                || finished.get()) {
            return;
        }
        if (Camera.getNumberOfCameras() == 0) {
            runOnUiThread(
                    () -> {
                        Toast.makeText(this, R.string.toast_scan_no_camera, Toast.LENGTH_LONG)
                                .show();
                        setResult(RESULT_CANCELED);
                        finish();
                    });
            return;
        }
        cameraId = findBackCameraId(0);
        Camera cam;
        try {
            cam = Camera.open(cameraId);
        } catch (Exception e) {
            runOnUiThread(
                    () -> {
                        String msg = e.getMessage() != null ? e.getMessage() : "?";
                        Toast.makeText(
                                        QrScanActivity.this,
                                        getString(R.string.toast_scan_camera_error, msg),
                                        Toast.LENGTH_LONG)
                                .show();
                        setResult(RESULT_CANCELED);
                        finish();
                    });
            return;
        }
        this.camera = cam;
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        int displayRot = displaySurfaceRotationDegrees();
        int displayOrientation;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            int r = (info.orientation + displayRot) % 360;
            r = (360 - r) % 360;
            displayOrientation = r;
        } else {
            displayOrientation = (info.orientation - displayRot + 360) % 360;
        }
        cam.setDisplayOrientation(displayOrientation);
        inputRotation = displayOrientation;
        Camera.Parameters params;
        try {
            params = cam.getParameters();
        } catch (Exception e) {
            runOnUiThread(
                    () -> {
                        releaseCamera();
                        String m = e.getMessage() != null ? e.getMessage() : "?";
                        Toast.makeText(
                                        QrScanActivity.this,
                                        getString(R.string.toast_scan_camera_error, m),
                                        Toast.LENGTH_LONG)
                                .show();
                        setResult(RESULT_CANCELED);
                        finish();
                    });
            return;
        }
        if (params.getSupportedFocusModes() != null
                && params.getSupportedFocusModes()
                        .contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
            params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
        }
        Camera.Size size = pickPreviewSize(params);
        if (size == null) {
            runOnUiThread(
                    () -> {
                        releaseCamera();
                        Toast.makeText(
                                        this,
                                        R.string.toast_scan_no_preview_size,
                                        Toast.LENGTH_LONG)
                                .show();
                        setResult(RESULT_CANCELED);
                        finish();
                    });
            return;
        }
        params.setPreviewSize(size.width, size.height);
        params.setPreviewFormat(ImageFormat.NV21);
        try {
            cam.setParameters(params);
        } catch (Exception e) {
            runOnUiThread(
                    () -> {
                        releaseCamera();
                        String m = e.getMessage() != null ? e.getMessage() : "?";
                        Toast.makeText(
                                        QrScanActivity.this,
                                        getString(R.string.toast_scan_camera_error, m),
                                        Toast.LENGTH_LONG)
                                .show();
                        setResult(RESULT_CANCELED);
                        finish();
                    });
            return;
        }
        previewW = size.width;
        previewH = size.height;
        int bufSize =
                previewW
                        * previewH
                        * ImageFormat.getBitsPerPixel(ImageFormat.NV21)
                        / 8;
        for (int i = 0; i <= 1; i++) {
            cam.addCallbackBuffer(new byte[bufSize]);
        }
        cam.setPreviewCallbackWithBuffer(
                (data, c) -> {
                    if (data == null) {
                        return;
                    }
                    c.addCallbackBuffer(data);
                    if (finished.get()) {
                        return;
                    }
                    long now = System.currentTimeMillis();
                    if (now - lastSubmitMs < 200L) {
                        return;
                    }
                    if (isProcessing) {
                        return;
                    }
                    isProcessing = true;
                    lastSubmitMs = now;
                    processNv21(data);
                });
        try {
            cam.setPreviewTexture(tv.getSurfaceTexture());
        } catch (Exception e) {
            runOnUiThread(
                    () -> {
                        releaseCamera();
                        String m = e.getMessage() != null ? e.getMessage() : "?";
                        Toast.makeText(
                                        QrScanActivity.this,
                                        getString(R.string.toast_scan_camera_error, m),
                                        Toast.LENGTH_LONG)
                                .show();
                        setResult(RESULT_CANCELED);
                        finish();
                    });
            return;
        }
        try {
            cam.startPreview();
        } catch (Exception e) {
            runOnUiThread(
                    () -> {
                        releaseCamera();
                        String m = e.getMessage() != null ? e.getMessage() : "?";
                        Toast.makeText(
                                        QrScanActivity.this,
                                        getString(R.string.toast_scan_camera_error, m),
                                        Toast.LENGTH_LONG)
                                .show();
                        setResult(RESULT_CANCELED);
                        finish();
                    });
        }
    }

    @SuppressWarnings("deprecation")
    private int displaySurfaceRotationDegrees() {
        if (getWindowManager() == null
                || getWindowManager().getDefaultDisplay() == null) {
            return 0;
        }
        int r = getWindowManager().getDefaultDisplay().getRotation();
        if (r == Surface.ROTATION_0) {
            return 0;
        } else if (r == Surface.ROTATION_90) {
            return 90;
        } else if (r == Surface.ROTATION_180) {
            return 180;
        } else {
            return 270;
        }
    }

    private int findBackCameraId(int def) {
        int n = Camera.getNumberOfCameras();
        for (int i = 0; i < n; i++) {
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(i, info);
            if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                return i;
            }
        }
        if (n > 0) {
            return 0;
        }
        return def;
    }

    @Nullable
    private Camera.Size pickPreviewSize(Camera.Parameters p) {
        List<Camera.Size> sup = p.getSupportedPreviewSizes();
        if (sup == null) {
            return null;
        }
        Camera.Size best = null;
        int bestDiff = Integer.MAX_VALUE;
        for (Camera.Size s : sup) {
            int w = s.width;
            int h = s.height;
            int diff = Math.abs(w * h - 640 * 480);
            if (diff < bestDiff) {
                bestDiff = diff;
                best = s;
            }
        }
        return best;
    }

    private void processNv21(@NonNull byte[] data) {
        BarcodeScanner s = scanner;
        if (s == null) {
            isProcessing = false;
            return;
        }
        int w = previewW;
        int h = previewH;
        if (w == 0 || h == 0) {
            isProcessing = false;
            return;
        }
        final InputImage input;
        try {
            input = InputImage.fromByteArray(data, w, h, inputRotation, ImageFormat.NV21);
        } catch (Exception e) {
            isProcessing = false;
            return;
        }
        s.process(input)
                .addOnSuccessListener(
                        (List<Barcode> list) -> {
                            if (finished.get()) {
                                isProcessing = false;
                                return;
                            }
                            for (Barcode b : list) {
                                String raw = b.getRawValue() != null ? b.getRawValue().trim() : null;
                                if (raw == null) {
                                    continue;
                                }
                                if (!isPlausibleTourUrl(raw)) {
                                    continue;
                                }
                                if (finished.compareAndSet(false, true)) {
                                    runOnUiThread(
                                            () -> {
                                                releaseCamera();
                                                setResult(
                                                        RESULT_OK,
                                                        new Intent().putExtra(EXTRA_TEXT, raw));
                                                finish();
                                            });
                                } else {
                                    isProcessing = false;
                                }
                                return;
                            }
                            isProcessing = false;
                        })
                .addOnFailureListener(e -> isProcessing = false);
    }

    @Override
    public void onRequestPermissionsResult(
            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode != REQ_CODE_CAMERA) {
            return;
        }
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            maybeOpenCamera();
        } else {
            Toast.makeText(this, R.string.toast_scan_camera_denied, Toast.LENGTH_LONG).show();
            setResult(RESULT_CANCELED);
            finish();
        }
    }

    private void releaseCamera() {
        isProcessing = false;
        Camera c = this.camera;
        this.camera = null;
        if (c == null) {
            return;
        }
        try {
            c.setPreviewCallbackWithBuffer(null);
        } catch (Exception ignored) {
        }
        try {
            c.stopPreview();
        } catch (Exception ignored) {
        }
        try {
            c.release();
        } catch (Exception ignored) {
        }
    }

    @Override
    protected void onDestroy() {
        if (scanner != null) {
            scanner.close();
        }
        releaseCamera();
        super.onDestroy();
    }

    private static boolean isPlausibleTourUrl(@NonNull String s) {
        if (s.isEmpty()) {
            return false;
        }
        if (Patterns.WEB_URL.matcher(s).matches()) {
            return true;
        }
        String t = s.trim();
        if (t.regionMatches(true, 0, "http://", 0, 7)
                || t.regionMatches(true, 0, "https://", 0, 8)) {
            return Uri.parse(t).getScheme() != null;
        }
        return false;
    }
}
