const Layout = require("Layout");
const heatshrink = require('heatshrink');
const storage = require('Storage');

let tStart;
let historyY = [];
let historyZ = [];
let historyAvgY = [];
let historyAvgZ = [];
let historySlopeY = [];
let historySlopeZ = [];

let lastZeroPassCameFromPositive;
let lastZeroPassTime = 0;

let lastExerciseCompletionTime = 0;
let lastExerciseHalfCompletionTime = 0;

let exerciseType = {
  "id": "",
  "name": ""
};

// add new exercises here:
const exerciseTypes = [{
    "id": "pushup",
    "name": "push ups",
    "useYaxis": true,
    "useZaxis": false,
    "threshold": 2500,
    "thresholdMinTime": 800, // mininmal time between two push ups in ms
    "thresholdMaxTime": 5000, // maximal time between two push ups in ms
    "thresholdMinDurationTime": 600, // mininmal duration of half a push up in ms
  },
  {
    "id": "curl",
    "name": "curls",
    "useYaxis": true,
    "useZaxis": false,
    "threshold": 2500,
    "thresholdMinTime": 800, // mininmal time between two curls in ms
    "thresholdMaxTime": 5000, // maximal time between two curls in ms
    "thresholdMinDurationTime": 500, // mininmal duration of half a curl in ms
  },
  {
    "id": "situp",
    "name": "sit ups",
    "useYaxis": false,
    "useZaxis": true,
    "threshold": 3500,
    "thresholdMinTime": 800, // mininmal time between two sit ups in ms
    "thresholdMaxTime": 5000, // maximal time between two sit ups in ms
    "thresholdMinDurationTime": 500, // mininmal duration of half a sit up in ms
  }
];
let exerciseCounter = 0;

let layout;
let recordActive = false;

// Size of average window for data analysis
const avgSize = 6;

let hrtValue;

let settings = storage.readJSON("banglexercise.json", 1) || {
  'buzz': true
};

function showMainMenu() {
  let menu;
  menu = {
    "": {
      title: "BanglExercise",
      back: load
    }
  };

  exerciseTypes.forEach(function(et) {
    menu[et.name] = function() {
      exerciseType = et;
      E.showMenu();
      startTraining();
    };
  });

  if (exerciseCounter > 0) {
    menu["--------"] = {
      value: ""
    };
    menu["Last:"] = {
      value: exerciseCounter + " " + exerciseType.name
    };
  }
  menu.exit = function() {
    load();
  };

  E.showMenu(menu);
}

function accelHandler(accel) {
  if (!exerciseType) return;
  const t = Math.round(new Date().getTime()); // time in ms
  const y = exerciseType.useYaxis ? accel.y * 8192 : 0;
  const z = exerciseType.useZaxis ? accel.z * 8192 : 0;
  //console.log(t, y, z);

  if (exerciseType.useYaxis) {
    while (historyY.length > avgSize)
      historyY.shift();

    historyY.push(y);

    if (historyY.length > avgSize / 2) {
      const avgY = E.sum(historyY) / historyY.length;
      historyAvgY.push([t, avgY]);
      while (historyAvgY.length > avgSize)
        historyAvgY.shift();
    }
  }

  if (exerciseType.useZaxis) {
    while (historyZ.length > avgSize)
      historyZ.shift();

    historyZ.push(z);

    if (historyZ.length > avgSize / 2) {
      const avgZ = E.sum(historyZ) / historyZ.length;
      historyAvgZ.push([t, avgZ]);
      while (historyAvgZ.length > avgSize)
        historyAvgZ.shift();
    }
  }

  // slope for Y
  if (exerciseType.useYaxis) {
    let l = historyAvgY.length;
    if (l > 1) {
      const p1 = historyAvgY[l - 2];
      const p2 = historyAvgY[l - 1];
      const slopeY = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000);
      // we use this data for exercises which can be detected by using Y axis data
      isValidExercise(slopeY, t);
    }
  }

  // slope for Z
  if (exerciseType.useZaxis) {
    l = historyAvgZ.length;
    if (l > 1) {
      const p1 = historyAvgZ[l - 2];
      const p2 = historyAvgZ[l - 1];
      const slopeZ = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000);
      // we use this data for some exercises which can be detected by using Z axis data
      isValidExercise(slopeZ, t);
    }
  }
}

/*
 * Check if slope value of Y-axis or Z-axis data (depending on exercise type) looks like an exercise
 *
 * In detail we look for slop values which are bigger than the configured threshold for the current exercise type
 * Then we look for two consecutive slope values of which one is above 0 and the other is below zero.
 * If we find one pair of these values this could be part of one exercise.
 * Then we look for a pair of values which cross the zero from the otherwise direction
 */
