const BANGLEJS2 = process.env.HWVERSION == 2; // check for bangle 2
const CONFIGFILE = "sleepphasealarm.json";
const Layout = require("Layout");
const locale = require('locale');
const alarms = require("Storage").readJSON("sched.json",1) || [];
const config = Object.assign({
    logs: [], // array of length 31 with one entry for each day of month
    settings: {
        startBeforeAlarm: 0, // 0 = start immediately, 1..23 = start 1h..23h before alarm time
        disableAlarm: false,
    }
}, require("Storage").readJSON(CONFIGFILE,1) || {});
const active = alarms.filter(alarm => require("sched").getTimeToAlarm(alarm));
const schedSettings = require("sched").getSettings();
let buzzCount = schedSettings.buzzCount;
let logs = [];
let drawTimeTimeout;

// Sleep/Wake detection with Estimation of Stationary Sleep-segments (ESS):
// Marko Borazio, Eugen Berlin, Nagihan Kücükyildiz, Philipp M. Scholl and Kristof Van Laerhoven, "Towards a Benchmark for Wearable Sleep Analysis with Inertial Wrist-worn Sensing Units", ICHI 2014, Verona, Italy, IEEE Press, 2014.
// https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en
//
// Function needs to be called for every measurement but returns a value at maximum once a second (see winwidth)
// start of sleep marker is delayed by sleepthresh due to continous data reading
const winwidth=13; // Actually 12.5 Hz, rounded
const nomothresh=0.023; // Original implementation: 6, resolution 11 bit, scale +-4G = 6/(2^(11-1))*4 = 0.023438 in G
const sleepthresh=600;
var ess_values = [];
var slsnds = 0;
function calc_ess(acc_magn) {"ram"
  ess_values.push(acc_magn);

  if (ess_values.length == winwidth) {
    // calculate standard deviation over ~1s 
    const mean = ess_values.reduce((prev,cur) => cur+prev) / ess_values.length;
    const stddev = Math.sqrt(ess_values.map(val => Math.pow(val-mean,2)).reduce((prev,cur) => prev+cur)/ess_values.length);
    ess_values = [];

    // check for non-movement according to the threshold
    const nonmot = stddev < nomothresh;

    // amount of seconds within non-movement sections
    if (nonmot) {
      slsnds+=1;
      if (slsnds >= sleepthresh) {
        return true; // sleep
      }
    } else {
      slsnds=0;
      return false; // awake
    }
  }
}

// locate next alarm
var nextAlarmDate;
var nextAlarmConfig;
active.forEach(alarm => {
  const now = new Date();
  const time = require("time_utils").decodeTime(alarm.t);
  var dateAlarm = new Date(now.getFullYear(), now.getMonth(), now.getDate(), time.h, time.m);
  if (dateAlarm < now) { // dateAlarm in the past, add 24h
    dateAlarm.setTime(dateAlarm.getTime() + (24*60*60*1000));
  }
  if ((alarm.dow >> dateAlarm.getDay()) & 1) { // check valid day of week
    if (nextAlarmDate === undefined || dateAlarm < nextAlarmDate) {
      nextAlarmDate = dateAlarm;
      nextAlarmConfig = alarm;
    }
  }
});

const LABEL_ETA = /*LANG*/"ETA";
const LABEL_WAKEUP_TIME = /*LANG*/"Alarm at";

var layout = new Layout({
  type:"v", c: [
    {type:"txt", font:"10%", label:"Sleep Phase Alarm", bgCol:g.theme.bgH, fillx: true, height:Bangle.appRect.h/6},
    {type:"txt", font:"16%", label: ' '.repeat(20), id:"date", height:Bangle.appRect.h/6},
    {type:"txt", font:"12%", label: "", id:"alarm_date", height:Bangle.appRect.h/6},
    {type:"txt", font:"10%", label: ' '.repeat(20), id:"eta", height:Bangle.appRect.h/6},
    {type:"txt", font:"12%", label: ' '.repeat(20), id:"state", height:Bangle.appRect.h/6},
  ]
}, {lazy:true});

