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

const BANGLEJS2 = process.env.HWVERSION==2;
const storage = require('Storage');
let settings;
const scrolls = [];
let menuScroller;

function updateSettings() {
  //storage.erase('setting.json'); // - not needed, just causes extra writes if settings were the same
  storage.write('setting.json', settings);
}

function pushMenu(menu) {
  if(menuScroller) scrolls.push(menuScroller.scroll);

  // if !menu, we're just pushing and something else takes
  // care of E.showMenu()
  if(menu) {
    const m = E.showMenu(menu);
    menuScroller = m.scroller;
    return m;
  }
}

function restoreMenu(menu) {
  // equivalent to pushMenu(null); popMenu(menu);
  if(!menu[""]) menu[""] = {};
  if(menuScroller) // may be undefined on BangleJS1
    menu[""].scroll = menuScroller.scroll;
  menuScroller = E.showMenu(menu).scroller;
}

function popMenu(menu) {
  if(!menu[""]) menu[""] = {};
  menu[""].scroll = scrolls.pop() | 0;
  const m = E.showMenu(menu);
  menuScroller = m.scroller;
  return m;
}

function updateOptions() {
  var o = settings.options;
  // Check to make sure nobody disabled all wakeups and locked themselves out!
  if (BANGLEJS2) {
    if (!(o.wakeOnBTN1||o.wakeOnFaceUp||o.wakeOnTouch||o.wakeOnDoubleTap||o.wakeOnTwist)) {
      o.wakeOnBTN1 = true;
    }
  } else {
    if (!(o.wakeOnBTN1||o.wakeOnBTN2||o.wakeOnBTN3||o.wakeOnFaceUp||o.wakeOnTouch||o.wakeOnTwist))
      o.wakeOnBTN2 = true;
  }
  updateSettings();
  Bangle.setOptions(o)
}



function resetSettings() {
  settings = {
    ble: true,                      // Bluetooth enabled by default
    blerepl: true,                  // Is REPL on Bluetooth - can Espruino IDE be used?
    log: false,                     // Do log messages appear on screen?
    quiet: 0,                       // quiet mode:  0: off, 1: priority only, 2: total silence
    timeout: 10,                    // Default LCD timeout in seconds
    vibrate: true,                  // Vibration enabled by default. App must support
    beep: BANGLEJS2 ? true : "vib", // Beep enabled by default. App must support
    chargeBuzz: true,               // Vibrate on connect charger
    timezone: 0,                    // Set the timezone for the device
    HID: false,                     // BLE HID mode, off by default
    clock: null,                    // a string for the default clock's name
    // clockHasWidgets: false,      // Does the clock in 'clock' contain the string 'Bangle.loadWidgets'
    "12hour" : false,               // 12 or 24 hour clock?
    firstDayOfWeek: 0,              // 0 -> Sunday (default), 1 -> Monday
    brightness: 1,                  // LCD brightness from 0 to 1
    // welcomed : undefined/true (whether welcome app should show)
    options: {
      wakeOnBTN1: true,
      wakeOnBTN2: true,
      wakeOnBTN3: true,
      wakeOnFaceUp: false,
      wakeOnTouch: false,
      wakeOnTwist: false,
      twistThreshold: 819.2,
      twistMaxY: -800,
      twistTimeout: 1000
    },
  };
  if (Bangle.haptic) // don't add by default as it'll break 2v29 and earlier firmwares
    settings.options.hapticTime = 25;
  updateSettings();
}

settings = storage.readJSON('setting.json', 1);
if (("object" != typeof settings) ||
    ("object" != typeof settings.options))
  resetSettings();

function mainMenu() {

  const mainmenu = {
    '': { 'title': /*LANG*/'Settings' },
    '< Back': ()=>load(),
    /*LANG*/'Apps': ()=>pushMenu(appSettingsMenu()),
    /*LANG*/'System': ()=>pushMenu(systemMenu()),
    /*LANG*/'Bluetooth': ()=>pushMenu(BLEMenu()),
    /*LANG*/'Sound/Vibration': ()=>pushMenu(vibrateMenu()),
    /*LANG*/'Utils': ()=>pushMenu(utilMenu())
  };

  return mainmenu;
}

function systemMenu() {
  const mainmenu = {
    '': { 'title': /*LANG*/'System' },
    '< Back': ()=>popMenu(mainMenu()),
    /*LANG*/'Theme': ()=>showThemeMenu(),
    /*LANG*/'LCD': ()=>pushMenu(LCDMenu()),
    /*LANG*/'Locale': ()=>pushMenu(localeMenu()),
    /*LANG*/'Clock': ()=>pushMenu(clockMenu()),
    /*LANG*/'Launcher': ()=>pushMenu(launcherMenu()),
    /*LANG*/'Date & Time': ()=>pushMenu(setTimeMenu())
  };
  if (Bangle.getPressure) mainmenu[/*LANG*/"Altitude"] = ()=>pushMenu(showAltitude());

  return mainmenu;
}

