import Head from 'next/head'
import Link from 'next/link'
import dynamic from 'next/dynamic'
import React, { useEffect, useRef, useState } from 'react'
import { QRCodeSVG } from 'qrcode.react'
import styles from '../../styles/Post.module.scss'
import localStyles from './ee-qrcode.module.scss'
import HeaderNav from '../../lib/HeaderNav'
import HeroImage from '../../lib/HeroImage'

let scannerWorker = null

async function startCapture(video, canvas, captureType) {
  if (typeof window !== 'undefined') {
    let captureStream = null

    try {
      if (captureType === 'screen')
        captureStream = await navigator.mediaDevices.getDisplayMedia({ audio: false, video: true })
      else
        captureStream = await navigator.mediaDevices.getUserMedia({ audio: false, video: { facingMode: 'environment' } })
    } catch (e) {
      alert(`Failed to start screen capture: ${e}`)
      return
    }

    video.srcObject = captureStream
    video.play()
    await new Promise(resolve => {
      video.addEventListener('loadedmetadata', resolve)
    })
    canvas.width = video.videoWidth
    canvas.height = video.videoHeight

    return (() => {
      captureStream.getTracks().forEach(track => track.stop())
      video.srcObject = null
    })
  }
}

function scanImage(imageData) {
  if (scannerWorker === null) {
    scannerWorker = new Worker(new URL('../../lib/ee-qrcode.worker.js', import.meta.url))
  }

  return new Promise(resolve => {
    scannerWorker.onmessage = e => resolve(e.data.response)
    scannerWorker.postMessage({imageData})
  })
}

async function processImage(video, hiddenCanvas, previewCanvas, filterSymbols, onResult) {
  const hiddenCtx = hiddenCanvas.getContext('2d')
  hiddenCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight)

  const allSymbols = await scanImage(hiddenCtx.getImageData(0, 0, video.videoWidth, video.videoHeight))

  previewCanvas.width = video.videoWidth
  previewCanvas.height = video.videoHeight
  const previewCtx = previewCanvas.getContext('2d')
  previewCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight)
  const symbols = allSymbols.filter(filterSymbols || (() => true))
  previewCtx.strokeStyle = 'rgba(255, 0, 0, 0.7)'
  previewCtx.lineWidth = 6
  symbols.map((sym) => {
    if (sym.points < 2) return
    const [firstPoint, ...points] = sym.points

    previewCtx.beginPath()
    previewCtx.moveTo(firstPoint.x, firstPoint.y)
    points.forEach(point => previewCtx.lineTo(point.x, point.y))
    previewCtx.closePath()

    previewCtx.stroke()
  })
  if (symbols.length > 0) {
    onResult(symbols[0].value)
  }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms))
}

async function runScanner({ video, canvas, previewCanvas, filterSymbols, onResult, running, captureType }) {
  const stopCapture = await startCapture(video, canvas, captureType)

  while (running()) {
    await processImage(video, canvas, previewCanvas, filterSymbols, onResult)
    await sleep(500)
  }

  stopCapture()
}

function CodeScanner({ filterSymbols, onResult, captureType }) {
  const videoRef = useRef(null)
  const canvasRef = useRef(null)
  const previewCanvasRef = useRef(null)

  useEffect(() => {
    let running = true;
    if (videoRef.current && canvasRef.current && previewCanvasRef.current) {
      runScanner({ video: videoRef.current, canvas: canvasRef.current, previewCanvas: previewCanvasRef.current, running: (() => running), filterSymbols, onResult, captureType })
    }
    return (() => {
      running = false;
    });
  }, [videoRef, canvasRef, previewCanvasRef, captureType])

  return (
    <div>
      <video ref={videoRef} className={localStyles.video} />
      <canvas ref={canvasRef} className={localStyles.video} />
      <canvas ref={previewCanvasRef} className={localStyles.canvas} />
    </div>
  )
}

function esimQrcodeFilter(symbol) {
  if (symbol.typeName !== 'ZBAR_QRCODE') return false
  if (!symbol.value.match(/^LPA:/)) return false
  return true
}

function fixData(srcData) {
  return srcData.replace(/[$][$]$/, '')
}

function StartCaptureButtons({ setRunning }) {
  const isBrowser = typeof window !== 'undefined'
  const hasScreenCapture = isBrowser && typeof navigator.mediaDevices.getDisplayMedia === 'function';
  const hasWebcamCapture = isBrowser && typeof navigator.mediaDevices.getUserMedia === 'function';

  return (
    <span suppressHydrationWarning={true}>
      {hasScreenCapture ? <button onClick={() => setRunning('screen')}>Start Screen Capture</button> : null}
      {hasWebcamCapture ? <button onClick={() => setRunning('camera')}>Start Webcam Capture</button> : null}
    </span>
  )
}

export default function EsimMangler() {
  const [running, setRunning] = useState(false)
  const [scannedData, setScannedData] = useState(null)

  return (
    <div className={styles.container}>
      <HeaderNav />
      <HeroImage image="https://farm9.staticflickr.com/8218/8437956869_05a4e887b0_k_d.jpg" credit={{ url: "https://www.flickr.com/photos/npobre/8437956869/", text: "Norlando Pobre, Flickr" }} withGradient="top-blue">
	EE QR Code Mangler
      </HeroImage>
      <Head>
        <title>EE QR Code Mangler | Luke Granger-Brown</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <div className={styles.post}>
          <h1>EE eSIM QR Code Fixer</h1>
          <p>EE, as of 2022-05-01, still generates not-quite-spec-compliant eSIM QR Codes, which get rejected by some Android devices - including the Pixel series of devices.</p>
          <p>This page is intended to generate a fixed version of the QR code (which happens entirely on your computer!).</p>
          <p>{running ? <button onClick={() => setRunning(false)}>Stop Capture</button> : (
            <StartCaptureButtons setRunning={setRunning} />
          )}</p>
          {(scannedData !== null) ? <p><QRCodeSVG value={fixData(scannedData)} /></p> : null}
          {(scannedData !== null) ? <p><tt>{fixData(scannedData)}</tt></p> : null}
          {(running != false) ? <CodeScanner filterSymbols={esimQrcodeFilter} onResult={(result) => {
            setScannedData(result)
            setRunning(false)
      	  }} captureType={running} /> : null}
        </div>
      </main>
    </div>
  )
}