E.setFlags({pretokenise:1});

function log(msg) {
  console.log("heart: " + msg + "; mem used: " + process.memory().usage / process.memory().blocksize);
  return;
}

log("start");

Bangle.loadWidgets();
Bangle.drawWidgets();

var settings = require("Storage").readJSON("heart.json",1)||{};

function getFileNbr(n) {
  return ".heart"+n.toString(36);
}

function updateSettings() {
  require("Storage").write("heart.json", settings);
  if (WIDGETS["heart"])
    WIDGETS["heart"].reload();
  return;
}

function showMainMenu() {
  const mainMenu = {
    '': { 'title': 'Heart Recorder' },
    'RECORD': {
      value: !!settings.isRecording,
      format: v=>v?"On":"Off",
      onchange: v => {
        settings.isRecording = v;
        updateSettings();
      }
    },
    'File Number': {
      value: settings.fileNbr|0,
      min: 0,
      max: 35,
      step: 1,
      onchange: v => {
        settings.isRecording = false;
        settings.fileNbr = v;
        updateSettings();
      }
    },
    'View Records': ()=>{createRecordMenu(viewRecord.bind());},
    'Graph Records': ()=>{createRecordMenu(graphRecord.bind());},
    '< Back': ()=>{load();}
  };
  return E.showMenu(mainMenu);
}

// Date().as().str cannot be used as it always returns UTC time
function getDateString(timestamp) {
  var date = new Date(timestamp);
  var day = date.getDate() < 10 ? "0" + date.getDate().toString() : date.getDate().toString();
  var month = date.getMonth() < 10 ? "0" + date.getMonth().toString() : date.getMonth().toString();
  return day + "." + month + "." + date.getFullYear();
}

// Date().as().str cannot be used as it always returns UTC time
function getTimeString(timestamp) {
  var date = new Date(timestamp);
  var hour = date.getHours() < 10 ? '0' + date.getHours().toString() : date.getHours().toString();
  var minute = date.getMinutes() < 10 ? '0' + date.getMinutes().toString() : date.getMinutes().toString();
  return hour + ':' + minute;
}

function createRecordMenu(func) {
  const menu = {
    '': { 'title': 'Heart Records' }
  };
  var found = false;
  for (var n=0;n<36;n++) {
    var line = require("Storage").open(getFileNbr(n),"r").readLine();
    if (line!==undefined) {
      menu["#" + n + " " + getDateString(line.split(",")[0]*1000) + " " + getTimeString(line.split(",")[0]*1000)] = func.bind(null, n);
      found = true;
    }
  }
  if (!found)
    menu["No Records Found"] = function(){};
  menu['< Back'] = ()=>{showMainMenu();};
  return E.showMenu(menu);
}

function viewRecord(n) {
  E.showMenu({'': 'Heart Record '+n});
  E.showMessage(
    "Loading Data ...\n\nMay take a while,\nwill vibrate\nwhen done.",
    'Heart Record '+n
  );
  const menu = {
    '': { 'title': 'Heart Record '+n }
  };
  var heartCount = 0;
  var heartTime;
  var f = require("Storage").open(getFileNbr(n),"r");
  var l = f.readLine();
  // using arrays for memory optimization
  var limits = Uint8Array(2);
  // using arrays for memory optimization
  var avg = Uint32Array(2);
  // minimum
  limits[0] = 2000;
  // maximum
  limits[1] = 0;
  // count
  avg[0] = 0;
  // average sum
  avg[1] = 0;
  var count = 0;
  var value = 0;
  if (l!==undefined)
    heartTime = new Date(l.split(",")[0]*1000);
  log("parsing records");
  while (l!==undefined) {
    count++;
    if (parseInt(l.split(',')[2]) >= 70) {
      avg[0]++;
      value = parseInt(l.split(',')[1]);
      if (value < limits[0]) {
        limits[0] = value;
      } else if (value > limits[1]) {
        limits[1] = value;
      }
      avg[1] += value;
    }
    l = f.readLine();
  }
  l = undefined;
  value = undefined;
  log("finished parsing");
  if (heartTime)
    menu[" "+heartTime.toString().substr(4,17)] = function(){};
  menu[count + " records"] = function(){};
  menu["Min: " + limits[0]] = function(){};
  menu["Max: " + limits[1]] = function(){};
  menu["Avg: " + Math.round(avg[1] / avg[0])] = function(){};
  menu["Erase"] = function() {
    E.showPrompt("Delete Record?").then(function(v) {
      if (v) {
        if (n == settings.fileNbr) {
          settings.isRecording = false;
          updateSettings();
        }
        require("Storage").open(getFileNbr(n),"r").erase();
        E.showMenu();
        createRecordMenu(viewRecord.bind());
      } else
        return viewRecord(n);
    });
  };
  menu['< Back'] = ()=>{createRecordMenu(viewRecord.bind());};
  Bangle.buzz(200, 0.3);
  return E.showMenu(menu);
}

function stop() {
  E.showMenu();
  load();
}