function vibrateMenu() {
  var beepMenuItem;
  if (BANGLEJS2) {
    beepMenuItem = {
      value: settings.beep!=false,
      onchange: v => {
        settings.beep = v;
        updateSettings();
        if (settings.beep) {
          analogWrite(VIBRATE,0.1,{freq:2000});
          setTimeout(()=>VIBRATE.reset(),200);
        } // beep with vibration moter
      }
    };
  } else { // Bangle.js 1
    var beepV = [false, true, "vib"];
    var beepN = [/*LANG*/"Off", /*LANG*/"Piezo", /*LANG*/"Vibrate"];
    beepMenuItem = {
      value: Math.max(0 | beepV.indexOf(settings.beep),0),
      min: 0, max: beepV.length-1,
      format: v => beepN[v],
      onchange: v => {
        settings.beep = beepV[v];
        if (v==1) { analogWrite(D18,0.5,{freq:2000});setTimeout(()=>D18.reset(),200); } // piezo on Bangle.js 1
        else if (v==2) { analogWrite(VIBRATE,0.1,{freq:2000});setTimeout(()=>VIBRATE.reset(),200); } // vibrate
        updateSettings();
      }
    };
  }

  let mainmenu = {
    '': { 'title': /*LANG*/'Sound/Vibration' },
    '< Back': ()=>popMenu(mainMenu()),
    /*LANG*/'Beep': beepMenuItem,
    /*LANG*/'Vibration': {
      value: settings.vibrate,
      onchange: () => {
        settings.vibrate = !settings.vibrate;
        updateSettings();
        if (settings.vibrate) {
          VIBRATE.write(1);
          setTimeout(() => VIBRATE.write(0), 10);
        }
      }
    }
  };
  if (Bangle.haptic)
    mainmenu = Object.assign(mainmenu, {
      /*LANG*/'Haptic Strength': {
        value: settings.options.hapticTime ?? 25,  // ?? 25 converts null or undefined to 25
        min: 0, max: 50,
        step:5,
        format: v => v==0?/*LANG*/"Off":v,
        onchange: v => {
          settings.options.hapticTime = v;
          updateOptions();
        }
      }
    });
  return Object.assign(mainmenu, {
    /*LANG*/"Quiet Mode": {
      value: (settings.quiet|0)%3,
      min:0, max:2,
      format: v => [/*LANG*/"Off", /*LANG*/"Alarms", /*LANG*/"Silent"][v],
      onchange: v => {
        settings.quiet = v%3;
        updateOptions();
        if ("qmsched" in WIDGETS) WIDGETS["qmsched"].draw();
      },
    },
    /*LANG*/"Charge Vibration": {
      value: !!settings.chargeBuzz,
      onchange: v => {
        settings.chargeBuzz = v;
        updateSettings();
      },
    }
  });
}


function BLEMenu() {
  var hidV = [false, "kbmedia", "kb", "com", "joy"];
  var hidN = [/*LANG*/"Off", /*LANG*/"Kbrd & Media", /*LANG*/"Kbrd", /*LANG*/"Kbrd & Mouse", /*LANG*/"Joystick"];
  var privacy = [/*LANG*/"Off", /*LANG*/"Show name", /*LANG*/"Hide name"];

  var menu = {
    '': { 'title': /*LANG*/'Bluetooth' },
    '< Back': ()=>popMenu(mainMenu()),
    /*LANG*/'Make Connectable': ()=>makeConnectable(),
    /*LANG*/'BLE': {
      value: settings.ble,
      onchange: () => {
        settings.ble = !settings.ble;
        updateSettings();
      }
    },
    /*LANG*/'Programmable': {
      value: settings.blerepl,
      onchange: () => {
        settings.blerepl = !settings.blerepl;
        updateSettings();
      }
    },
    /*LANG*/'HID': {
      value: Math.max(0,0 | hidV.indexOf(settings.HID)),
      min: 0, max: hidN.length-1,
      format: v => hidN[v],
      onchange: v => {
        settings.HID = hidV[v];
        updateSettings();
      }
    },
    /*LANG*/'Passkey': {
      value: settings.passkey?settings.passkey:/*LANG*/"none",
      onchange: () => setTimeout(() => pushMenu(passkeyMenu())) // graphical_menu redraws after the call
    },
    /*LANG*/'Whitelist': {
      value:
        (
          (settings.whitelist_disabled || !settings.whitelist) ? /*LANG*/"Off" : /*LANG*/"On"
        ) + (
          settings.whitelist
          ? " (" + settings.whitelist.length + ")"
          : ""
        ),
      onchange: () => setTimeout(() => pushMenu(whitelistMenu())) // graphical_menu redraws after the call
    }
  };
  if (BANGLEJS2) menu[/*LANG*/'Privacy'] = {
      min: 0, max: privacy.length-1,
      format: v => privacy[v],
      value: (() => {
        // settings.bleprivacy may be some custom object, but we ignore that for now
        if (settings.bleprivacy && settings.blename === false) return 2;
        if (settings.bleprivacy) return 1;
        return 0;
      })(),
      onchange: v => {
        settings.bleprivacy = 0;
        delete settings.blename;
        switch (v) {
          case 0:
            break;
          case 1:
            settings.bleprivacy = 1;
            break;
          case 2:
            settings.bleprivacy = 1;
            settings.blename = false;
            break;
        }
        updateSettings();
      }
    };
  return menu;
}

