/**
 * BangleJS DotMatrixCLOCK
 *
 * + Original Author: Paul Cockrell https://github.com/paulcockrell
 * + Created: May 2020
 */
const storage = require('Storage');
const settings = (storage.readJSON('setting.json', 1) || {});
const is12Hour = settings["12hour"] || false;
const timeout = settings.timeout || 20;

const font7x7 = {
  "empty": "00000000",
  "0": "3E61514945433E",
  "1": "1808080808081C",
  "2": "7E01013E40407F",
  "3": "7E01013E01017E",
  "4": "4141417F010101",
  "5": "7F40407E01017E",
  "6": "3E40407E41413E",
  "7": "3F010202040408",
  "8": "3E41413E41413E",
  "9": "3E41413F01013E",
};

const font5x5 = {
  "empty": "00000000",
  "-": "0000FF0000",
  "0": "0E1915130E",
  "1": "0C0404040E",
  "2": "1E010E101F",
  "3": "1E010E011E",
  "4": "11111F0101",
  "5": "1F101E011E",
  "6": "0E101E110E",
  "7": "1F01020408",
  "8": "0E110E110E",
  "9": "0E110F010E",
  "A": "040A0E1111",
  "B": "1E111E111E",
  "C": "0F1010100F",
  "D": "1E1111111E",
  "E": "1F101E101F",
  "F": "1F101E1010",
  "G": "0F1013110E",
  "H": "11111F1111",
  "I": "0E0404040E",
  "J": "1F0404140C",
  "L": "101010101F",
  "M": "111B151111",
  "N": "1119151311",
  "O": "0E1111110E",
  "P": "1E111E1010",
  "R": "1E111E1111",
  "S": "0F100E011E",
  "T": "1F04040404",
  "U": "111111110E",
  "V": "1111110A04",
  "W": "111115150A",
  "Y": "110A040404",
};

// Char renderer
const COLORS = {
  blue: {
    BG: "#0297fe",
    DARK: "#3b3ce8",
    LIGHT: "#E9ffff",
  },
  orange: {
    BG: "#f7b336",
    DARK: "#ac721e",
    LIGHT: "#f6fc0f",
  }
};

let selectedColor = "blue";
let displayTimeoutRef, sensorTimeoutRef;

// Example
// binToHex(["0111110", "1000000", "1000000", "1111110", "1000001", "1000001", "0111110"])
function binToHex(bins) {
  return bins.map(bin => ("00" + (parseInt(bin, 2).toString(16))).substr(-2).toUpperCase()).join("");
}

// Example
// hexToBin("3E40407E41413E")
function hexToBin(hexStr) {
  const regEx = new RegExp("..", "g");
  const bin = hexStr
    .replace(regEx, el => el + '_')
    .slice(0, -1)
    .split('_')
    .map(hex => ("00000000" + (parseInt(hex, 16)).toString(2)).substr(-8));

  return bin;
}

function drawPixel(opts) {
  g.setColor(opts.color);
  g.fillRect(opts.x, opts.y, opts.x + opts.w, opts.y + opts.h);
}

function drawGrid(pos, dims, charAsBin, opts) {
  const defaultOpts = {
    pxlW: 5,
    pxlH: 5,
    gap: 1,
    offColor: COLORS[selectedColor].DARK,
    onColor: COLORS[selectedColor].LIGHT
  };
  const pxl = Object.assign({}, defaultOpts, opts);

  for (let rowY = 0; rowY < dims.rows; rowY++) {
    const y = pos.y + ((pxl.pxlH + pxl.gap) * rowY);

    for (let colX = 7; colX > (7 - dims.cols); colX--) {
      const x = pos.x + ((pxl.pxlW + pxl.gap) * colX);
      const color = (charAsBin && parseInt(charAsBin[rowY][colX])) ? pxl.onColor : pxl.offColor;

      drawPixel({
        x: x,
        y: y,
        w: pxl.pxlW,
        h: pxl.pxlH,
        color: color,
      });
    }
  }
}

function drawFont(str, font, x, y) {
  let fontMap, rows, cols;

  switch(font) {
    case "7x7":
      fontMap = font7x7;
      rows = cols = 7;
      break;
    case "5x5":
      fontMap = font5x5;
      rows = cols = 5;
      break;
    default:
      throw "Unknown font type: " + font;
  }

  const pxlW = 2;
  const pxlH = 2;
  const gap = 2;
  const gutter = 3;
  const charArr = str.split("");
  const gridWidthTotal = (rows * (pxlW + gap)) + gutter;
  for (let i = 0; i < charArr.length; i++) {
    const charAsBin = fontMap.hasOwnProperty(charArr[i])?
      hexToBin(fontMap[charArr[i]]):
      fontMap.empty;

    drawGrid(
      {x: x + (i * gridWidthTotal), y: y},
      {rows: rows, cols: cols},
      charAsBin,
      {pxlW: pxlW, pxlH: pxlH, gap: gap}
    );
  }
}

function drawTitles() {
  g.setColor("#ffffff");
  g.setFont("6x8");
  g.drawString("COMPASS", 52, 49);
  g.drawString("HEART", 122, 49);
  g.drawString("TIME", 52, 94);
  g.drawString("DATE", 52, 144);
}

