(function() {
let medianPressure;
let threeHourAvrPressure;
let currentPressures = [];
let stop = false; // semaphore

const LOG_FILE = "widbaroalarm.log.json";
const SETTINGS_FILE = "widbaroalarm.json";
const storage = require('Storage');

let settings;

function loadSettings() {
  settings =
      Object.assign(storage.readJSON("widbaroalarm.default.json", true) || {},
                    storage.readJSON(SETTINGS_FILE, true) || {});
}

loadSettings();

function setting(key) { return settings[key]; }

function saveSetting(key, value) {
  settings[key] = value;
  storage.write(SETTINGS_FILE, settings);
}

const interval = setting("interval");

let history3 =
    storage.readJSON(LOG_FILE, true) || []; // history of recent 3 hours

function showAlarm(body, key, type) {
  if (body == undefined)
    return;
  stop = true;

  E.showPrompt(body, {
     title : "Pressure " + (type != undefined ? type : "alarm"),
     buttons : {"Ok" : 1, "Dismiss" : 2, "Pause" : 3}
   }).then(function(v) {
    const tsNow = Math.round(Date.now() / 1000); // seconds

    if (v == 1) {
      saveSetting(key, tsNow);
    }
    if (v == 2) {
      // save timestamp of the future so that we do not warn again for the same
      // event until then
      saveSetting(key, tsNow + 60 * setting('dismissDelayMin'));
    }
    if (v == 3) {
      // save timestamp of the future so that we do not warn again for the same
      // event until then
      saveSetting(key, tsNow + 60 * setting('pauseDelayMin'));
    }
    stop = false;
    load();
  });

  if (setting("buzz") && !(storage.readJSON('setting.json', 1) || {}).quiet) {
    Bangle.buzz();
  }

  setTimeout(function() {
    stop = false;
    load();
  }, 20000);
}

/*
 * returns true if an alarm should be triggered
 */
function doWeNeedToAlarm(key) {
  const tsNow = Math.round(Date.now() / 1000); // seconds
  return setting(key) == undefined || setting(key) == 0 || tsNow > setting(key);
}

function isValidPressureValue(pressure) {
  if (pressure == undefined || pressure <= 0)
    return false;
  return pressure > 800 && pressure < 1200; // very rough values
}

function handlePressureValue(pressure) {
  if (pressure == undefined || pressure <= 0)
    return;

  const ts = Math.round(Date.now() / 1000); // seconds
  const d = {"ts" : ts, "p" : pressure};

  history3.push(d);

  // delete oldest entries until we have max 50
  while (history3.length > 50) {
    history3.shift();
  }

  // delete entries older than 3h
  for (let i = 0; i < history3.length; i++) {
    if (history3[i]["ts"] < ts - (3 * 60 * 60)) {
      history3.shift();
    } else {
      break;
    }
  }

  // write data to storage
  storage.writeJSON(LOG_FILE, history3);

  calculcate3hAveragePressure();

  if (setting("lowalarm") || setting("highalarm") || setting("drop3halarm") ||
      setting("raise3halarm")) {
    checkForAlarms(pressure, ts);
  }
}

function checkForAlarms(pressure, ts) {
  let alreadyWarned = false;

  if (setting("lowalarm")) {
    // Is below the alarm threshold?
    if (pressure <= setting("min")) {
      if (!doWeNeedToAlarm("lowWarnTs")) {
        showAlarm("Pressure low: " + Math.round(pressure) + " hPa", "lowWarnTs",
                  "low");
        alreadyWarned = true;
      }
    } else {
      saveSetting("lowWarnTs", 0);
    }
  }

  if (setting("highalarm")) {
    // Is above the alarm threshold?
    if (pressure >= setting("max")) {
      if (doWeNeedToAlarm("highWarnTs")) {
        showAlarm("Pressure high: " + Math.round(pressure) + " hPa",
                  "highWarnTs", "high");
        alreadyWarned = true;
      }
    } else {
      saveSetting("highWarnTs", 0);
    }
  }

  if (history3.length > 0 && !alreadyWarned) {
    // 3h change detection
    const drop3halarm = setting("drop3halarm");
    const raise3halarm = setting("raise3halarm");
    if (drop3halarm > 0 || raise3halarm > 0) {
      // we need at least 30 minutes of data for reliable detection
      const diffDateAge = Math.abs(history3[0]["ts"] - ts);
      if (diffDateAge > 30 * 60) {
        // Get oldest entry:
        const oldestPressure = history3[0]["p"];
        if (oldestPressure != undefined && oldestPressure > 0) {
          const diffPressure = Math.abs(oldestPressure - pressure);

          // drop alarm
          if (drop3halarm > 0 && oldestPressure > pressure) {
            if (diffPressure >= drop3halarm) {
              if (doWeNeedToAlarm("dropWarnTs")) {
                showAlarm((Math.round(diffPressure * 10) / 10) +
                              " hPa/3h from " + Math.round(oldestPressure) +
                              " to " + Math.round(pressure) + " hPa",
                          "dropWarnTs", "drop");
              }
            } else {
              if (ts > setting("dropWarnTs"))
                saveSetting("dropWarnTs", 0);
            }
          } else {
            if (ts > setting("dropWarnTs"))
              saveSetting("dropWarnTs", 0);
          }

          // raise alarm
          if (raise3halarm > 0 && oldestPressure < pressure) {
            if (diffPressure >= raise3halarm) {
              if (doWeNeedToAlarm("raiseWarnTs")) {
                showAlarm((Math.round(diffPressure * 10) / 10) +
                              " hPa/3h from " + Math.round(oldestPressure) +
                              " to " + Math.round(pressure) + " hPa",
                          "raiseWarnTs", "raise");
              }
            } else {
              if (ts > setting("raiseWarnTs"))
                saveSetting("raiseWarnTs", 0);
            }
          } else {
            if (ts > setting("raiseWarnTs"))
              saveSetting("raiseWarnTs", 0);
          }
        }
      }
    }
  }
}

function calculcate3hAveragePressure() {
  if (history3 != undefined && history3.length > 0) {
    let sum = 0;
    for (let i = 0; i < history3.length; i++) {
      sum += history3[i]["p"];
    }
    threeHourAvrPressure = sum / history3.length;
  } else {
    threeHourAvrPressure = undefined;
  }
}

function barometerPressureHandler(e) {
  const MEDIANLENGTH = 20;
  while (currentPressures.length > MEDIANLENGTH)
    currentPressures.pop();

  const pressure = e.pressure;
  if (isValidPressureValue(pressure)) {
    currentPressures.unshift(pressure);
    let median = currentPressures.slice().sort();

    if (median.length > 10) {
      var mid = median.length >> 1;
      medianPressure = Math.round(E.sum(median.slice(mid - 4, mid + 5)) / 9);
      if (medianPressure > 0) {
        turnOff();
        WIDGETS.baroalarm.draw();
        handlePressureValue(medianPressure);
      }
    }
  }
}

/*
 turn on barometer power
 take multiple measurements
 sort the results
 take the middle one (median)
 turn off barometer power
*/
function getPressureValue() {
  if (stop) return;
  Bangle.setBarometerPower(true, "widbaroalarm");
  Bangle.on('pressure', barometerPressureHandler);
  setTimeout(turnOff, 30000);
}

function turnOff() {
  Bangle.removeListener('pressure', barometerPressureHandler);
  Bangle.setBarometerPower(false, "widbaroalarm");
}

function draw() {
  if (this.x == undefined || this.y != 0)
    return; // widget not yet there

  g.reset("widget").clearRect(this.x, this.y, this.x + this.width - 1, this.y + 23);

  if (setting("show")) {
    g.setFont("6x8", 1).setFontAlign(1, 0);
    if (medianPressure == undefined) {
      // lets load last value from log (if available)
      if (history3.length > 0) {
        medianPressure = history3[history3.length - 1]["p"];
        g.drawString(Math.round(medianPressure), this.x + 24, this.y + 6);
      } else {
        g.drawString("...", this.x + 24, this.y + 6);
      }
    } else {
      g.drawString(Math.round(medianPressure), this.x + 24, this.y + 6);
    }

    if (threeHourAvrPressure == undefined) {
      calculcate3hAveragePressure();
    }
    if (threeHourAvrPressure != undefined) {
      if (medianPressure != undefined) {
        const diff = Math.round(medianPressure - threeHourAvrPressure);
        g.drawString((diff > 0 ? "+" : "") + diff, this.x + 24,
                     this.y + 6 + 10);
      }
    }
  }
}

WIDGETS["baroalarm"] = {
  width : setting("show") ? 24 : 0,
  area : "tr",
  draw : draw
};

// delay pressure measurement by interval-lastrun
const lastRun = history3.length > 0 ? history3[history3.length-1].ts : 0;
const lastRunAgo = Math.round(Date.now() / 1000) - lastRun;
let diffNextRun = interval*60-lastRunAgo;
if (diffNextRun < 0) {
  diffNextRun = 0; // run asap
}
setTimeout(() => {
  if (interval > 0) {
    setInterval(getPressureValue, interval * 60000);
  }
  getPressureValue();
}, diffNextRun*1000);

})();