function showThemeMenu(pop) {
  function cl(x) { return g.setColor(x).getColor(); }
  function upd(th) {
    g.theme = th;
    settings.theme = th;
    updateSettings();
    delete g.reset;
    g._reset = g.reset;
    g.reset = function(n) { return g._reset().setColor(th.fg).setBgColor(th.bg); };
    g.clear = function(n) { if (n) g.reset(); return g.clearRect(0,0,g.getWidth(),g.getHeight()); };
    g.clear(1);
    Bangle.drawWidgets();
    m.draw();
  }

  var themesMenu = {
    '':{title:/*LANG*/'Theme'},
    '< Back': ()=>popMenu(systemMenu()),
    /*LANG*/'Dark BW': ()=>{
      upd({
        fg:cl("#fff"), bg:cl("#000"),
        fg2:cl("#fff"), bg2:cl("#004"),
        fgH:cl("#fff"), bgH:cl("#00f"),
        fgW:cl("#fff"), bgW:cl("#000"),
        dark:true
      });
    },
    /*LANG*/'Light BW': ()=>{
      upd({
        fg:cl("#000"), bg:cl("#fff"),
        fg2:cl("#000"), bg2:cl("#cff"),
        fgH:cl("#000"), bgH:cl("#0ff"),
        fgW:cl("#000"), bgW:cl("#fff"),
        dark:false
      });
    }
  };

  storage.list(/^.*\.theme$/).forEach(
    n => {
      let newTheme = storage.readJSON(n);
      themesMenu[newTheme.name ? newTheme.name : n] = () => {
        upd({
          fg:cl(newTheme.fg), bg:cl(newTheme.bg),
          fg2:cl(newTheme.fg2), bg2:cl(newTheme.bg2),
          fgH:cl(newTheme.fgH), bgH:cl(newTheme.bgH),
          fgW:cl(newTheme.fgW), bgW:cl(newTheme.bgW),
          dark:newTheme.dark
        });
      };
    }
  );

  themesMenu[/*LANG*/'Customize'] = () => showCustomThemeMenu();

  var m = (pop ? popMenu : pushMenu)(themesMenu);

  function showCustomThemeMenu() {
    function setT(t, v) {
      let th = g.theme;
      th[t] = v;
      if (t==="bg") {
        th['dark'] = (v===cl("#000"));
      }
      upd(th);
    }
    let rgb = {};
    rgb[/*LANG*/'black'] = "#000";
    rgb[/*LANG*/'white'] = "#fff";
    rgb[/*LANG*/'red'] = "#f00";
    rgb[/*LANG*/'green'] = "#0f0";
    rgb[/*LANG*/'blue'] = "#00f";
    rgb[/*LANG*/'cyan'] = "#0ff";
    rgb[/*LANG*/'magenta'] = "#f0f";
    rgb[/*LANG*/'yellow'] = "#ff0";
    if (!BANGLEJS2) {
      // these would cause dithering, which is not great for e.g. text
      rgb[/*LANG*/'orange'] = "#ff7f00";
      rgb[/*LANG*/'purple'] = "#7f00ff";
      rgb[/*LANG*/'grey'] = "#7f7f7f";
    }
    let colors = [], names = [];
    for(const c in rgb) {
      names.push(c);
      colors.push(cl(rgb[c]));
    }
    let menu = {
      '':{title:/*LANG*/'Custom Theme'},
      "< Back": () => showThemeMenu(1)
    };
    const labels = {
      fg: /*LANG*/'Foreground', bg: /*LANG*/'Background',
      fg2: /*LANG*/'Foreground 2', bg2: /*LANG*/'Background 2',
      fgH: /*LANG*/'Highlight FG', bgH: /*LANG*/'Highlight BG',
      fgW: /*LANG*/'Widget FG', bgW: /*LANG*/'Widget BG',
    };
    ["fg", "bg", "fg2", "bg2", "fgH", "bgH", "fgW", "bgW"].forEach(t => {
      menu[labels[t]] = {
          min : 0, max : colors.length-1, wrap : true,
          value: Math.max(colors.indexOf(g.theme[t]),0),
          format: v => names[v],
          onchange: function(v) {
            var c = colors[v];
            // if we select the same fg and bg: set the other to the old color
            // e.g. bg=black;fg=white, user selects fg=black -> bg changes to white automatically
            // so users don't end up with a black-on-black menu
            if (t === 'fg' && g.theme.bg === c) setT('bg', g.theme.fg);
            if (t === 'bg' && g.theme.fg === c) setT('fg', g.theme.bg);
            setT(t, c);
          },
        };
    });
    m = pushMenu(menu);
  }
}

