/*
 This file is part of GNU Taler
 (C) 2022 Taler Systems S.A.
 GNU Taler is free software; you can redistribute it and/or modify it under the
 terms of the GNU General Public License as published by the Free Software
 Foundation; either version 3, or (at your option) any later version.
 GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 You should have received a copy of the GNU General Public License along with
 GNU Taler; see the file COPYING.  If not, see 
 */
import {
  classifyTalerUri,
  TalerUriType,
  TranslatedString,
} from "@gnu-taler/taler-util";
import { styled } from "@linaria/react";
import { css } from "@linaria/core";
import { Fragment, h, VNode } from "preact";
import { Ref, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { useTranslationContext } from "../context/translation.js";
import { Alert } from "../mui/Alert.js";
import { Button } from "../mui/Button.js";
import { TextField } from "../mui/TextField.js";
import jsQR, * as pr from "jsqr";
import { InputFile } from "../mui/InputFile.js";
import { Grid } from "../mui/Grid.js";
import { notDeepEqual } from "assert";
const QrCanvas = css`
  width: 80%;
  margin-left: auto;
  margin-right: auto;
  padding: 8px;
  background-color: black;
`;
const LINE_COLOR = "#FF3B58";
const Container = styled.div`
  display: flex;
  flex-direction: column;
  & > * {
    margin-bottom: 20px;
  }
`;
interface Props {
  onDetected: (url: string) => void;
}
type XY = { x: number; y: number };
function drawLine(
  canvas: CanvasRenderingContext2D,
  begin: XY,
  end: XY,
  color: string,
) {
  canvas.beginPath();
  canvas.moveTo(begin.x, begin.y);
  canvas.lineTo(end.x, end.y);
  canvas.lineWidth = 4;
  canvas.strokeStyle = color;
  canvas.stroke();
}
function drawBox(context: CanvasRenderingContext2D, code: pr.QRCode) {
  drawLine(
    context,
    code.location.topLeftCorner,
    code.location.topRightCorner,
    LINE_COLOR,
  );
  drawLine(
    context,
    code.location.topRightCorner,
    code.location.bottomRightCorner,
    LINE_COLOR,
  );
  drawLine(
    context,
    code.location.bottomRightCorner,
    code.location.bottomLeftCorner,
    LINE_COLOR,
  );
  drawLine(
    context,
    code.location.bottomLeftCorner,
    code.location.topLeftCorner,
    LINE_COLOR,
  );
}
const SCAN_PER_SECONDS = 3;
const TIME_BETWEEN_FRAMES = 1000 / SCAN_PER_SECONDS;
async function delay(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
function drawIntoCanvasAndGetQR(
  tag: HTMLVideoElement | HTMLImageElement,
  canvas: HTMLCanvasElement,
): string | undefined {
  const context = canvas.getContext("2d");
  if (!context) {
    throw Error("no 2d canvas context");
  }
  context.clearRect(0, 0, canvas.width, canvas.height);
  context.drawImage(tag, 0, 0, canvas.width, canvas.height);
  const imgData = context.getImageData(0, 0, canvas.width, canvas.height);
  const code = jsQR.default(imgData.data, canvas.width, canvas.height, {
    inversionAttempts: "attemptBoth",
  });
  if (code) {
    drawBox(context, code);
    return code.data;
  }
  return undefined;
}
async function readNextFrame(
  video: HTMLVideoElement,
  canvas: HTMLCanvasElement,
): Promise {
  const requestFrame =
    "requestVideoFrameCallback" in video
      ? video.requestVideoFrameCallback.bind(video)
      : requestAnimationFrame;
  return new Promise((ok, bad) => {
    requestFrame(() => {
      try {
        const code = drawIntoCanvasAndGetQR(video, canvas);
        ok(code);
      } catch (error) {
        bad(error);
      }
    });
  });
}
async function createCanvasFromVideo(
  video: HTMLVideoElement,
  canvas: HTMLCanvasElement,
): Promise {
  const context = canvas.getContext("2d", {
    willReadFrequently: true,
  });
  if (!context) {
    throw Error("no 2d canvas context");
  }
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;
  let last = Date.now();
  let found: string | undefined = undefined;
  while (!found) {
    const timeSinceLast = Date.now() - last;
    if (timeSinceLast < TIME_BETWEEN_FRAMES) {
      await delay(TIME_BETWEEN_FRAMES - timeSinceLast);
    }
    last = Date.now();
    found = await readNextFrame(video, canvas);
  }
  video.pause();
  return found;
}
async function createCanvasFromFile(
  source: string,
  canvas: HTMLCanvasElement,
): Promise {
  const img = new Image(300, 300);
  img.src = source;
  canvas.width = img.width;
  canvas.height = img.height;
  return new Promise((ok, bad) => {
    img.addEventListener("load", (e) => {
      try {
        const code = drawIntoCanvasAndGetQR(img, canvas);
        ok(code);
      } catch (error) {
        bad(error);
      }
    });
  });
}
async function waitUntilReady(video: HTMLVideoElement): Promise {
  return new Promise((ok, bad) => {
    if (video.readyState === video.HAVE_ENOUGH_DATA) {
      return ok();
    }
    setTimeout(waitUntilReady, 100);
  });
}
export function QrReaderPage({ onDetected }: Props): VNode {
  const videoRef = useRef(null);
  const canvasRef = useRef(null);
  const [error, setError] = useState();
  const [value, setValue] = useState("");
  const [show, setShow] = useState<"canvas" | "video" | "nothing">("nothing");
  const { i18n } = useTranslationContext();
  function onChange(str: string) {
    if (!!str) {
      if (!str.startsWith("taler://")) {
        setError(
          i18n.str`URI is not valid. Taler URI should start with "taler://"`,
        );
      } else if (classifyTalerUri(str) === TalerUriType.Unknown) {
        setError(i18n.str`Unknown type of Taler URI`);
      } else {
        setError(undefined);
      }
    } else {
      setError(undefined);
    }
    setValue(str);
  }
  async function startVideo() {
    if (!videoRef.current || !canvasRef.current) {
      return;
    }
    const video = videoRef.current;
    if (!video || !video.played) return;
    const stream = await navigator.mediaDevices.getUserMedia({
      video: { facingMode: "environment" },
      audio: false,
    });
    setShow("video");
    setError(undefined);
    video.srcObject = stream;
    await video.play();
    await waitUntilReady(video);
    try {
      const code = await createCanvasFromVideo(video, canvasRef.current);
      if (code) {
        onChange(code);
        setShow("canvas");
      }
      stream.getTracks().forEach((e) => {
        e.stop();
      });
    } catch (error) {
      setError(i18n.str`something unexpected happen: ${error}`);
    }
  }
  async function onFileRead(fileContent: string) {
    if (!canvasRef.current) {
      return;
    }
    setShow("nothing");
    setError(undefined);
    try {
      const code = await createCanvasFromFile(fileContent, canvasRef.current);
      if (code) {
        onChange(code);
        setShow("canvas");
      } else {
        setError(i18n.str`Could not found a QR code in the file`);
      }
    } catch (error) {
      setError(i18n.str`something unexpected happen: ${error}`);
    }
  }
  const active = value === "";
  return (
    
      
        
          
            Scan a QR code or enter taler:// URI below
          
        
        
          
        
        
          
            {error && {error}}
          
          
            {!active && (
              
            )}
          
          
            {value && (
              
            )}
          
          
            Read QR from file
          
          
            
              
            
          
        
      
      
        
        
      
    
  );
}