(function() {
  var settings = Object.assign(
    require('Storage').readJSON("powermanager.default.json", true) || {},
    require('Storage').readJSON("powermanager.json", true) || {}
  );

  if (settings.log) {
    let logFile = require('Storage').open("powermanager.log","a");
    let def = require('Storage').readJSON("powermanager.def.json", true) || {};
    if (!def.start) def.start = Date.now();
    if (!def.deferred) def.deferred = {};
    let hw = require('Storage').readJSON("powermanager.hw.json", true) || {};
    if (!hw.start) hw.start = Date.now();
    if (!hw.power) hw.power = {};

    const saveEvery = 1000 * 60 * 5;
    const TO_WRAP = ["GPS","Compass","Barometer","HRM","LCD"];

    let save = ()=>{
      let defExists = require("Storage").read("powermanager.def.json")!==undefined;
      if (!(!defExists && def.saved)){
        def.saved = Date.now();
        require('Storage').writeJSON("powermanager.def.json", def);
      }
      let hwExists = require("Storage").read("powermanager.hw.json")!==undefined;
      if (!(!hwExists && hw.saved)){
        hw.saved = Date.now();
        require('Storage').writeJSON("powermanager.hw.json", hw);
      }
    }

    setInterval(save, saveEvery);

    E.on("kill", ()=>{
      for (let c of TO_WRAP){
        if (lastPowerOn[c] && Bangle["is"+c+"On"]()){
          hw.power[c] += Date.now() - lastPowerOn[c];
        }
      }
      save();
    });


    let logPower = (type, oldstate, state, app) => {
      logFile.write(Date.now() + ",p," + type + ',' + (oldstate?1:0) + ',' + (state?1:0) + ',' + app + "\n");
    };
    let logDeferred = (type, duration, source) => {
      logFile.write(Date.now() + "," + type + ',' + duration + ',' + source.replace(/\n/g, " ").replace(/,/g,"") + "\n");
    };

    let lastPowerOn = {};

    for (let c of TO_WRAP){
      let functionName = "set" + c + "Power";
      let checkName = "is" + c + "On";
      let type = c + "";
      lastPowerOn[type] = (!lastPowerOn[type] && Bangle[checkName]()) ? Date.now() : undefined;

      lastPowerOn[type] = Date.now();

      Bangle[functionName] = ((o) => (a,b) => {
        let oldstate = Bangle[checkName]();
        let result = o(a,b);
        if (!lastPowerOn[type] && result) {
          //switched on, store time
          lastPowerOn[type] = Date.now();
        } else if (lastPowerOn[type] && !result){
          //switched off
          hw.power[type] += Date.now() - lastPowerOn[type];
          lastPowerOn[type] = undefined;
        }

        if (settings.logDetails) logPower(type, oldstate, result, b);
        return result;
      })(Bangle[functionName]);
    }

    let functions = {};
    let wrapDeferred = ((o,t) => (a) => {
      if (a == eval || typeof a == "string") {
        return o.apply(this, arguments);
      } else {
        let wrapped = a;
        if (!a.__wrapped){
          wrapped = ()=>{
            let start = Date.now();
            let result = a.apply(undefined, arguments.slice(2)); // function arguments for deferred calls start at index 2, first is function, second is time
            let end = Date.now()-start;
            let f = a.toString().substring(0,100);
            if (settings.logDetails) logDeferred(t, end, f);
            if (!def.deferred[f]) def.deferred[f] = 0;
            def.deferred[f] += end;
            return result;
          };
          //copy over properties of functions
          for (let p in a){
            wrapped[p] = a[p];
          }
          //mark function as wrapped
          wrapped.__wrapped = true;
        }
        let newArgs = arguments.slice();
        newArgs[0] = wrapped;
        return o.apply(this, newArgs);
      }
    });

    global.setTimeout = wrapDeferred(global.setTimeout, "t");
    global.setInterval = wrapDeferred(global.setInterval, "i");
  }

  if (settings.warnEnabled){
    var chargingInterval;

    let handleCharging = (charging) => {
        if (charging){
          if (chargingInterval) clearInterval(chargingInterval);
          chargingInterval = setInterval(()=>{
            if (E.getBattery() > settings.warn){
              Bangle.buzz(1000);
            }
          }, 10000);
      }
      if (chargingInterval && !charging){
        clearInterval(chargingInterval);
        chargingInterval = undefined;
      }
    };

    Bangle.on("charging",handleCharging);
    handleCharging(Bangle.isCharging());
  }

  if (settings.forceMonoPercentage){
    var p = (E.getBattery()+E.getBattery()+E.getBattery()+E.getBattery())/4;
    var op = E.getBattery;
    E.getBattery = function() {
      var current = Math.round((op()+op()+op()+op())/4);
      if (Bangle.isCharging() && current > p) p = current;
      if (!Bangle.isCharging() && current < p) p = current;
      return p;
    };
  }
  
  if (settings.forceMonoVoltage){
    var v = (NRF.getBattery()+NRF.getBattery()+NRF.getBattery()+NRF.getBattery())/4;
    var ov = NRF.getBattery;
    NRF.getBattery = function() {
      var current = (ov()+ov()+ov()+ov())/4;
      if (Bangle.isCharging() && current > v) v = current;
      if (!Bangle.isCharging() && current < v) v = current;
      return v;
    };
  }

  if (settings.autoCalibration){
    let chargeStart;
    if (Bangle.isCharging()) chargeStart = Date.now();
    Bangle.on("charging", (charging)=>{
      if (!chargeStart && charging) chargeStart = Date.now();
      if (chargeStart && !charging && (Date.now() - chargeStart > 1000*60*60*3)) require("powermanager").setCalibration();
      if (!charging) chargeStart = undefined;
    });
  }
})();