function passkeyMenu() {
  var menu = {
    "< Back" : ()=>popMenu(BLEMenu()),
    /*LANG*/"Disable" : () => {
      settings.passkey = undefined;
      updateSettings();
      popMenu(BLEMenu());
    }
  };
  if (!settings.passkey || settings.passkey.length!=6) {
    settings.passkey = "123456";
    updateSettings();
  }
  for (var i=0;i<6;i++) (function(i){
    menu[`Digit ${i+1}`] = {
      value : 0|settings.passkey[i],
      min: 0, max: 9,
      onchange: v => {
        var p = settings.passkey.split("");
        p[i] = v;
        settings.passkey = p.join("");
        updateSettings();
      }
    };
  })(i);
  return menu;
}

function whitelistMenu() {
  var menu = {
    "< Back" : ()=>popMenu(BLEMenu()),
  };
  if (settings.whitelist_disabled) {
    menu[/*LANG*/"Enable"] = () => {
      delete settings.whitelist_disabled;
      updateSettings();
      popMenu(BLEMenu());
    };
  } else {
    menu[/*LANG*/"Disable"] = () => {
      settings.whitelist_disabled = true;
      updateSettings();
      popMenu(BLEMenu());
    };
  }

  if (settings.whitelist) settings.whitelist.forEach(function(d){
    menu[d.substr(0,17)] = function() {
      E.showPrompt(/*LANG*/'Remove\n'+d).then((v) => {
        if (v) {
          settings.whitelist.splice(settings.whitelist.indexOf(d),1);
          updateSettings();
        }
        setTimeout(() => restoreMenu(whitelistMenu()), 50);
      });
    }
  });
  menu[/*LANG*/'Add Device']=function() {
    E.showAlert(/*LANG*/"Connect device\nto add to\nwhitelist",/*LANG*/"Whitelist").then(function() {
      NRF.removeAllListeners('connect');
      restoreMenu(whitelistMenu());
    });
    NRF.removeAllListeners('connect');
    NRF.on('connect', function(addr) {
      if (!settings.whitelist) settings.whitelist=[];
      delete settings.whitelist_disabled;
      if (NRF.resolveAddress !== undefined) {
        let resolvedAddr = NRF.resolveAddress(addr);
        if (resolvedAddr !== undefined) {
          addr = resolvedAddr + " (resolved)";
        }
      }
      settings.whitelist.push(addr);
      updateSettings();
      NRF.removeAllListeners('connect');
      restoreMenu(whitelistMenu());
    });
  };
  return menu;
}

