// pages/camera.js
//
import {
  upload_image_data_urls,
  check_auto_torch,
} from '../../upload.js'

import {
  frame_pre_check,
  data_url_from_frame,
  load_qrtool,
  is_emblem_qr_pattern,
} from '../../precheck.js'

import {
  get_camera_rule,
  get_system_info,
  send_event,
} from '../../utils.js'

import { debug, info, error } from '../../log.js'

Page({

  /**
   * Page initial data
   */
  data: {
    hint_text: '查找二维码',
    enable_debug: false,
    debug_countdown: 10,
    camera_flash: "off",
    camoverlay_grayscale: 0,
    phone_model: "unknown",
    progress: 0,
    zoom: -1,
    rule_zoom: -1,
    max_zoom: 1,
    use_worker: false,
    show_tip: false,
    show_modal: '',
    busy: true,
    logs: [],
    should_check_auto_torch: true,
    done_checking_auto_torch: false,
    camera_sensitivity: 1,
    frame_uploaded: 0,
    last_frame_upload_time: 0,
    frame_upload_seq_num: 0,
    qrarc_class: "sm",
    qrmarkers_class: "hidden",
    frame_upload_in_flight: 0,
    frame_upload_failed: 0,
    frame_upload_time_cost: 0,
    frame_upload_interval_ms: 2000,
  },

  make_hint_text(r) {
    var qr_is_valid = false;
    if (r.qrcode && r.qrcode.length > 0) {
      qr_is_valid = is_emblem_qr_pattern(r.qrcode);
      if (!qr_is_valid) {
        return "无效编码";
      }
    }
    if (qr_is_valid) {
      if (this.on_qr_found) {
        this.on_qr_found();
        this.on_qr_found = null;
      }
      var err = r.err || "";
      if (err.includes("margin too small")) {
        return "对齐定位点";
      } else if (err.includes("energy check failed") || err.includes("cannot detect angle")) {
        return "移近一点";
      }
      return "对齐定位点";
    }
    return "查找二维码";
  },

  log(...what) {
    console.log(...what);
    this.setData({
      logs: this.data.logs.concat([new Date() + ": " + what.join(" ")])
    });
  },

  onLoad(options) {
    this.image_data_urls = [];
    console.log("camera page load", options);
    this.setData({
      ai_chat_mode: options.ai_chat_mode,
    });
    if (options.env == 'dev') {
      console.log("Using dev env settings.");
      getApp().globalData.server_url = 'https://dev.themblem.com';
    }
    this.log("camera page load (build240622.2120)");
    get_camera_rule(null, () => {});
    this.log("options", options);
    options = options || {};
    if (options.debug || options.scene == 'debug') {
      getApp().globalData.debug = true
    }
    var enable_debug = getApp().globalData.debug;
    const si = get_system_info();
    const phone_model = si.model;
    this.log("phone model: " + phone_model);
    getApp().globalData.phone_model = phone_model;
    this.log("window width", si.windowWidth, "height", si.windowHeight);
    const use_worker = phone_model.toLowerCase().includes("iphone");
    this.setData({
      enable_debug,
      phone_model,
      window_width: si.windowWidth,
      window_height: si.windowHeight,
      use_worker,
    });
    if (use_worker) {
      this.worker = getApp().globalData.get_worker();
      this.worker.onMessage((msg) => {
        if (msg.type == "result") {
          var res = msg.res;
          this.setData({
            processing_time: Date.now() - this.processing_begin,
            result: JSON.stringify(res),
          });
          if (this.data.should_check_auto_torch && is_emblem_qr_pattern(res.qrcode)) {
            this.start_check_auto_torch(res.qrcode);
          }
          if (this.data.done_checking_auto_torch && res.ok && res.qrcode.length && is_emblem_qr_pattern(res.qrcode)) {
            this.pending_hint_text = null;
            this.setData({
              hint_text: "识别成功",
              show_modal: "verifying",
            });
            this.fetch_from_worker();
          } else {
            this.pending_hint_text = this.make_hint_text(res);
            this.reset_busy();
          }
        } else if (this.data.done_checking_auto_torch && msg.type == "submit") {
          this.log("got submit");
          var res = msg.res;
          const image_data = msg.image_data;
          var uca = new Uint8ClampedArray(image_data.data);
          var data_url = data_url_from_frame(image_data.width, image_data.height, uca);
          this.image_data_urls.push(data_url);
          if (this.image_data_urls.length >= 3) {
            this.submit_image(this.image_data_urls, res.qrcode, res.angle);
            this.image_data_urls = [];
          }
        }
      });
    } else {
      load_qrtool();
    }
  },
  start_check_auto_torch(qrcode) {
    this.setData({
      should_check_auto_torch: false,
    });
    check_auto_torch(qrcode, (auto_torch, camera_sensitivity) => {
      this.log("check_auto_torch cb", auto_torch);
      if (auto_torch) {
        this.torch_on();
      }
      setTimeout(() => {
        this.setData({
          done_checking_auto_torch: true,
          camera_sensitivity: camera_sensitivity || 1,
        });
      }, 300);
    }
    );
  },

  fetch_from_worker() {
    // send the signal message to worker to fetch the image data
    this.log("fetch_from_worker");
    this.worker.postMessage({
      type: "ready_to_submit",
    });
  },

  onShow() {
    this.log("camera page show");
    this.setup_camera();
    this.setData({
      show_tip: false,
    })
    this.reset_busy();
    this.hint_interval = setInterval(() => {
      this.update_hint_text();
    }, 1000);
    this.setup_tooltip_timer();
  },

  update_hint_text() {
    if (this.pending_hint_text) {
      this.setData({
        hint_text: this.pending_hint_text,
      });
      this.pending_hint_text = null;
    }
  },

  setup_tooltip_timer() {
    if (this.tooltip_timer) {
      clearTimeout(this.tooltip_timer);
      this.tooltip_timer = null;
    }
    this.tooltip_timer = setTimeout(() => {
      if (this.data.show_modal != '') return;
      wx.vibrateShort({ type: "heavy", });
      this.setData({
        show_tip: true,
      });
    }, 15000);
  },

  clean_up() {
    clearTimeout(this.tooltip_timer);
    clearTimeout(this.hint_interval);
    this.listener.stop();
    this.listener = null;
  },

  onHide() {
    this.busy = true;
    this.clean_up();
  },

  onUnload() {
    this.clean_up();
  },

  submit_image(data_urls, qr_code, angle) {
    this.log("submit image", qr_code, angle);
    this.busy = true;
    var begin = Date.now();
    wx.vibrateShort({ type: "heavy", });
    var gd = getApp().globalData;
    gd.image_data_urls = data_urls;
    gd.qr_code = qr_code;
    gd.angle = angle;
    this.log("uploading image");
    const fail = (e) => {
      info("upload failed, goto not found: " + JSON.stringify(e));
      this.show_verifyfailed();
    };
    const success = (res) => {
      info("upload success, code", res.statusCode);
      this.log("upload success, code", res.statusCode);
      if (res.statusCode == 200) {
        var resp;
        if (typeof res.data == "string") {
          resp = JSON.parse(res.data);
        } else {
          resp = res.data;
        }
        this.log(resp);
        info("resp", resp);
        gd.verify_resp = resp;
        if (resp.serial_code) {
          // Let the first part of the loading animation more "noticable" and
          // the ui experience less "jumpy"
          var delay = 3000 - (Date.now() - begin);
          setTimeout(() => {
            this.show_result_page(resp.serial_code);
          }, delay > 0 ? delay : 0);
        } else if (gd.debug && resp.info == "ok") {
          // this is from the debug backend sigifying the upload has succeeded
          // we have no product info to show here because "ok" is the only returned information
          // just show a temporary page
          info("redirecting to test result page");
          wx.redirectTo({ url: '/pages/test_result_page/test_result_page?qr_code=' + encodeURIComponent(upload_result.qr_code || 'unknown') })
        } else {
          this.show_verifyfailed();
        }
      } else {
        info("invalid status code");
        this.show_verifyfailed();
      }
    };
    upload_image_data_urls(data_urls, success, fail, this.data.logs.join("\n"));
  },

  show_result_page(serial_code) {
    this.busy = true;
    if (this.data.ai_chat_mode) {
      wx.redirectTo({
        url: '/pages/chat/chat?serial_code=' + serial_code,
      });
    } else {
      wx.redirectTo({
        url: '/pages/productinfo/productinfo?serial_code=' + serial_code,
      });
    }
  },

  show_verifyfailed() {
    this.busy = true;
    this.setData({
      show_modal: "verifyfailed",
    });
  },

  show_service() {
    this.busy = true;
    this.setData({
      show_modal: "service",
    });
  },

  show_scanguide() {
    this.busy = true;
    this.setData({
      show_modal: "scanguide",
    });
  },

  restart_camera() {
    wx.redirectTo({
      url: '/pages/camera/camera',
    })
  },

  reset_busy() {
    setTimeout(() => {
      this.busy = false;
    }, 20)
  },

  setup_camera: function (opts) {
    var max_zoom = null;
    if (opts) {
      console.log(opts);
      max_zoom = opts.detail.maxZoom;
      this.setData({
        max_zoom,
      });
    }
    get_camera_rule(max_zoom, (rule) => {
      var zoom = rule.zoom;
      var initial_zoom = 2;
      this.setData({
        zoom: initial_zoom,
        rule_zoom: zoom,
      });
      const ctx = wx.createCameraContext();
      this.log(`camera set initial zoom to ${initial_zoom}x, will zoom in to ${rule.zoom}x when qr is found`);
      ctx.setZoom({ zoom: initial_zoom });
      this.on_qr_found = () => {
        this.log(`qr found, zoom to ${rule.zoom}x`);
        ctx.setZoom({ zoom: rule.zoom });
        this.setData({
          zoom: rule.zoom,
          qrmarkers_class: "",
          qrarc_class: "lg"
        });
      }
      if (!this.listener) {
        this.log("creating camera frame listener...");
        const listener = ctx.onCameraFrame((res) => { this.handle_frame(res) });
        this.listener = listener;
      }
      const worker = getApp().globalData.get_worker();
      this.listener.start({
        worker,
      });
    });
  },

  handle_frame(res) {
    if (this.failed) return;
    try {
      this.do_handle_frame(res);
    } catch (e) {
      console.log(e);
      this.failed = true;
    }
  },
  handle_image_data_url(data_url) {
    const now = Date.now();
    if (
      now - this.data.last_frame_upload_time < this.data.frame_upload_interval_ms ||
      this.data.frame_upload_in_flight > 0
    ) {
      return;
    }
    var begin = Date.now();
    this.setData({
      last_frame_upload_time: now,
      frame_upload_seq_num: this.data.frame_upload_seq_num + 1,
      frame_upload_in_flight: this.data.frame_upload_in_flight + 1,
    });
    var seq_num = this.data.frame_upload_seq_num.toString().padStart(3, '0');
    var event_data = {
      session_id: getApp().globalData.session_id,
      phone_model: getApp().globalData.phone_model,
      seq_num: seq_num,
      image_data_url: data_url,
    }
    var success_cb = (res) => {
      this.setData({
        frame_uploaded: this.data.frame_uploaded + 1,
        frame_upload_in_flight: this.data.frame_upload_in_flight - 1,
        frame_upload_time_cost: Date.now() - begin,
        frame_upload_interval_ms: this.data.frame_upload_interval_ms * 2,
      });
    }
    var fail_cb = (e) => {
      this.log("frame upload failed", e);
      this.setData({
        frame_upload_failed: this.data.frame_upload_failed + 1,
        frame_upload_in_flight: this.data.frame_upload_in_flight - 1,
        frame_upload_time_cost: Date.now() - begin,
        frame_upload_interval_ms: this.data.frame_upload_interval_ms * 2,
      });
    }
    send_event("camera-frame", event_data, success_cb, fail_cb);
  },
  do_handle_frame(res) {
    if (this.busy) return;
    this.busy = true;
    if (this.data.use_worker) {
      this.processing_begin = Date.now();
      console.log("postMessage frame");
      this.worker.postMessage({
        type: 'frame',
        width: res.width,
        height: res.height,
        camera_sensitivity: this.data.camera_sensitivity,
      });
    } else {
      /* Make sure we are copying the frame data to avoid TOCTOU race condition
       * with camera event (update of res.data while we're working) */
      var uca1 = new Uint8ClampedArray(res.data);
      var uca = new Uint8ClampedArray(uca1);
      const data_url = data_url_from_frame(res.width, res.height, uca);
      var r = frame_pre_check(res.width, res.height, uca,
        this.data.camera_sensitivity)
      const result = r.result;
      this.log("frame_pre_check: ", JSON.stringify(result));
      this.setData({
        result: JSON.stringify(r.result),
        debug_image_data_url: r.data_url,
      });
      this.handle_image_data_url(data_url);
      if (this.data.done_checking_auto_torch && result.ok && result.angle >= 0 && result.qrcode.length && is_emblem_qr_pattern(result.qrcode)) {
        this.image_data_urls.push(data_url);
        if (this.image_data_urls.length >= 3) {
          this.setData({
            show_modal: "verifying",
          });
          this.submit_image(this.image_data_urls, result.qrcode, result.angle);
          this.image_data_urls = [];
        } else {
          this.reset_busy();
        }
      } else {
        if (this.data.should_check_auto_torch && is_emblem_qr_pattern(result.qrcode)) {
          this.start_check_auto_torch(result.qrcode);
        }
        this.pending_hint_text = this.make_hint_text(result);
        this.reset_busy();
      }
    }
  },

  torch_on() {
    this.log("torch on");
    this.setData({
      camera_flash: "torch",
    });
  },

  toggle_torch() {
    this.log("toggle torch");
    var cf = this.data.camera_flash == "torch" ? "off" : "torch";
    this.setData({
      camera_flash: cf,
    });
  },

  debug_tap() {
    if (this.data.enable_debug) {
      return;
    }
    this.data.debug_countdown -= 1;
    if (this.data.debug_countdown <= 0) {
      this.log("enabling debug");
      this.setData({
        enable_debug: true,
      })
    }
  },
})