function graphRecord(n) {
  var headline = "Heart Record " + n;
  E.showMenu({'': headline});
  E.showMessage(
    "Loading Data ...\n\nMay take a while,\nwill vibrate\nwhen done.",
    headline
  );

  const MinMeasurement = 30;
  const MaxMeasurement = 150;
  const GraphXLabel = 35;
  const GraphXZero = 40;
  const GraphY100 = 60;
  const GraphMarkerOffset = 5;
  // calculate number of pixels based on display width
  const MaxValueCount = g.getWidth() - GraphXZero - ( g.getWidth() - 220 ) - GraphMarkerOffset;
  // calculate Y axis "0" pixel
  const GraphYZero = g.getHeight() - g.setFont("Vector", 10).getFontHeight() - GraphMarkerOffset * 2;
  // calculate X axis max drawable pixel
  const GraphXMax = GraphXZero + MaxValueCount;
  // calculate space between labels of scale
  const LabelOffset = (GraphYZero - GraphY100) / (MaxMeasurement - MinMeasurement);

  var lineCount = 0;
  var startLine = 1;
  var f = require("Storage").open(getFileNbr(n),"r");
  var line = f.readLine();

  log("Counting lines");

  while (line !== undefined) {
    lineCount++;
    line = f.readLine();
  }

  log(`lineCount: ${lineCount}`);
  if (lineCount > MaxValueCount)
    startLine = lineCount - MaxValueCount;
  f = undefined;
  line = undefined;
  lineCount = undefined;
  log(`startLine: ${startLine}`);

  f = require("Storage").open(getFileNbr(n),"r");
  line = f.readLine();

  var times = Uint32Array(2);
  var tempCount = 0;
  var positionX = GraphXZero;
  var positionY = GraphYZero;
  var measure;

  while (line !== undefined) {
    currentLine = line;
    line = f.readLine();
    tempCount++;
    if (tempCount == startLine) {
      // generating rgaph in loop when reaching startLine to keep loading
      // message on screen until graph can be drawn
      g.clear().
      // Home for Btn2
        setColor(1, 1, 1).
        drawLine(220, 118, 227, 110).
        drawLine(227, 110, 234, 118).
        drawPoly([222,117,222,125,232,125,232,117], false).
        drawRect(226,120,229,125).

      // headline
        setFontAlign(0, -1, 0).
        setFont("6x8", 2).
        drawString(headline, g.getWidth()/2 - headline.length/2, GraphY100 - g.getFontHeight() - GraphMarkerOffset).

      // Chart
        setColor(1, 1, 0).
        // horizontal bottom line
        drawLine(GraphXZero, GraphYZero + GraphMarkerOffset, GraphXZero, GraphY100).
        // vertical left line
        drawLine(GraphXZero - GraphMarkerOffset, GraphYZero, GraphXMax + GraphMarkerOffset, GraphYZero).
        // scale indicator line for 100%
        drawLine(GraphXZero - GraphMarkerOffset, GraphY100, GraphXZero, GraphY100).
        // scale indicator line for 50%
        drawLine(GraphXZero - GraphMarkerOffset, GraphY100 + (GraphYZero - GraphY100)/2, GraphXZero, GraphY100 + (GraphYZero - GraphY100)/2).
        // background line for 50%
        setColor(1, 1, 1).
        drawLine(GraphXZero + 1, GraphY100 + (GraphYZero - GraphY100)/2, GraphXMax, GraphY100 + (GraphYZero - GraphY100)/2).
        setFontAlign(1, -1, 0).
        setFont("Vector", 10);

      // scale text
      for (var i = MaxMeasurement; i >= MinMeasurement; i-=10) {
        g.drawString(i, GraphXLabel, GraphY100 + LabelOffset * ( MaxMeasurement - i ) - GraphMarkerOffset);
      }

      log("Finished drawing chart");
    } else if (tempCount > startLine) {
      positionX++;
      if (parseInt(currentLine.split(",")[2]) >= 70) {
        g.setColor(1, 0.3, 0.3);
        oldPositionY = positionY;
        measure = parseInt(currentLine.split(",")[1]);
        positionY = GraphYZero - measure + MinMeasurement;
        if (positionY > GraphYZero) {
          positionY = GraphYZero;
        }
        if (positionY < GraphY100) {
          positionY = GraphY100;
        }

        if (times[0] === 0) {
          times[0] = parseInt(currentLine.split(",")[0]);
        }
        if (tempCount == startLine + 1) {
          g.setPixel(positionX, positionY);
        } else {
          g.drawLine(positionX - 1, oldPositionY, positionX, positionY);
          times[1] = parseInt(currentLine.split(",")[0]);
        }
      }
    }
  }

  g.setColor(1, 1, 0).setFont("Vector", 10);
  log('startTime: ' + times[0]);
  log('endTime: ' + times[1]);

  if (times[0] !== 0) {
    g.setFontAlign(-1, -1, 0).
      drawString(getTimeString(times[0]*1000), 15, GraphYZero + 12);
  }

  if (times[1] !== 0) {
    var dateStr = getDateString(times[1]*1000);
    g.setFontAlign(-1, -1, 0).
      drawString(dateStr, GraphXMax/2 - dateStr.length/2 - GraphMarkerOffset, GraphYZero + 12).
      setFontAlign(1, -1, 0).
      drawString(getTimeString(times[1]*1000), GraphXMax, GraphYZero + 12);
  }

  log("Finished rendering data");
  Bangle.buzz(200, 0.3);
  g.flip();
  setWatch(stop, BTN2, {edge:"falling", debounce:50, repeat:false});
  return;
}

showMainMenu();

// vim: et ts=2 sw=2

showMainMenu();
