(function(back) {
  function writeSettings(key, value) {
    let s = require('Storage').readJSON(FILE, true) || {};
    s[key] = value;
    require('Storage').writeJSON(FILE, s);
    readSettings();
  }

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

  let FILE="bthrm.json";
  let settings;
  readSettings();

  let log = ()=>{};
  if (settings.debuglog)
    log = print;

  function applyCustomSettings(){
    writeSettings("enabled",true);
    writeSettings("replace",settings.custom_replace);
    writeSettings("startWithHrm",settings.custom_startWithHrm);
    writeSettings("allowFallback",settings.custom_allowFallback);
    writeSettings("fallbackTimeout",settings.custom_fallbackTimeout);
  }

  function buildMainMenu(){
    let mainmenu = {
      '': { 'title': 'Bluetooth HRM' },
      '< Back': back,
      'Mode': {
        value: 0 | settings.mode,
        min: 0,
        max: 3,
        format: v => ["Off", "Default", "Both", "Custom"][v],
        onchange: v => {
          settings.mode = v;
          switch (v){
            case 0:
              writeSettings("enabled",false);
              break;
            case 1:
              writeSettings("enabled",true);
              writeSettings("replace",true);
              writeSettings("startWithHrm",true);
              writeSettings("allowFallback",true);
              writeSettings("fallbackTimeout",10);
              break;
            case 2:
              writeSettings("enabled",true);
              writeSettings("replace",false);
              writeSettings("startWithHrm",false);
              writeSettings("allowFallback",false);
              break;
            case 3:
              applyCustomSettings();
              break;
          }
          writeSettings("mode",v);
        }
      }
    };

    if (settings.btname || settings.btid){
      let name = "Clear " + (settings.btname || settings.btid);
      mainmenu[name] = function() {
      E.showPrompt("Clear current device?").then((r)=>{
          if (r) {
            writeSettings("btname",undefined);
            writeSettings("btid",undefined);
            writeSettings("cache", undefined);
          }
          E.showMenu(buildMainMenu());
        });
      };
    }

    mainmenu["BLE Scan"] = ()=> createMenuFromScan();
    mainmenu["Custom Mode"] = function() { E.showMenu(submenu_custom); };
    mainmenu.Debug = function() { E.showMenu(submenu_debug); };
    return mainmenu;
  }

  let submenu_debug = {
    '' : { title: "Debug"},
    '< Back': function() { E.showMenu(buildMainMenu()); },
    'Alert on disconnect': {
      value: !!settings.warnDisconnect,
      onchange: v => {
        writeSettings("warnDisconnect",v);
      }
    },
    'Debug log': {
      value: !!settings.debuglog,
      onchange: v => {
        writeSettings("debuglog",v);
      }
    },
    'Use bonding': {
      value: !!settings.bonding,
      onchange: v => {
        writeSettings("bonding",v);
      }
    },
    'Use active scanning': {
      value: !!settings.active,
      onchange: v => {
        writeSettings("active",v);
      }
    },
    'Grace periods': function() { E.showMenu(submenu_grace); }
  };

  let supportedServices = [
    "0x180d", // Heart Rate
    "0x180f", // Battery
  ];

  let supportedCharacteristics = [
    "0x2a37", // Heart Rate
    "0x2a38", // Body sensor location
    "0x2a19", // Battery
  ];

  var characteristicsToCache = function(characteristics) {
    log("Cache characteristics");
    let cache = {};
    if (!cache.characteristics) cache.characteristics = {};
    for (var c of characteristics){
      //"handle_value":16,"handle_decl":15
      log("Saving handle " + c.handle_value + " for characteristic: ", c.uuid);
      cache.characteristics[c.uuid] = {
        "handle": c.handle_value,
        "uuid": c.uuid,
        "notify": c.properties.notify,
        "read": c.properties.read
      };
    }
    writeSettings("cache", cache);
  };

  let createCharacteristicPromise = function(newCharacteristic) {
    log("Create characteristic promise", newCharacteristic.uuid);
    return Promise.resolve().then(()=>log("Handled characteristic", newCharacteristic.uuid));
  };

  let attachCharacteristicPromise = function(promise, characteristic) {
    return promise.then(()=>{
      log("Handling characteristic:", characteristic.uuid);
      return createCharacteristicPromise(characteristic);
    });
  };

  let characteristics;

  let createCharacteristicsPromise = function(newCharacteristics) {
    log("Create characteristics promise ", newCharacteristics.length);
    let result = Promise.resolve();
    for (let c of newCharacteristics){
      if (!supportedCharacteristics.includes(c.uuid)) continue;
      log("Supporting characteristic", c.uuid);
      characteristics.push(c);

      result = attachCharacteristicPromise(result, c);
    }
    return result.then(()=>log("Handled characteristics"));
  };

  let createServicePromise = function(service) {
    log("Create service promise", service.uuid);
    let result = Promise.resolve();
    result = result.then(()=>{
      log("Handling service", service.uuid);
      return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c));
    });
    return result.then(()=>log("Handled service", service.uuid));
  };

  let attachServicePromise = function(promise, service) {
    return promise.then(()=>createServicePromise(service));
  };

  function cacheDevice(deviceId){
    let promise;
    let filters;
    let gatt;
    characteristics = [];
    filters = [{ id: deviceId }];

    log("Requesting device with filters", filters);
    promise = NRF.requestDevice({ filters: filters, active: settings.active });

    promise = promise.then((d)=>{
      log("Got device", d);
      gatt = d.gatt;
      log("Connecting...");
      return gatt.connect().then(function() {
        log("Connected.");
      });
    });

    if (settings.bonding){
      promise = promise.then(() => {
        log(JSON.stringify(gatt.getSecurityStatus()));
        if (gatt.getSecurityStatus().bonded) {
          log("Already bonded");
          return Promise.resolve();
        } else {
          log("Start bonding");
          return gatt.startBonding()
            .then(() => log("Security status after bonding" + gatt.getSecurityStatus()));
        }
      });
    }

    promise = promise.then(()=>{
      log("Getting services");
      return gatt.getPrimaryServices();
    });

    promise = promise.then((services)=>{
      log("Got services", services.length);
      let result = Promise.resolve();
      for (let service of services){
        if (!(supportedServices.includes(service.uuid))) continue;
        log("Supporting service", service.uuid);
        result = attachServicePromise(result, service);
      }
      return result;
    });

    return promise.then(()=>{
      log("Connection established, saving cache");
      characteristicsToCache(characteristics);
    });
  }

  function createMenuFromScan(){
    E.showMenu();
    E.showMessage("Scanning for 4 seconds");

    let submenu_scan = {
      '< Back': function() { E.showMenu(buildMainMenu()); }
    };
    NRF.findDevices(function(devices) {
      submenu_scan[''] = { title: `Scan (${devices.length} found)`};
      if (devices.length === 0) {
        E.showAlert("No devices found")
          .then(() => E.showMenu(buildMainMenu()));
        return;
      } else {
        devices.forEach((d) => {
          log("Found device", d);
          let shown = (d.name || d.id.substr(0, 17));
          submenu_scan[shown] = function () {
            E.showPrompt("Connect to\n" + shown + "?", {title: "Pairing"}).then((r) => {
              if (r) {
                E.showMessage("Connecting...", {img:require("Storage").read("bthrm.img")});
                let count = 0;
                const successHandler = ()=>{
                  E.showPrompt("Success!", {
                    img:require("Storage").read("bthrm.img"),
                    buttons: { "OK":true }
                  }).then(()=>{
                  writeSettings("btid", d.id);
                  // Store the name for displaying later. Will connect by ID
                  if (d.name) {
                    writeSettings("btname", d.name);
                  }
                    E.showMenu(buildMainMenu());
                  });
                };
                const errorHandler = (e)=>{
                  count++;
                  log("ERROR", e);
                  if (count <= 10){
                    E.showMessage("Error during caching\nRetry " + count + "/10", e);
                    return cacheDevice(d.id).then(successHandler).catch(errorHandler);
                  } else {
                    E.showAlert("Error during caching", e).then(()=>{
                      E.showMenu(buildMainMenu());
                    });
                  }
                };

                return cacheDevice(d.id).then(successHandler).catch(errorHandler);
              }
            });
          };
        });
      }
      E.showMenu(submenu_scan);
    }, { timeout: 4000, active: true, filters: [{services: [ "180d" ]}]});
  }

  let submenu_custom = {
    '' : { title: "Custom mode"},
    '< Back': function() { E.showMenu(buildMainMenu()); },
    'Replace HRM': {
      value: !!settings.custom_replace,
      onchange: v => {
        writeSettings("custom_replace",v);
        if (settings.mode == 3) applyCustomSettings();
      }
    },
    'Start w. HRM': {
      value: !!settings.custom_startWithHrm,
      onchange: v => {
        writeSettings("custom_startWithHrm",v);
        if (settings.mode == 3) applyCustomSettings();
      }
    },
    'HRM Fallback': {
      value: !!settings.custom_allowFallback,
      onchange: v => {
        writeSettings("custom_allowFallback",v);
        if (settings.mode == 3) applyCustomSettings();
      }
    },
    'Fallback Timeout': {
      value: settings.custom_fallbackTimeout,
      min: 5,
      max: 60,
      step: 5,
      format: v=>v+"s",
      onchange: v => {
        writeSettings("custom_fallbackTimout",v*1000);
        if (settings.mode == 3) applyCustomSettings();
      }
    },
  };

  let submenu_grace = {
    '' : { title: "Grace periods"},
    '< Back': function() { E.showMenu(submenu_debug); },
    'Request': {
      value: settings.gracePeriodRequest,
      min: 0,
      max: 3000,
      step: 100,
      format: v=>v+"ms",
      onchange: v => {
        writeSettings("gracePeriodRequest",v);
      }
    },
    'Connect': {
      value: settings.gracePeriodConnect,
      min: 0,
      max: 3000,
      step: 100,
      format: v=>v+"ms",
      onchange: v => {
        writeSettings("gracePeriodConnect",v);
      }
    },
    'Notification': {
      value: settings.gracePeriodNotification,
      min: 0,
      max: 3000,
      step: 100,
      format: v=>v+"ms",
      onchange: v => {
        writeSettings("gracePeriodNotification",v);
      }
    }
  };

  E.showMenu(buildMainMenu());
})