function LCDMenu() {
  // converts g to Espruino internal unit
  function gToInternal(g) { return g * 8192; }
  // converts Espruino internal unit to g
  function internalToG(u) { return u / 8192; }

  var rotNames = [/*LANG*/"No",/*LANG*/"Rotate CW",/*LANG*/"Left Handed",/*LANG*/"Rotate CCW",/*LANG*/"Mirror"];

  const lcdMenu = {
    '': { 'title': 'LCD' },
    '< Back': ()=>popMenu(systemMenu()),
  };
  if (BANGLEJS2)
    Object.assign(lcdMenu, {
    /*LANG*/'Calibrate': () => showTouchscreenCalibration()
    });
  Object.assign(lcdMenu, {
    /*LANG*/'LCD Brightness': {
      value: settings.brightness,
      min : BANGLEJS2 ? 0 : 0.1,
      max: 1,
      step: 0.1,
      onchange: v => {
        settings.brightness = v ?? 1;
        updateSettings();
        Bangle.setLCDBrightness(settings.brightness);
      }
    },
    /*LANG*/'LCD Timeout': {
      value: settings.timeout,
      min: 0,
      max: 60,
      step: 5,
      onchange: v => {
        settings.timeout = 0 | v;
        updateSettings();
        Bangle.setLCDTimeout(settings.timeout);
      }
    },
    /*LANG*/'Rotate': {
      value: 0|settings.rotate,
      min: 0,
      max: rotNames.length-1,
      format: v=> rotNames[v],
      onchange: v => {
        settings.rotate = 0 | v;
        updateSettings();
        g.setRotation(settings.rotate&3,settings.rotate>>2).clear();
        Bangle.drawWidgets();
      }
    }
  });

  if (BANGLEJS2) {
    Object.assign(lcdMenu, {
      /*LANG*/'Wake on Button': {
        value: !!settings.options.wakeOnBTN1,
        onchange: () => {
          settings.options.wakeOnBTN1 = !settings.options.wakeOnBTN1;
          updateOptions();
        }
      },
      /*LANG*/'Wake on Tap': {
        value: !!settings.options.wakeOnTouch,
        onchange: () => {
          settings.options.wakeOnTouch = !settings.options.wakeOnTouch;
          updateOptions();
        }
      }
    });
    if (process.env.VERSION.replace("v",0)>=2020)
      Object.assign(lcdMenu, {
        /*LANG*/'Wake on Double Tap': {
          value: !!settings.options.wakeOnDoubleTap,
          onchange: () => {
            settings.options.wakeOnDoubleTap = !settings.options.wakeOnDoubleTap;
            updateOptions();
          }
        }
      });
  } else
    Object.assign(lcdMenu, {
     /*LANG*/'Wake on BTN1': {
      value: !!settings.options.wakeOnBTN1,
      onchange: () => {
        settings.options.wakeOnBTN1 = !settings.options.wakeOnBTN1;
        updateOptions();
      }
    },
    /*LANG*/'Wake on BTN2': {
      value: !!settings.options.wakeOnBTN2,
      onchange: () => {
        settings.options.wakeOnBTN2 = !settings.options.wakeOnBTN2;
        updateOptions();
      }
    },
    /*LANG*/'Wake on BTN3': {
      value: !!settings.options.wakeOnBTN3,
      onchange: () => {
        settings.options.wakeOnBTN3 = !settings.options.wakeOnBTN3;
        updateOptions();
      }
    },
    /*LANG*/'Wake on Touch': {
      value: !!settings.options.wakeOnTouch,
      onchange: () => {
        settings.options.wakeOnTouch = !settings.options.wakeOnTouch;
        updateOptions();
      }
    }});
  Object.assign(lcdMenu, {
    /*LANG*/'Wake on FaceUp': {
      value: !!settings.options.wakeOnFaceUp,
      onchange: () => {
        settings.options.wakeOnFaceUp = !settings.options.wakeOnFaceUp;
        updateOptions();
      }
    },
    /*LANG*/'Wake on Twist': {
      value: !!settings.options.wakeOnTwist,
      onchange: () => {
        settings.options.wakeOnTwist = !settings.options.wakeOnTwist;
        updateOptions();
      }
    },
    /*LANG*/'Twist Threshold': {
      value: internalToG(settings.options.twistThreshold),
      min: -0.5,
      max: 0.5,
      step: 0.01,
      onchange: v => {
        settings.options.twistThreshold = gToInternal(v || 0.1);
        updateOptions();
      }
    },
    /*LANG*/'Twist Max Y': {
      value: settings.options.twistMaxY,
      min: -1500,
      max: 1500,
      step: 100,
      onchange: v => {
        settings.options.twistMaxY = v || -800;
        updateOptions();
      }
    },
    /*LANG*/'Twist Timeout': {
      value: settings.options.twistTimeout,
      min: 0,
      max: 2000,
      step: 100,
      onchange: v => {
        settings.options.twistTimeout = v || 1000;
        updateOptions();
      }
    }
  });

  return lcdMenu
}

function localeMenu() {
  const localemenu = {
    '': { 'title': /*LANG*/'Locale' },
    '< Back': ()=>popMenu(systemMenu()),
    /*LANG*/'Time Zone': {
      value: settings.timezone,
      format: v => (v > 0 ? "+" : "") + v,
      min: -11,
      max: 13,
      step: 0.5,
      onchange: v => {
        settings.timezone = v || 0;
        updateSettings();
      }
    },
    /*LANG*/'Time Format': {
      value: !!settings["12hour"],
      format: v => v ? "12h" : "24h",
      onchange: v => {
        settings["12hour"] = v;
        updateSettings();
      }
    },
    /*LANG*/'Start Week On': {
      value: settings["firstDayOfWeek"] || 0,
      min: 0, // Sunday
      max: 1, // Monday
      format: v => require("date_utils").dow(v, 1),
      onchange: v => {
        settings["firstDayOfWeek"] = v;
        updateSettings();
      },
    }
  };
  return localemenu;
}