function drawCompass(lastHeading) {
  const directions = [
    'N',
    'NE',
    'E',
    'SE',
    'S',
    'SW',
    'W',
    'NW'
  ];
  const cps = Bangle.getCompass();
  let angle = 360-cps.heading;
  let heading = angle?
    directions[Math.round(((angle %= 360) < 0 ? angle + 360 : angle) / 45) % 8]:
    "-- ";

  heading = (heading + "   ").slice(0, 3);
  if (lastHeading != heading) drawFont(heading, "5x5", 40, 67);
  setTimeout(drawCompass.bind(null, heading), 1000 * 2);
}

function drawHeart(hrm) {
  drawFont(("   " + (hrm ? hrm.bpm : "---")).slice(-3), "5x5", 109, 67);
}

function drawTime(lastHrs, lastMns, toggle) {
  const date = new Date();
  const h = date.getHours();
  const hrs = ("00" + ((is12Hour && h > 12) ? h - 12 : h)).substr(-2);
  const mns = ("00" + date.getMinutes()).substr(-2);

  if (lastHrs != hrs) {
    drawFont(hrs, "7x7", 48, 109);
  }
  if (lastMns != mns) {
    drawFont(mns, "7x7", 124, 109);
  }

  const color = toggle? COLORS[selectedColor].LIGHT : COLORS[selectedColor].DARK;

  // This should toggle on/off per second
  drawPixel({
    color: color,
    x: 118, y: 118,
    w: 2, h: 2,
  });
  drawPixel({
    color: color,
    x: 118, y: 125,
    w: 2, h: 2,
  });

  setTimeout(drawTime.bind(null, hrs, mns, !toggle), 1000);
}

function drawDate(lastDate) {
  const locale = require('locale');
  const date = new Date();

  if (lastDate != date.toISOString().split('T')[0]) {
    const dow = locale.dow(date, 1).toUpperCase();
    const dayNum = ("00" + date.getDate()).slice(-2);
    const mon = locale.month(date).toUpperCase().slice(0, 3);
    const yr = date.getFullYear().toString().slice(-2);
    drawFont(dow + " " + dayNum, "5x5", 40, 159);
    drawFont(mon + " " + yr, "5x5", 40, 189);
  }

  setTimeout(drawDate.bind(null, date.toISOString().split('T')), 1000 * 60);
}

function setSensors(state) {
  // Already reading sensors and trying to activate sensors, do nothing
  if (sensorTimeoutRef && state === 1) return;

  // If we are activating the sensors, turn them off again in one minute
  if (state === 1) {
    sensorTimeoutRef = setTimeout(() => { setSensors(0); }, 1000 * 60);
  } else {
    if (sensorTimeoutRef) {
      clearInterval(sensorTimeoutRef);
      sensorTimeoutRef = null;
    }
    // Bit nasty, but we only redraw the heart value on sensor callback
    // but we want to blank out when sensor is off, but no callback for
    // that so force redraw here
    drawHeart();
  }

  Bangle.setHRMPower(state);
  Bangle.setCompassPower(state);
}

function drawScreen() {
  g.setBgColor(COLORS[selectedColor].BG);
  g.clearRect(0, 24, g.getWidth(), g.getHeight());

  // Draw components
  drawTitles();
  drawCompass();
  drawHeart();
  drawTime();
  drawDate();
}

function clearTimers(){
  if (displayTimeoutRef) {
    clearInterval(displayTimeoutRef);
    displayTimeoutRef = null;
  }

  if (sensorTimeoutRef) {
    clearInterval(sensorTimeoutRef);
    sensorTimeoutRef = null;
  }
}

function resetDisplayTimeout() {
  if (displayTimeoutRef) clearInterval(displayTimeoutRef);
  Bangle.setLCDPower(true);

  displayTimeoutRef = setTimeout(() => {
    if (Bangle.isLCDOn()) Bangle.setLCDPower(false);
    clearTimers();
  }, 1000 * timeout);
}

// Turn sensors on
setSensors(1);

// Reset screen
g.clear();

// Load and draw widgets
Bangle.loadWidgets();
Bangle.drawWidgets();

// Draw screen
drawScreen();
resetDisplayTimeout();

// Setup callbacks
Bangle.on('swipe', (sDir) => {
  selectedColor = selectedColor === "blue" ? "orange" : "blue";
  resetDisplayTimeout();
  drawScreen();
});

Bangle.on('HRM', drawHeart);

setWatch(() => {
  setSensors(1);
  resetDisplayTimeout();
}, BTN1, {repeat: true, edge: "falling"});

setWatch(() => {
  setSensors(0);
  clearTimers();
  Bangle.setLCDMode();
  Bangle.showLauncher();
}, BTN2, {repeat: false, edge: "falling"});

Bangle.on('lcdPower', (on) => {
  if(on) {
    resetDisplayTimeout();
  } else {
    clearTimers();
    setSensors(0);
  }
});

Bangle.on('faceUp', (up) => {
  if (up && !Bangle.isLCDOn()) {
    setSensors(1);
    resetDisplayTimeout();
  }
});