function isValidExercise(slope, t) {
  if (!exerciseType) return;

  const threshold = exerciseType.threshold;
  const historySlopeValues = exerciseType.useYaxis ? historySlopeY : historySlopeZ;
  const thresholdMinTime = exerciseType.thresholdMinTime;
  const thresholdMaxTime = exerciseType.thresholdMaxTime;
  const thresholdMinDurationTime = exerciseType.thresholdMinDurationTime;
  const exerciseName = exerciseType.name;


  if (Math.abs(slope) >= threshold) {
    historySlopeValues.push([t, slope]);
    //console.log(t, Math.abs(slope));

    const lSlopeHistory = historySlopeValues.length;
    if (lSlopeHistory > 1) {
      const p1 = historySlopeValues[lSlopeHistory - 1][1];
      const p2 = historySlopeValues[lSlopeHistory - 2][1];
      if (p1 > 0 && p2 < 0) {
        if (lastZeroPassCameFromPositive == false) {
          lastExerciseHalfCompletionTime = t;
          console.log(t, exerciseName + " half complete...");

          layout.progress.label = "½";
          layout.recording.label = "TRAINING";
          g.clear();
          layout.render();
        }

        lastZeroPassCameFromPositive = true;
        lastZeroPassTime = t;
      }
      if (p2 > 0 && p1 < 0) {
        if (lastZeroPassCameFromPositive == true) {
          const tDiffLastExercise = t - lastExerciseCompletionTime;
          const tDiffStart = t - tStart;
          console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastExercise), Math.round(tDiffStart));

          // check minimal time between exercises:
          if ((lastExerciseCompletionTime <= 0 && tDiffStart >= thresholdMinTime) || tDiffLastExercise >= thresholdMinTime) {

            // check maximal time between exercises:
            if (lastExerciseCompletionTime <= 0 || tDiffLastExercise <= thresholdMaxTime) {

              // check minimal duration of exercise:
              const tDiffExerciseHalfCompletion = t - lastExerciseHalfCompletionTime;
              if (tDiffExerciseHalfCompletion > thresholdMinDurationTime) {
                //console.log(t, exerciseName + " complete!!!");

                lastExerciseCompletionTime = t;
                exerciseCounter++;

                layout.count.label = exerciseCounter;
                layout.progress.label = "";
                layout.recording.label = "Good!";

                g.clear();
                layout.render();

                if (settings.buzz)
                  Bangle.buzz(200, 0.5);
              } else {
                console.log(t, exerciseName + " too quick for duration time threshold!"); // thresholdMinDurationTime
                lastExerciseCompletionTime = t;

                layout.recording.label = "Go slower!";
                g.clear();
                layout.render();
              }
            } else {
              console.log(t, exerciseName + " top slow for time threshold!"); // thresholdMaxTime
              lastExerciseCompletionTime = t;

              layout.recording.label = "Go faster!";
              g.clear();
              layout.render();
            }
          } else {
            console.log(t, exerciseName + " too quick for time threshold!"); // thresholdMinTime
            lastExerciseCompletionTime = t;

            layout.recording.label = "Go slower!";
            g.clear();
            layout.render();
          }
        }

        lastZeroPassCameFromPositive = false;
        lastZeroPassTime = t;
      }
    }
  }
}


function reset() {
  historyY = [];
  historyZ = [];
  historyAvgY = [];
  historyAvgZ = [];
  historySlopeY = [];
  historySlopeZ = [];

  lastZeroPassCameFromPositive = undefined;
  lastZeroPassTime = 0;
  lastExerciseHalfCompletionTime = 0;
  lastExerciseCompletionTime = 0;
  exerciseCounter = 0;
  tStart = 0;
}


function startTraining() {
  if (recordActive) return;
  g.clear(1);
  reset();
  Bangle.setLCDTimeout(0); // force LCD on
  Bangle.setHRMPower(1, "banglexercise");
  if (!hrtValue) hrtValue = "...";

  layout = new Layout({
    type: "v",
    c: [{
        type: "txt",
        id: "type",
        font: "6x8:2",
        label: exerciseType.name,
        pad: 5
      },
      {
        type: "h",
        c: [{
            type: "txt",
            id: "count",
            font: exerciseCounter < 100 ? "6x8:9" : "6x8:8",
            label: exerciseCounter,
            pad: 5
          },
          {
            type: "txt",
            id: "progress",
            font: "6x8:2",
            label: "",
            pad: 5
          },
        ]
      },
      {
        type: "h",
        c: [{
            type: "img",
            pad: 4,
            src: function() {
              return heatshrink.decompress(atob("h0OwYOLkmQhMkgACByVJgESpIFBpEEBAIFBCgIFCCgsABwcAgQOCAAMSpAwDyBNM"));
            }
          },
          {
            type: "txt",
            id: "hrtRate",
            font: "6x8:2",
            label: hrtValue,
            pad: 5
          },
        ]
      },
      {
        type: "txt",
        id: "recording",
        font: "6x8:2",
        label: "TRAINING",
        bgCol: "#f00",
        pad: 5,
        fillx: 1
      },
    ]
  }, {
    btns: [{
      label: "STOP",
      cb: () => {
        stopTraining();
      }
    }],
    lazy: false
  });
  layout.render();

  Bangle.setPollInterval(80); // 12.5 Hz

  tStart = new Date().getTime();
  recordActive = true;
  if (settings.buzz)
    Bangle.buzz(200, 1);

  // delay start a little bit
  setTimeout(() => {
    Bangle.on('accel', accelHandler);
  }, 1000);
}

function stopTraining() {
  if (!recordActive) return;

  g.clear(1);
  Bangle.removeListener('accel', accelHandler);
  Bangle.setHRMPower(0, "banglexercise");
  showMainMenu();
  recordActive = false;
}

Bangle.on('HRM', function(hrm) {
  hrtValue = hrm.bpm;
});

g.clear(1);
Bangle.loadWidgets();
showMainMenu();