function utilMenu() {
  var menu = {
    '': { 'title': /*LANG*/'Utilities' },
    '< Back': ()=>popMenu(mainMenu()),
    /*LANG*/'Debug': {
      value: E.clip(0|settings.log,0,3),
      min: 0,
      max: 3,
      format: v => [/*LANG*/"Off",/*LANG*/"Display",/*LANG*/"Log", /*LANG*/"Both"][E.clip(0|v,0,3)],
      onchange: v => {
        settings.log = v;
        updateSettings();
      }
    },
    /*LANG*/'Compact Storage': () => {
      E.showMessage(/*LANG*/"Compacting...\nTakes approx\n1 minute",{title:/*LANG*/"Storage"});
      storage.compact();
      restoreMenu(utilMenu());
    },
    /*LANG*/'Rewrite Settings': () => {
      storage.write(".boot0","eval(require('Storage').read('bootupdate.js'));");
      load("setting.app.js");
    },
    /*LANG*/'Flatten Battery': () => {
      E.showMessage(/*LANG*/'Flattening battery - this can take hours.\nLong-press button to cancel.');
      Bangle.setLCDTimeout(0);
      Bangle.setLCDPower(1);
      Bangle.setLCDBrightness(1);
      if (Bangle.setGPSPower) Bangle.setGPSPower(1,"flat");
      if (Bangle.setHRMPower) Bangle.setHRMPower(1,"flat");
      if (Bangle.setCompassPower) Bangle.setCompassPower(1,"flat");
      if (Bangle.setBarometerPower) Bangle.setBarometerPower(1,"flat");
      setInterval(function() {
        var i=1000;while (i--);
      }, 1);
    }
  };
  const back = () => {
    restoreMenu(utilMenu());
  };

  if (BANGLEJS2)
    menu[/*LANG*/'Calibrate Battery'] = () => {
      E.showPrompt(/*LANG*/"Is the battery fully charged?",{title:/*LANG*/"Calibrate",back}).then(ok => {
        if (ok) {
          var s=storage.readJSON("setting.json");
          s.batFullVoltage = (analogRead(D3)+analogRead(D3)+analogRead(D3)+analogRead(D3))/4;
          storage.writeJSON("setting.json",s);
          E.showAlert(/*LANG*/"Calibrated!").then(() => load("setting.app.js"));
        } else {
          E.showAlert(/*LANG*/"Please charge Bangle.js for 3 hours and try again").then(back);
        }
      });
    };
  menu[/*LANG*/'Reset Settings'] = () => {
      E.showPrompt(/*LANG*/'Reset to Defaults?',{title:/*LANG*/"Settings",back}).then((v) => {
        if (v) {
          E.showMessage(/*LANG*/'Resetting');
          resetSettings();
          setTimeout(() => popMenu(mainMenu()), 50);
        } else restoreMenu(utilMenu());
      });
    };
  menu[/*LANG*/"Turn Off"] = () => {
    E.showPrompt(/*LANG*/"Are you sure? Alarms and timers won't fire", {
      title:/*LANG*/"Turn Off",back
    }).then((confirmed) => {
      if (confirmed) {
        E.showMessage(/*LANG*/"See you\nlater!", /*LANG*/"Goodbye");
        setTimeout(() => {
          // clear the screen so when the user will turn on the watch they'll see
          // an empty screen instead of the latest displayed screen
          E.showMessage();
          g.clear(true);

          Bangle.softOff ? Bangle.softOff() : Bangle.off();
        }, 2500);
      } else {
        restoreMenu(utilMenu());
      }
    });
  };
  if (Bangle.factoryReset) {
    menu[/*LANG*/'Factory Reset'] = ()=>{
      E.showPrompt(/*LANG*/'This will remove everything!',{title:/*LANG*/"Factory Reset",back}).then((v) => {
        if (v) {
          var n = ((Math.random()*4)&3) + 1;
          E.showPrompt(/*LANG*/"To confirm, please press "+n,{
            title:/*LANG*/"Factory Reset",
            buttons : {"1":1,"2":2,"3":3,"4":4},
            back
          }).then(function(v) {
            if (v==n) {
              E.showMessage();
              Terminal.setConsole();
              Bangle.factoryReset();
            } else {
              back();
            }
          });
        } else back();
      });
    }
  }

  return menu;
}

function makeConnectable() {
  try { NRF.wake(); } catch (e) { }
  Bluetooth.setConsole(1);
  NRF.ignoreWhitelist = 1;
  var name = "Bangle.js " + NRF.getAddress().substr(-5).replace(":", "");
  E.showPrompt(name + /*LANG*/"\nStay Connectable?", { title: /*LANG*/"Connectable" }).then(r => {
    if (settings.ble != r) {
      settings.ble = r;
      updateSettings();
    }
    if (!r) try { NRF.sleep(); } catch (e) { }
    delete NRF.ignoreWhitelist;
    restoreMenu(BLEMenu());
  });
}
function clockMenu() {
  var clockApps = storage.list(/\.info$/)
    .map(app => {var a=storage.readJSON(app, 1);return (a&&a.type == "clock")?a:undefined})
    .filter(app => app) // filter out any undefined apps
    .sort((a, b) => a.sortorder - b.sortorder);
  const back = ()=>popMenu(systemMenu());
  const clockMenu = {
    '': {
      'title': /*LANG*/'Select Clock',
    },
    '< Back': back,
  };
  clockApps.forEach((app, index) => {
    var label = app.name;
    if ((!settings.clock && index === 0) || (settings.clock === app.src)) {
      label = "* " + label;
    }
    clockMenu[label] = () => {
      settings.clock = app.src;
      settings.clockHasWidgets = storage.read(app.src).includes("Bangle.loadWidgets");
      updateSettings();
      back();
    };
  });
  if (clockApps.length === 0) {
    clockMenu[/*LANG*/"No Clocks Found"] = () => { };
  }
  return clockMenu;
}
function launcherMenu() {
  var launcherApps = storage.list(/\.info$/)
    .map(app => {var a=storage.readJSON(app, 1);return (a&&a.type == "launch")?a:undefined})
    .filter(app => app) // filter out any undefined apps
    .sort((a, b) => a.sortorder - b.sortorder);
  const back = ()=>popMenu(systemMenu());
  const launcherMenu = {
    '': {
      'title': /*LANG*/'Select Launcher',
    },
    '< Back': back,
  };
  launcherApps.forEach((app, index) => {
    var label = app.name;
    if ((!settings.launcher && index === 0) || (settings.launcher === app.src)) {
      label = "* " + label;
    }
    launcherMenu[label] = () => {
      settings.launcher = app.src;
      updateSettings();
      back();
    };
  });
  if (launcherApps.length === 0) {
    launcherMenu[/*LANG*/"No Launchers Found"] = () => { };
  }
  return launcherMenu;
}

