<!DOCTYPE html>
<html>
<head>
  <script src="face-recognition.min.js"></script>
  <script src="axios.min.js"></script>
  <script src="commons.js"></script>
  <link rel="stylesheet" href="styles.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.css">
  <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
</head>
<body>
  <div class="center-content page-container">
    <div class="row">
      <a
        class="waves-effect waves-light btn"
        href="faceSimilarity.html"
      >
        Go To Face Similarity Example
      </a>
    </div>
    <div>
      <div class="row center-content" id="loader">
        <input disabled value="" id="status" type="text" class="bold">
        <div class="progress">
          <div class="indeterminate"></div>
        </div>
      </div>
      <div class="row center-content">
        <img id="face" src=""/>
      </div>
      <div class="row">
        <label for="prediction">Prediction:</label>
        <input disabled value="-" id="prediction" type="text" class="bold">
      </div>
      <div class="row">
        <label for="time">Time:</label>
        <input disabled value="-" id="time" type="text" class="bold">
      </div>
      <div class="row">
        <label for="fps">Estimated Fps:</label>
        <input disabled value="-" id="fps" type="text" class="bold">
      </div>
      <div class="row">
        <button
          class="waves-effect waves-light btn"
          id="stop"
          onclick="onToggleStop()"
        >
          Stop
        </button>
        <button
          class="waves-effect waves-light btn"
          onclick="onSlower()"
        >
        <i class="material-icons left">-</i> Slower
        </button>
        <button
          class="waves-effect waves-light btn"
          onclick="onFaster()"
        >
          <i class="material-icons left">+</i> Faster
        </button>
      </div>
      <div class="row">
        <label for="interval">Interval:</label>
        <input disabled value="2000" id="interval" type="text" class="bold">
      </div>
    </div>
  </div>

  <script>
    // for 150 x 150 sized face images 0.6 is a good threshold to
    // judge whether two face descriptors are similar or not
    const threshold = 0.6
    let interval = 2000

    let isStop = false
    let trainDescriptorsByClass = []
    let net = {}
    let currImageIdx = 2, currClassIdx = 0
    let to = null

    function onSlower() {
      interval = Math.max(interval + 100, 0)
      $('#interval').val(interval)
    }

    function onFaster() {
      interval = Math.min(interval - 100, 2000)
      $('#interval').val(interval)
    }

    function onToggleStop() {
      clearTimeout(to)
      isStop = !isStop
      document.getElementById('stop').innerHTML = isStop ? 'Continue' : 'Stop'
      setStatusText(isStop ? 'stopped' : 'running face recognition:')
      if (!isStop) {
        runFaceRecognition()
      }
    }

    function setStatusText(text) {
      $('#status').val(text)
    }

    function displayTimeStats(timeInMs) {
      $('#time').val(`${timeInMs} ms`)
      $('#fps').val(`${round(1000 / timeInMs)}`)
    }

    function displayImage(src) {
      getImg().src = src
    }

    async function loadTrainingData(cb) {
      return await Promise.all(classes.map(
        async className => ({
          imgData: await bufferToImageData(
            await fetchImage(getImageUri(className, 1))
          ),
          className
        })
      ))
    }

    function getBestMatch(queryDescriptor) {
      return trainDescriptorsByClass
        .map(
          ({ descriptor, className }) => ({
            distance: round(facerecognition.euclideanDistance(descriptor, queryDescriptor)),
            className
          })
        )
        .reduce((best, curr) => best.distance < curr.distance ? best : curr)
    }

    async function runFaceRecognition() {
      async function next() {
        const imgBuf = await fetchImage(getImageUri(classes[currClassIdx], currImageIdx))
        const imgEl = $('#face').get(0)
        imgEl.src = await bufferToImgSrc(imgBuf)

        const imageData = await imgSrcToData(imgEl.src)

        const ts = Date.now()
        const result = await net.forward(imageData)
        displayTimeStats(Date.now() - ts)

        const descriptor = await result.data()
        const bestMatch = getBestMatch(descriptor)
        $('#prediction').val(`${bestMatch.distance < threshold ? bestMatch.className : 'unkown'} (${bestMatch.distance})`)

        currImageIdx = currClassIdx === (classes.length - 1)
          ? currImageIdx + 1
          : currImageIdx
        currClassIdx = (currClassIdx + 1) % classes.length

        currImageIdx = (currImageIdx % 6) || 2
        to = setTimeout(next, interval)
      }
      await next(0, 0)
    }

    async function run() {
      try {
        setStatusText('loading model file...')

        net = await initNet()
        setStatusText('computing initial descriptors...')

        const trainImgDatas = await loadTrainingData()
        trainDescriptorsByClass = await Promise.all(trainImgDatas.map(
          async ({ className, imgData }) => ({
            descriptor: await net.computeFaceDescriptor(imgData),
            className
          })
        ))
        $('#loader').hide()

        runFaceRecognition()
      } catch (err) {
        console.error(err)
      }
    }

    run()
  </script>

</body>
</html>