'use strict';

var _ = require('lodash');
var moment = require('moment');
var times = require('../times');
var levels = require('../levels');

// var ALL_STATUS_FIELDS = ['status-symbol', 'status-label', 'iob', 'freq', 'rssi']; Unused variable

function init (ctx) {
  var utils = require('../utils')(ctx);

  var loop = {
    name: 'loop'
    , label: 'Loop'
    , pluginType: 'pill-status'
  };

  var firstPrefs = true;

  loop.getPrefs = function getPrefs (sbx) {

    var prefs = {
      warn: sbx.extendedSettings.warn ? sbx.extendedSettings.warn : 30
      , urgent: sbx.extendedSettings.urgent ? sbx.extendedSettings.urgent : 60
      , enableAlerts: sbx.extendedSettings.enableAlerts
    };

    if (firstPrefs) {
      firstPrefs = false;
      console.info(' Prefs:', prefs);
    }

    return prefs;
  };

  loop.setProperties = function setProperties (sbx) {
    sbx.offerProperty('loop', function setLoop () {
      return loop.analyzeData(sbx);
    });
  };

  loop.analyzeData = function analyzeData (sbx) {
    var recentHours = 6;
    var recentMills = sbx.time - times.hours(recentHours).msecs;

    var recentData = _.chain(sbx.data.devicestatus)
      .filter(function(status) {
        return ('loop' in status) && sbx.entryMills(status) <= sbx.time && sbx.entryMills(status) >= recentMills;
      }).value();

    var prefs = loop.getPrefs(sbx);
    var recent = moment(sbx.time).subtract(prefs.warn / 2, 'minutes');

    function getDisplayForStatus (status) {

      var desc = {
        symbol: '⚠'
        , code: 'warning'
        , label: 'Warning'
      };

      if (!status) {
        return desc;
      }

      if (status.failureReason || (status.enacted && !status.enacted.received)) {
        desc.symbol = 'x';
        desc.code = 'error';
        desc.label = 'Error';
      } else if (status.enacted && moment(status.timestamp).isAfter(recent)) {
        desc.symbol = '⌁';
        desc.code = 'enacted';
        desc.label = 'Enacted';
      } else if (status.recommendedTempBasal && moment(status.recommendedTempBasal.timestamp).isAfter(recent)) {
        desc.symbol = '⏀';
        desc.code = 'recommendation';
        desc.label = 'Recomendation';
      } else if (status.moment && status.moment.isAfter(recent)) {
        desc.symbol = '↻';
        desc.code = 'looping';
        desc.label = 'Looping';
      }
      return desc;
    }

    var result = {
      lastLoop: null
      , lastEnacted: null
      , lastPredicted: null
      , lastOkMoment: null
    };

    function assignLastEnacted (loopStatus) {
      var enacted = loopStatus.enacted;
      if (enacted && enacted.timestamp) {
        enacted.moment = moment(enacted.timestamp);
        if (!result.lastEnacted || enacted.moment.isAfter(result.lastEnacted.moment)) {
          result.lastEnacted = enacted;
        }
      }
    }

    function assignLastPredicted (loopStatus) {
      if (loopStatus.predicted && loopStatus.predicted.startDate) {
        result.lastPredicted = loopStatus.predicted;
      }
    }

    function assignLastLoop (loopStatus) {
      if (!result.lastLoop || loopStatus.moment.isAfter(result.lastLoop.moment)) {
        result.lastLoop = loopStatus;
      }
    }

    function assignLastOverride (status) {
      var override = status.override;
      if (override && override.timestamp) {
        override.moment = moment(override.timestamp);
        if (!result.lastOverride || override.moment.isAfter(result.lastOverride.moment)) {
          result.lastOverride = override;
        }
      }
    }

    function assignLastOkMoment (loopStatus) {
      if (!loopStatus.failureReason && (!result.lastOkMoment || loopStatus.moment.isAfter(result.lastOkMoment))) {
        result.lastOkMoment = loopStatus.moment;
      }
    }

    _.forEach(recentData, function eachStatus (status) {
      if (status && status.loop && status.loop.timestamp) {
        var loopStatus = status.loop;
        loopStatus.moment = moment(loopStatus.timestamp);
        assignLastEnacted(loopStatus);
        assignLastLoop(loopStatus);
        assignLastPredicted(loopStatus);
        assignLastOverride(status);
        assignLastOkMoment(loopStatus);
      }
    });

    result.display = getDisplayForStatus(result.lastLoop);

    return result;
  };

  loop.checkNotifications = function checkNotifications (sbx) {
    var prefs = loop.getPrefs(sbx);

    if (!prefs.enableAlerts) { return; }

    var prop = sbx.properties.loop;

    if (!prop.lastLoop) {
      console.info('Loop hasn\'t reported a loop yet');
      return;
    }

    var now = moment();
    var level = statusLevel(prop, prefs, sbx);
    if (level >= levels.WARN) {
      sbx.notifications.requestNotify({
        level: level
        , title: 'Loop isn\'t looping'
        , message: 'Last Loop: ' + utils.formatAgo(prop.lastOkMoment, now.valueOf())
        , pushoverSound: 'echo'
        , group: 'Loop'
        , plugin: loop
        , debug: prop
      });
    }
  };

  loop.updateVisualisation = function updateVisualisation (sbx) {
    var prop = sbx.properties.loop;

    var prefs = loop.getPrefs(sbx);

    function valueString (prefix, value) {
      return (value != null) ? prefix + value : '';
    }

    var events = [];

    function addRecommendedTempBasal () {
      if (prop.lastLoop && prop.lastLoop.recommendedTempBasal) {

        var recommendedTempBasal = prop.lastLoop.recommendedTempBasal;

        var valueParts = [
          'Suggested Temp: ' + recommendedTempBasal.rate + 'U/hour for ' +
          recommendedTempBasal.duration + 'm'
        ];

        valueParts = concatIOB(valueParts);
        valueParts = concatCOB(valueParts);
        valueParts = concatEventualBG(valueParts);
        valueParts = concatRecommendedBolus(valueParts);

        events.push({
          time: moment(recommendedTempBasal.timestamp)
          , value: valueParts.join('')
        });
      }
    }

    function addRSSI () {

      var mostRecent = "";
      var pumpRSSI = "";
      var bleRSSI = "";
      var reportRSSI = "";

      _.forEach(sbx.data.devicestatus, function(entry) {

        if (entry.radioAdapter) {
          var entryMoment = moment(entry.created_at);

          if (mostRecent == "") {
            mostRecent = entryMoment;
            if (entry.radioAdapter.pumpRSSI) {
              pumpRSSI = entry.radioAdapter.pumpRSSI;
            }
            if (entry.radioAdapter.RSSI) {
              bleRSSI = entry.radioAdapter.RSSI;
            }
          }

          if (mostRecent < entryMoment) {
            mostRecent = entryMoment;
            if (entry.radioAdapter.pumpRSSI) {
              pumpRSSI = entry.radioAdapter.pumpRSSI;
            }
            if (entry.radioAdapter.RSSI) {
              bleRSSI = entry.radioAdapter.RSSI;
            }
          }
        }
      });

      if (bleRSSI != "") {
        reportRSSI = "BLE RSSI: " + bleRSSI + " ";
      }

      if (pumpRSSI != "") {
        reportRSSI = reportRSSI + "Pump RSSI: " + pumpRSSI;
      }

      if (reportRSSI != "") {
        events.push({
          time: mostRecent
          , value: reportRSSI
        });
      }

    }

    function addLastEnacted () {
      if (prop.lastEnacted) {
        var canceled = prop.lastEnacted.rate === 0 && prop.lastEnacted.duration === 0;

        var valueParts = [
          '<b>Temp Basal' + (canceled ? ' Canceled' : ' Started') + '</b>'
          , canceled ? '' : ' ' + prop.lastEnacted.rate.toFixed(2) + 'U/hour for ' + prop.lastEnacted.duration + 'm'
          , valueString(', ', prop.lastEnacted.reason)
        ];

        valueParts = concatIOB(valueParts);
        valueParts = concatCOB(valueParts);
        valueParts = concatEventualBG(valueParts);
        valueParts = concatRecommendedBolus(valueParts);

        events.push({
          time: prop.lastEnacted.moment
          , value: valueParts.join('')
        });
      }
    }

    function concatIOB (valueParts) {
      if (prop.lastLoop && prop.lastLoop.iob) {
        var iob = prop.lastLoop.iob;
        valueParts = valueParts.concat([
          ', IOB: '
          
          , sbx.roundInsulinForDisplayFormat(iob.iob) + 'U'
          
          , iob.basaliob ? ', Basal IOB ' + sbx.roundInsulinForDisplayFormat(iob.basaliob) + 'U' : ''
        ]);
      }

      return valueParts;
    }

    function concatCOB (valueParts) {
      if (prop.lastLoop && prop.lastLoop.cob) {
        var cob = prop.lastLoop.cob.cob;
        cob = Math.round(cob);
        valueParts = valueParts.concat([
          ', COB: '
          , cob + 'g'
        ]);
      }

      return valueParts;
    }

    function concatEventualBG (valueParts) {
      if (prop.lastLoop && prop.lastLoop.predicted) {
        var predictedBGvalues = prop.lastLoop.predicted.values;
        var eventualBG = predictedBGvalues[predictedBGvalues.length - 1];
        var maxBG = Math.max.apply(null, predictedBGvalues);
        var minBG = Math.min.apply(null, predictedBGvalues);
        var eventualBGscaled = sbx.settings.units === 'mmol' ?
          sbx.roundBGToDisplayFormat(sbx.scaleMgdl(eventualBG)) : eventualBG;
        var maxBGscaled = sbx.settings.units === 'mmol' ?
          sbx.roundBGToDisplayFormat(sbx.scaleMgdl(maxBG)) : maxBG;
        var minBGscaled = sbx.settings.units === 'mmol' ?
          sbx.roundBGToDisplayFormat(sbx.scaleMgdl(minBG)) : minBG;

        valueParts = valueParts.concat([
          ', Predicted Min-Max BG: '
          , minBGscaled
          , '-'
          , maxBGscaled
          , ', Eventual BG: '
          , eventualBGscaled
        ]);
      }

      return valueParts;
    }

    function concatRecommendedBolus (valueParts) {
      if (prop.lastLoop && prop.lastLoop.recommendedBolus) {
        var recommendedBolus = prop.lastLoop.recommendedBolus;
        valueParts = valueParts.concat([
          ', Recommended Bolus: '
          , recommendedBolus + 'U'
        ]);
      }

      return valueParts;
    }

    function getForecastPoints () {
      var points = [];

      function toPoints (startTime, offset) {
        return function toPoint (value, index) {
          return {
            mgdl: value
            , color: '#ff00ff'
            , mills: startTime.valueOf() + times.mins(5 * index).msecs + offset
            , noFade: true
          };
        };
      }

      if (prop.lastPredicted) {
        var predicted = prop.lastPredicted;
        var startTime = moment(predicted.startDate);
        if (predicted.values) {
          points = points.concat(_.map(predicted.values, toPoints(startTime, 0)));
        }
      }

      return points;
    }

    if ('error' === prop.display.code) {
      events.push({
        time: prop.lastLoop.moment
        , value: valueString('Error: ', prop.lastLoop.failureReason)
      });
      addRecommendedTempBasal();
    } else if ('enacted' === prop.display.code) {
      addLastEnacted();
    } else if ('looping' === prop.display.code) {
      addLastEnacted();
    } else {
      addRecommendedTempBasal();
    }

    addRSSI();

    var sorted = _.sortBy(events, function toMill (event) {
      return event.time.valueOf();
    }).reverse();

    var info = _.map(sorted, function eventToInfo (event) {
      return {
        label: utils.timeAt(false, sbx) + utils.timeFormat(event.time, sbx)
        , value: event.value
      };
    });

    var loopName = 'Loop';

    if (prop.lastLoop && prop.lastLoop.name) {
      loopName = prop.lastLoop.name;
    }

    var eventualBGValue = '';
    if (prop.lastLoop && prop.lastLoop.predicted) {
      var predictedBGvalues = prop.lastLoop.predicted.values;
      var eventualBG = predictedBGvalues[predictedBGvalues.length - 1];
      if (sbx.settings.units === 'mmol') {
        eventualBG = sbx.roundBGToDisplayFormat(sbx.scaleMgdl(eventualBG));
      }
      eventualBGValue = ' ↝ ' + eventualBG;
    }

    var label = loopName + ' ' + prop.display.symbol;

    var lastLoopValue = prop.lastLoop ?
      utils.timeFormat(prop.lastLoop.moment, sbx) + eventualBGValue : null;

    sbx.pluginBase.updatePillText(loop, {
      value: lastLoopValue
      , label: label
      , info: info
      , pillClass: statusClass(prop, prefs, sbx)
    });

    var forecastPoints = getForecastPoints();
    if (forecastPoints && forecastPoints.length > 0) {
      sbx.pluginBase.addForecastPoints(forecastPoints, { type: 'loop', label: 'Loop Forecasts' });
    }
  };

  function virtAsstForecastHandler (next, slots, sbx) {
    if (sbx.properties.loop.lastLoop.predicted) {
      var forecast = sbx.properties.loop.lastLoop.predicted.values;
      var max = forecast[0];
      var min = forecast[0];
      var maxForecastIndex = Math.min(6, forecast.length);

      var startPrediction = moment(sbx.properties.loop.lastLoop.predicted.startDate);
      var endPrediction = startPrediction.clone().add(maxForecastIndex * 5, 'minutes');
      if (endPrediction.valueOf() < sbx.time) {
        next('Loop Forecast', 'Unable to forecast with the data that is available');
      } else {
        for (var i = 1, len = forecast.slice(0, maxForecastIndex).length; i < len; i++) {
          if (forecast[i] > max) {
            max = forecast[i];
          }
          if (forecast[i] < min) {
            min = forecast[i];
          }
        }
        var value = '';
        if (min === max) {
          value = 'around ' + max;
        } else {
          value = 'between ' + min + ' and ' + max;
        }
        var response = 'According to the loop forecast you are expected to be ' + value + ' over the next ' + moment(endPrediction).from(moment(sbx.time));
        next('Loop Forecast', response);
      }
    } else {
      next('Loop forecast', 'Loop plugin does not seem to be enabled');
    }
  }

  function virtAsstLastLoopHandler (next, slots, sbx) {
    console.log(JSON.stringify(sbx.properties.loop.lastLoop));
    var response = 'The last successful loop was ' + moment(sbx.properties.loop.lastOkMoment).from(moment(sbx.time));
    next('Last loop', response);
  }

  loop.virtAsst = {
    intentHandlers: [{
      intent: 'MetricNow'
      , metrics: ['loop forecast', 'forecast']
      , intentHandler: virtAsstForecastHandler
    }, {
      intent: 'LastLoop'
      , intentHandler: virtAsstLastLoopHandler
    }]
  };

  function statusClass (prop, prefs, sbx) {
    var level = statusLevel(prop, prefs, sbx);
    var cls = 'current';

    if (level === levels.WARN) {
      cls = 'warn';
    } else if (level === levels.URGENT) {
      cls = 'urgent';
    }

    return cls;
  }

  function statusLevel (prop, prefs, sbx) {
    var level = levels.NONE;
    var now = moment(sbx.time);

    if (prop.lastOkMoment) {
      var urgentTime = prop.lastOkMoment.clone().add(prefs.urgent, 'minutes');
      var warningTime = prop.lastOkMoment.clone().add(prefs.warn, 'minutes');

      if (urgentTime.isBefore(now)) {
        level = levels.URGENT;
      } else if (warningTime.isBefore(now)) {
        level = levels.WARN;
      }
    }

    return level;
  }

  return loop;

}

module.exports = init;