function setTimeMenu() {
  let d = new Date();
  const timemenu = {
    '': { 'title': /*LANG*/'Date & Time' },
    '< Back': function () {
      setTime(d.getTime() / 1000);
      popMenu(systemMenu());
    },
    /*LANG*/'Day': {
      value: d.getDate(),
      onchange: function (v) {
        this.value = ((v+30)%31)+1;
        d.setDate(this.value);
      }
    },
    /*LANG*/'Month': {
      value: d.getMonth() + 1,
      format: v => require("date_utils").month(v),
      onchange: function (v) {
        this.value = ((v+11)%12)+1;
        d.setMonth(this.value - 1);
      }
    },
    /*LANG*/'Year': {
      value: d.getFullYear(),
      min: 2019,
      max: 2100,
      onchange: function (v) {
        d.setFullYear(v);
      }
    },
    /*LANG*/'Hour': {
      value: d.getHours(),
      onchange: function (v) {
        this.value = (v+24)%24;
        d.setHours(this.value);
      }
    },
    /*LANG*/'Minute': {
      value: d.getMinutes(),
      onchange: function (v) {
        this.value = (v+60)%60;
        d.setMinutes(this.value);
      }
    },
    /*LANG*/'Second': {
      value: d.getSeconds(),
      onchange: function (v) {
        this.value = (v+60)%60;
        d.setSeconds(this.value);
      }
    }
  };
  return timemenu;
}

function appSettingsMenu() {
  let appmenu = {
    '': { 'title': /*LANG*/'App Settings' },
    '< Back': ()=>popMenu(mainMenu()),
  }
  const apps = storage.list(/\.settings\.js$/)
    .map(s => s.substr(0, s.length-12))
    .map(id => {
      const a=storage.readJSON(id+'.info',1) || {name: id};
      return {id:id,name:a.name,sortorder:a.sortorder};
    })
    .sort((a, b) => {
      const n = (0|a.sortorder)-(0|b.sortorder);
      if (n) return n; // do sortorder first
      if (a.name<b.name) return -1;
      if (a.name>b.name) return 1;
      return 0;
    })
  if (apps.length === 0) {
    appmenu[/*LANG*/'No app has settings'] = () => { };
  }
  apps.forEach(function (app) {
    appmenu[app.name] = () => { showAppSettings(app) };
  })
  return appmenu;
}
function showAppSettings(app) {
  const back = () => popMenu(appSettingsMenu());
  const showError = msg => {
    E.showMessage(`${app.name}:\n${msg}!\n\nBTN1 to go back`);
    setWatch(back, BTN1, { repeat: false });
  }
  let appSettings = storage.read(app.id+'.settings.js');
  try {
    appSettings = eval(appSettings);
  } catch (e) {
    console.log(`${app.name} settings error:`, e);
    return showError(/*LANG*/'Error in settings');
  }
  if (typeof appSettings !== "function") {
    return showError(/*LANG*/'Invalid settings');
  }
  try {
    // pass appSettingsMenu as "back" argument
    pushMenu();
    appSettings(back);
  } catch (e) {
    console.log(`${app.name} settings error:`, e);
    return showError(/*LANG*/'Error in settings');
  }
}