function drawApp() {
  var alarmHour = nextAlarmDate.getHours();
  var alarmMinute = nextAlarmDate.getMinutes();
  if (alarmHour < 10) alarmHour = "0" + alarmHour;
  if (alarmMinute < 10) alarmMinute = "0" + alarmMinute;
  layout.alarm_date.label = `${LABEL_WAKEUP_TIME}: ${alarmHour}:${alarmMinute}`;
  layout.render();

  function drawTime() {"ram"
    const drawSeconds = !Bangle.isLocked();

    if (Bangle.isLCDOn()) {
      const now = new Date();
      layout.date.label = locale.time(now, !drawSeconds); // hide seconds on bangle 2
      const diff = nextAlarmDate - now;
      const diffHour = Math.floor((diff % 86400000) / 3600000).toString();
      const diffMinutes = Math.floor(((diff % 86400000) % 3600000) / 60000).toString();
      layout.eta.label = `${LABEL_ETA}: ${diffHour}:${diffMinutes.padStart(2, '0')}`;
      layout.render();
    }

    const period = drawSeconds ? 1000 : 60000;
    if (this.drawTimeTimeout !== undefined) {
      clearTimeout(this.drawTimeTimeout);
    }
    drawTimeTimeout = setTimeout(()=>{
      drawTimeTimeout = undefined;
      drawTime();
    }, period - (Date.now() % period));
  }

  Bangle.on('lock', function(on) {
    if (on === false) {
      drawTime();
    }
  });

  drawTime();
}

function buzz() {
  if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
  Bangle.setLCDPower(1);
  require("buzz").pattern(nextAlarmConfig.vibrate || ";");
  if (buzzCount--) {
    setTimeout(buzz, schedSettings.buzzIntervalMillis);
  } else {
    // back to main after finish
    setTimeout(load, 1000);
  }
}

function addLog(time, type) {
  logs.push({time: time, type: type});
  if (logs.length > 1) { // Do not write if there is only one state
    require("Storage").writeJSON(CONFIGFILE, config);
  }
}

// run
var minAlarm = new Date();
var measure = true;
if (nextAlarmDate !== undefined) {
  const logday = BANGLEJS2 ? nextAlarmDate.getDate() : 0;
  config.logs[logday] = []; // overwrite log on each day of month
  logs = config.logs[logday];
  g.clear();
  Bangle.loadWidgets();
  Bangle.drawWidgets();
  let swest_last;

  // minimum alert 30 minutes early
  minAlarm.setTime(nextAlarmDate.getTime() - (30*60*1000));
  run = () => {
    layout.state.label = /*LANG*/"Start";
    layout.render();
    Bangle.setOptions({powerSave: false}); // do not dynamically change accelerometer poll interval
    Bangle.setPollInterval(80); // 12.5Hz
    Bangle.on('accel', (accelData) => {"ram"
      const now = new Date();
      const acc = accelData.mag;
      const swest = calc_ess(acc);

      if (swest !== undefined) {
        if (Bangle.isLCDOn()) {
          layout.state.label = swest ? /*LANG*/"Sleep" : /*LANG*/"Awake";
          layout.render();
        }
        // log
        if (swest_last != swest) {
          if (swest) {
            addLog(new Date(now - sleepthresh*13/12.5*1000), "sleep"); // calculate begin of no motion phase, 13 values/second at 12.5Hz
          } else {
            addLog(now, "awake");
          }
          swest_last = swest;
        }
      }

      if (now >= nextAlarmDate) {
        // The alarm widget should handle this one
        addLog(now, "alarm");
        setTimeout(load, 1000);
      } else if (measure && now >= minAlarm && swest === false) {
        addLog(now, "alarm");
        measure = false;
        if (nextAlarmConfig.js) {
          eval(nextAlarmConfig.js); // run nextAlarmConfig.js if set
        } else {
          buzz();
          if (config.settings.disableAlarm) {
            // disable alarm for scheduler
            nextAlarmConfig.last = now.getDate();
            require('Storage').writeJSON('sched.json', alarms);
          }
        }
      }
    });
  };
  drawApp();
  if (config.settings.startBeforeAlarm === 0) {
    // Start immediately
    run();
  } else {
    // defer start
    layout.state.label = "Deferred";
    layout.render();
    const diff = nextAlarmDate - Date.now();
    let timeout = diff-config.settings.startBeforeAlarm*60*60*1000;
    if (timeout < 0) timeout = 0;
    setTimeout(run, timeout);
  }
} else {
  E.showMessage('No Alarm');
  setTimeout(load, 1000);
}
setWatch(() => load(), BANGLEJS2 ? BTN : BTN3, { repeat: false, edge: "falling" });