function showTouchscreenCalibration() {
  Bangle.setUI();
  require('widget_utils').hide();
  // disable touchscreen calibration (passed coords right through)
  Bangle.setOptions({touchX1: 0, touchY1: 0, touchX2: g.getWidth(), touchY2: g.getHeight() });

  var P = 32;
  var corners = [
    [P,P],
    [g.getWidth()-P,P],
    [g.getWidth()-P,g.getHeight()-P],
    [P,g.getHeight()-P],
  ];
  var currentCorner = 0;
  var currentTry = 0;
  var pt = {
    x1 : 0, y1 : 0, x2 : 0, y2 : 0
  };

  function showTapSpot() {
    var spot = corners[currentCorner];
    g.clear(1);
    g.drawLine(spot[0]-32,spot[1],spot[0]+32,spot[1]);
    g.drawLine(spot[0],spot[1]-32,spot[0],spot[1]+32);
    g.drawCircle(spot[0],spot[1], 16);
    var tapsLeft = (2-currentTry)*4+(4-currentCorner);
    g.setFont("6x8:2").setFontAlign(0,0).drawString(tapsLeft+/*LANG*/" taps\nto go", g.getWidth()/2, g.getHeight()/2);
  }

  function calcCalibration() {
    g.clear(1);
    // we should now have 6 of each tap in 'pt'
    pt.x1 /= 6;
    pt.y1 /= 6;
    pt.x2 /= 6;
    pt.y2 /= 6;
    // work out final values
    var calib = {
      x1 : Math.round(pt.x1 - (pt.x2-pt.x1)*P/(g.getWidth()-P*2)),
      y1 : Math.round(pt.y1 - (pt.y2-pt.y1)*P/(g.getHeight()-P*2)),
      x2 : Math.round(pt.x2 + (pt.x2-pt.x1)*P/(g.getWidth()-P*2)),
      y2 : Math.round(pt.y2 + (pt.y2-pt.y1)*P/(g.getHeight()-P*2))
    };
    var dx = calib.x2-calib.x1;
    var dy = calib.y2-calib.y1;
    if(dx<100 || dx>280 || dy<100 || dy>280) {
      g.setFont("6x8:2").setFontAlign(0,0).drawString(/*LANG*/"Out of Range.\nPlease\ntry again", g.getWidth()/2, g.getHeight()/2);
    } else {
      Bangle.setOptions({
        touchX1: calib.x1, touchY1: calib.y1, touchX2: calib.x2, touchY2: calib.y2
      });
      var s = storage.readJSON("setting.json",1)||{};
      s.touch = calib;
      storage.writeJSON("setting.json",s);
      g.setFont("6x8:2").setFontAlign(0,0).drawString(/*LANG*/"Calibrated!", g.getWidth()/2, g.getHeight()/2);
    }
    // now load the menu again
    setTimeout(() => restoreMenu(LCDMenu()), 500);
  }

  function touchHandler(_,e) {
    E.stopEventPropagation&&E.stopEventPropagation();
    var spot = corners[currentCorner];
    // store averages
    if (spot[0]*2 < g.getWidth())
      pt.x1 += e.x;
    else
      pt.x2 += e.x;
    if (spot[1]*2 < g.getHeight())
      pt.y1 += e.y;
    else
      pt.y2 += e.y;
    // go to next corner
    currentCorner++;
    if (currentCorner>=corners.length) {
      currentCorner = 0;
      currentTry++;
      if (currentTry==3) {
        Bangle.removeListener('touch', touchHandler);
        return calcCalibration();
      }
    }
    showTapSpot();
  }
  Bangle.prependListener?Bangle.prependListener('touch',touchHandler):Bangle.on('touch',touchHandler);

  showTapSpot();
}

// Calibrate altitude - Bangle.js2 only
function showAltitude() {
  function onPressure(pressure) {
    menuPressure.value = Math.round(pressure.pressure).toString(); // toString stops tapping on the item bringing up an adjustment menu
    menuAltitude.value = Math.round(pressure.altitude).toString();
    m.draw();
  }
  function altitudeDone() {
    settings.seaLevelPressure = seaLevelPressure;
    updateSettings();
  }

  Bangle.setBarometerPower(1,"settings");
  Bangle.on("pressure",onPressure);
  E.on("kill", altitudeDone);
  var seaLevelPressure = Bangle.getOptions().seaLevelPressure;
  if (!isFinite(seaLevelPressure)) seaLevelPressure=1013.25;
  var menuPressure = {value:"-"};
  var menuAltitude = {value:"-"};
  var m = E.showMenu({ "" : {title:/*LANG*/"Altitude",back:() => {
      Bangle.setBarometerPower(0,"settings");
      Bangle.removeListener("pressure",onPressure);
      E.removeListener("kill",altitudeDone);
      altitudeDone();
      popMenu(systemMenu());
    }},
    /*LANG*/"Pressure (hPa)" : menuPressure,
    /*LANG*/"Altitude (m)" : menuAltitude,
    /*LANG*/"Adjust up" : function() {
      Bangle.buzz(80);
      seaLevelPressure++;
      Bangle.setOptions({seaLevelPressure});
    },
    /*LANG*/"Adjust down" : function() {
      Bangle.buzz(80);
      seaLevelPressure--;
      Bangle.setOptions({seaLevelPressure});
    },
    /*LANG*/"Set Default" : function() {
      Bangle.buzz();
      seaLevelPressure=1013.25;
      Bangle.setOptions({seaLevelPressure});
    }
  });
}

// Show the main menu
pushMenu(mainMenu());
