#include <stdint.h>
#include <stdbool.h>
#include "libc.h"
#include "stm32f4xx_hal.h"
#include "defines.h"
#include "config.h"
#include "setup.h"
#include "util.h"
#include "bldc/BLDC_controller.h"      /* BLDC's header file */
#include "bldc/rtwtypes.h"
#include "version.h"
#include "obj/gitversion.h"
#include "comms.h"
#include "drivers/clock.h"
#include "early_init.h"
#include "drivers/i2c_soft.h"
#include "drivers/angle_sensor.h"
#include "boards.h"


//------------------------------------------------------------------------
// Global variables set externally
//------------------------------------------------------------------------
extern TIM_HandleTypeDef htim_left;
extern TIM_HandleTypeDef htim_right;
extern ADC_HandleTypeDef hadc;
extern volatile adc_buf_t adc_buffer;

// Matlab defines - from auto-code generation
//---------------
extern P    rtP_Left;                   /* Block parameters (auto storage) */
extern P    rtP_Right;                  /* Block parameters (auto storage) */
extern ExtY rtY_Left;                   /* External outputs */
extern ExtY rtY_Right;                  /* External outputs */
extern ExtU rtU_Left;                   /* External inputs */
extern ExtU rtU_Right;                  /* External inputs */
//---------------

// TODO: remove both, unneeded. Also func in util.c too
extern int16_t speedAvg;                // Average measured speed
extern int16_t speedAvgAbs;             // Average measured speed in absolute

extern volatile int pwml;               // global variable for pwm left. -1000 to 1000
extern volatile int pwmr;               // global variable for pwm right. -1000 to 1000

extern uint8_t enable_motors;                  // global variable for motor enable

extern int16_t batVoltage;              // global variable for battery voltage

extern int32_t motPosL;
extern int32_t motPosR;

extern board_t board;

//------------------------------------------------------------------------
// Global variables set here in main.c
//------------------------------------------------------------------------
extern volatile uint32_t buzzerTimer;
volatile uint32_t torque_cmd_timeout = 0U;
volatile uint32_t ignition_off_counter = 0U;

uint16_t cnt_press = 0;

int16_t batVoltageCalib;         // global variable for calibrated battery voltage
int16_t board_temp_deg_c;        // global variable for calibrated temperature in degrees Celsius
volatile int16_t cmdL;                    // global variable for Left Command
volatile int16_t cmdR;                    // global variable for Right Command

uint8_t hw_type;                 // type of the board detected(0 - base, 3 - knee)
uint8_t ignition = 0;            // global variable for ignition on SBU2 line
uint8_t charger_connected = 0;   // status of the charger port
uint8_t pkt_idx = 0;             // For CAN msg counter
//------------------------------------------------------------------------
// Local variables
//------------------------------------------------------------------------
static uint32_t tick_prev = 0U;

static uint32_t main_loop_1Hz = 0U;
static uint32_t main_loop_1Hz_runtime = 0U;

static uint32_t main_loop_10Hz = 0U;
static uint32_t main_loop_10Hz_runtime = 0U;

static uint32_t main_loop_100Hz = 0U;
static uint32_t main_loop_100Hz_runtime = 0U;

void __initialize_hardware_early(void) {
  early_initialization();
}

int main(void) {
  HAL_Init();
  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
  HAL_NVIC_SetPriority(MemoryManagement_IRQn, 0, 0);
  HAL_NVIC_SetPriority(BusFault_IRQn, 0, 0);
  HAL_NVIC_SetPriority(UsageFault_IRQn, 0, 0);
  HAL_NVIC_SetPriority(SVCall_IRQn, 0, 0);
  HAL_NVIC_SetPriority(DebugMonitor_IRQn, 0, 0);
  HAL_NVIC_SetPriority(PendSV_IRQn, 0, 0);
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);

  SystemClock_Config();
  MX_GPIO_Clocks_Init();

  __HAL_RCC_DMA2_CLK_DISABLE();

  board_detect();
  MX_GPIO_Common_Init();
  MX_TIM_Init();
  MX_ADC_Init();
  BLDC_Init();

  HAL_ADC_Start(&hadc);

  if (hw_type == HW_TYPE_BASE) {
    out_enable(POWERSWITCH, true);
    out_enable(IGNITION, ignition);
    out_enable(TRANSCEIVER, true);
    // Loop until button is released, only for base board
    while(HAL_GPIO_ReadPin(BUTTON_PORT, BUTTON_PIN)) { HAL_Delay(10); }
  } else {
    out_enable(POWERSWITCH, false);
    ignition = 1;
    knee_detected = 1;
  }
  // Reset LEDs on startup
  out_enable(LED_RED, false);
  out_enable(LED_GREEN, false);
  out_enable(LED_BLUE, false);

  llcan_set_speed(board.CAN, 5000, false, false);
  llcan_init(board.CAN);

  SW_I2C_init();
  IMU_soft_init();

  poweronMelody();

  int32_t board_temp_adcFixdt = adc_buffer.temp << 16;  // Fixed-point filter output initialized with current ADC converted to fixed-point
  int16_t board_temp_adcFilt  = adc_buffer.temp;

  uint16_t sensor_angle[2] = { 0 };
  uint16_t hall_angle_offset[2] = { 0 };

  uint16_t unknown_imu_data[6] = { 0 };

  if (hw_type == HW_TYPE_KNEE) {
    angle_sensor_read(sensor_angle); // Initial data to set offsets between angle sensor and hall sensor
    hall_angle_offset[0] = (sensor_angle[0] * ANGLE_TO_DEGREES);
    hall_angle_offset[1] = (sensor_angle[1] * ANGLE_TO_DEGREES);
  }

  while(1) {
    if ((HAL_GetTick() - tick_prev) >= 1) { // 1kHz loop
      // runs at 100Hz
      if ((HAL_GetTick() - (main_loop_100Hz - main_loop_100Hz_runtime)) >= 10) {
        main_loop_100Hz_runtime = HAL_GetTick();

        calcAvgSpeed();

        if (ignition == 0) {
          cmdL = cmdR = 0;
          enable_motors = 0;
        }

        if (!enable_motors || (torque_cmd_timeout > 10)) {
          cmdL = cmdR = 0;
        }

        if (ignition == 1 && enable_motors == 0 && (!rtY_Left.z_errCode && !rtY_Right.z_errCode) && (ABS(cmdL) < 50 && ABS(cmdR) < 50)) {
          beepShort(6); // make 2 beeps indicating the motor enable
          beepShort(4);
          HAL_Delay(100);
          cmdL = cmdR = 0;
          enable_motors = 1; // enable motors
        }

        if (hw_type == HW_TYPE_KNEE) {
          // Safety to stop operation if angle sensor reading failed TODO: adjust sensivity and add lowpass to angle sensor?
          fault_status.left_angle = (ABS((hall_angle_offset[0] + ((motPosL / 15 / GEARBOX_RATIO_LEFT) % 360)) - (sensor_angle[0] * ANGLE_TO_DEGREES)) > 5);
          fault_status.right_angle = (ABS((hall_angle_offset[1] + ((motPosR / 15 / GEARBOX_RATIO_RIGHT) % 360)) - (sensor_angle[1] * ANGLE_TO_DEGREES)) > 5);

          if (fault_status.left_angle || fault_status.right_angle) {
            cmdL = cmdR = 0;
          }
          // Safety to stop movement when reaching dead angles, around 20 and 340 degrees
          if (((sensor_angle[0] < 900) && (cmdL < 0)) || ((sensor_angle[0] > 15500) && (cmdL > 0))) {
            cmdL = 0;
          }
          if (((sensor_angle[1] < 900) && (cmdR < 0)) || ((sensor_angle[1] > 15500) && (cmdR > 0))) {
            cmdR = 0;
          }
        }

        if (ABS(cmdL) < 10) {
          rtP_Left.n_cruiseMotTgt   = 0;
          rtP_Left.b_cruiseCtrlEna  = 1;
        } else {
          rtP_Left.b_cruiseCtrlEna  = 0;
          if (hw_type == HW_TYPE_KNEE) {
            pwml = -CLAMP((int)cmdL, -TRQ_LIMIT_LEFT, TRQ_LIMIT_LEFT);
          } else {
            pwml = CLAMP((int)cmdL, -TORQUE_BASE_MAX, TORQUE_BASE_MAX);
          }
        }
        if (ABS(cmdR) < 10) {
          rtP_Right.n_cruiseMotTgt  = 0;
          rtP_Right.b_cruiseCtrlEna = 1;
        } else {
          rtP_Right.b_cruiseCtrlEna = 0;
          if (hw_type == HW_TYPE_KNEE) {
            pwmr = -CLAMP((int)cmdR, -TRQ_LIMIT_RIGHT, TRQ_LIMIT_RIGHT);
          } else {
            pwmr = -CLAMP((int)cmdR, -TORQUE_BASE_MAX, TORQUE_BASE_MAX);
          }
        }

        if (ignition_off_counter <= IGNITION_OFF_DELAY) {
          // MOTORS_DATA: speed_L(2), speed_R(2), counter(1), checksum(1)
          uint8_t dat[8];
          uint16_t speedL = rtY_Left.n_mot;
          uint16_t speedR = -(rtY_Right.n_mot); // Invert speed sign for the right wheel
          dat[0] = (speedL >> 8U) & 0xFFU;
          dat[1] = speedL & 0xFFU;
          dat[2] = (speedR >> 8U) & 0xFFU;
          dat[3] = speedR & 0xFFU;
          dat[4] = 0;
          dat[5] = 0;
          dat[6] = pkt_idx;
          dat[7] = crc_checksum(dat, 7, crc_poly);
          can_send_msg((0x201U + board.can_addr_offset), ((dat[7] << 24U) | (dat[6] << 16U) | (dat[5]<< 8U) | dat[4]), ((dat[3] << 24U) | (dat[2] << 16U) | (dat[1] << 8U) | dat[0]), 8U);
          ++pkt_idx;
          pkt_idx &= 0xFU;

          //MOTORS_CURRENT: left_pha_ab(2), left_pha_bc(2), right_pha_ab(2), right_pha_bc(2)
          dat[0] = (rtU_Left.i_phaAB >> 8U) & 0xFFU;
          dat[1] = rtU_Left.i_phaAB & 0xFFU;
          dat[2] = (rtU_Left.i_phaBC >> 8U) & 0xFFU;
          dat[3] = rtU_Left.i_phaBC & 0xFFU;
          dat[4] = (rtU_Right.i_phaAB >> 8U) & 0xFFU;
          dat[5] = rtU_Right.i_phaAB & 0xFFU;
          dat[6] = (rtU_Right.i_phaBC >> 8U) & 0xFFU;
          dat[7] = rtU_Right.i_phaBC & 0xFFU;
          can_send_msg((0x204U + board.can_addr_offset), ((dat[7] << 24U) | (dat[6] << 16U) | (dat[5] << 8U) | dat[4]), ((dat[3] << 24U) | (dat[2] << 16U) | (dat[1] << 8U) | dat[0]), 8U);

          uint16_t left_hall_angle;
          uint16_t right_hall_angle;
          if (hw_type == HW_TYPE_KNEE) {
            angle_sensor_read(sensor_angle);
            left_hall_angle = hall_angle_offset[0] + ((motPosL / 15 / GEARBOX_RATIO_LEFT) % 360);
            right_hall_angle = hall_angle_offset[1] + ((motPosR / 15 / GEARBOX_RATIO_RIGHT) % 360);
          } else {
            left_hall_angle = motPosL / 15;
            right_hall_angle = -motPosR / 15;
          }
          //MOTORS_ANGLE: left angle sensor(2), right angle sensor(2), left hall angle(2), right hall angle(2)
          dat[0] = (sensor_angle[0]>>8U) & 0xFFU;
          dat[1] = sensor_angle[0] & 0xFFU;
          dat[2] = (sensor_angle[1]>>8U) & 0xFFU;
          dat[3] = sensor_angle[1] & 0xFFU;
          dat[4] = (left_hall_angle>>8U) & 0xFFU;
          dat[5] = left_hall_angle & 0xFFU;
          dat[6] = (right_hall_angle>>8U) & 0xFFU;
          dat[7] = right_hall_angle & 0xFFU;
          can_send_msg((0x205U + board.can_addr_offset), ((dat[7] << 24U) | (dat[6] << 16U) | (dat[5] << 8U) | dat[4]), ((dat[3] << 24U) | (dat[2] << 16U) | (dat[1] << 8U) | dat[0]), 8U);

          IMU_soft_sensor_read(unknown_imu_data);
          //BOARD_IMU_RAW1: FIXME: add comment after discovering data, looks like magnetometer
          dat[0] = (unknown_imu_data[0]>>8U) & 0xFFU;
          dat[1] = unknown_imu_data[0] & 0xFFU;
          dat[2] = (unknown_imu_data[1]>>8U) & 0xFFU;
          dat[3] = unknown_imu_data[1] & 0xFFU;
          dat[4] = (unknown_imu_data[2]>>8U) & 0xFFU;
          dat[5] = unknown_imu_data[2] & 0xFFU;
          can_send_msg((0x206U + board.can_addr_offset), ((dat[5] << 8U) | dat[4]), ((dat[3] << 24U) | (dat[2] << 16U) | (dat[1] << 8U) | dat[0]), 6U);

          //BOARD_IMU_RAW2: FIXME: add comment after discovering data, looks like acceleration?
          dat[0] = (unknown_imu_data[3]>>8U) & 0xFFU;
          dat[1] = unknown_imu_data[3] & 0xFFU;
          dat[2] = (unknown_imu_data[4]>>8U) & 0xFFU;
          dat[3] = unknown_imu_data[4] & 0xFFU;
          dat[4] = (unknown_imu_data[5]>>8U) & 0xFFU;
          dat[5] = unknown_imu_data[5] & 0xFFU;
          can_send_msg((0x207U + board.can_addr_offset), ((dat[5] << 8U) | dat[4]), ((dat[3] << 24U) | (dat[2] << 16U) | (dat[1] << 8U) | dat[0]), 6U);
        }
        torque_cmd_timeout = (torque_cmd_timeout < MAX_uint32_T) ? (torque_cmd_timeout+1) : 0;
        main_loop_100Hz_runtime = HAL_GetTick() - main_loop_100Hz_runtime;
        main_loop_100Hz = HAL_GetTick();
      }

      // runs at 10Hz
       if ((HAL_GetTick() - (main_loop_10Hz - main_loop_10Hz_runtime)) >= 100) {
        main_loop_10Hz_runtime = HAL_GetTick();
        if (ignition_off_counter <= IGNITION_OFF_DELAY) {
          // VAR_VALUES: fault_status(0:4), enable_motors(0:1), ignition(0:1), left motor error(1), right motor error(1)
          uint8_t dat[2];
          dat[0] = ((fault_status.right_angle << 5U) | (fault_status.right_i2c << 4U) | (fault_status.left_angle << 3U) | (fault_status.left_i2c << 2U) | (enable_motors << 1U) | ignition);
          dat[1] = rtY_Left.z_errCode;
          dat[2] = rtY_Right.z_errCode;
          can_send_msg((0x202U + board.can_addr_offset), 0x0U, ((dat[2] << 16U) | (dat[1] << 8U) | dat[0]), 3U);
        }
        out_enable(LED_GREEN, ignition);

        main_loop_10Hz_runtime = HAL_GetTick() - main_loop_10Hz_runtime;
        main_loop_10Hz = HAL_GetTick();
      }

      // runs at 1Hz
      if ((HAL_GetTick() - (main_loop_1Hz - main_loop_1Hz_runtime)) >= 1000) {
        main_loop_1Hz_runtime = HAL_GetTick();
         // ####### CALC BOARD TEMPERATURE #######
        filtLowPass32(adc_buffer.temp, TEMP_FILT_COEF, &board_temp_adcFixdt);
        board_temp_adcFilt  = (int16_t)(board_temp_adcFixdt >> 16);  // convert fixed-point to integer
        board_temp_deg_c    = (TEMP_CAL_HIGH_DEG_C - TEMP_CAL_LOW_DEG_C) * (board_temp_adcFilt - TEMP_CAL_LOW_ADC) / (TEMP_CAL_HIGH_ADC - TEMP_CAL_LOW_ADC) + TEMP_CAL_LOW_DEG_C;

        // ####### CALC CALIBRATED BATTERY VOLTAGE #######
        batVoltageCalib = batVoltage * BAT_CALIB_REAL_VOLTAGE / BAT_CALIB_ADC;

        charger_connected = !HAL_GPIO_ReadPin(CHARGER_PORT, CHARGER_PIN);
        uint8_t battery_percent = 100 - (((420 * BAT_CELLS) - batVoltageCalib) / BAT_CELLS / VOLTS_PER_PERCENT / 100); // Battery % left

        // BODY_DATA: MCU temp(2), battery voltage(2), battery_percent(0:7), charger_connected(0:1)
        uint8_t dat[4];
        dat[0] = board_temp_deg_c & 0xFFU;
        dat[1] = (batVoltageCalib >> 8U) & 0xFFU;
        dat[2] = batVoltageCalib & 0xFFU;
        dat[3] = (((battery_percent & 0x7FU) << 1U) | charger_connected);
        can_send_msg((0x203U + board.can_addr_offset), 0x0U, ((dat[3] << 24U) | (dat[2] << 16U) | (dat[1] << 8U) | dat[0]), 4U);

        out_enable(LED_BLUE, false); // Reset LED after CAN RX
        out_enable(LED_GREEN, true); // Always use LED to show that body is on

        if ((hw_type == HW_TYPE_BASE) && ignition) {
          ignition_off_counter = 0;
        } else {
          ignition_off_counter = (ignition_off_counter < MAX_uint32_T) ? (ignition_off_counter+1) : 0;
        }

        if ((TEMP_POWEROFF_ENABLE && board_temp_deg_c >= TEMP_POWEROFF && speedAvgAbs < 20) || (batVoltage < BAT_DEAD && speedAvgAbs < 20)) {  // poweroff before mainboard burns OR low bat 3
          poweroff();
        } else if (rtY_Left.z_errCode || rtY_Right.z_errCode) { // 1 beep (low pitch): Motor error, disable motors
          enable_motors = 0;
          beepCount(1, 24, 1);
        } else if (fault_status.left_angle || fault_status.left_i2c || fault_status.right_angle || fault_status.right_i2c) { // 2 beeps (low pitch): Motor error, disable motors
          beepCount(2, 24, 1);
        } else if (TEMP_WARNING_ENABLE && board_temp_deg_c >= TEMP_WARNING) { // 5 beeps (low pitch): Mainboard temperature warning
          beepCount(5, 24, 1);
        } else if (batVoltage < BAT_LVL1) { // 1 beep fast (medium pitch): Low bat 1
          beepCount(0, 10, 6);
          out_enable(LED_RED, true);
        } else if (batVoltage < BAT_LVL2) { // 1 beep slow (medium pitch): Low bat 2
          beepCount(0, 10, 30);
        } else {  // do not beep
          beepCount(0, 0, 0);
          out_enable(LED_RED, false);
        }

        main_loop_1Hz_runtime = HAL_GetTick() - main_loop_1Hz_runtime;
        main_loop_1Hz = HAL_GetTick();
      }

      if (hw_type == HW_TYPE_BASE) {
        if (HAL_GPIO_ReadPin(BUTTON_PORT, BUTTON_PIN)) {
          cnt_press += 1;
          if (cnt_press == (2 * 1008)) {
            poweroff();
          }
        } else if (cnt_press >= 10) {
          ignition = !ignition;
          out_enable(IGNITION, ignition);
          beepShort(5);
          cnt_press = 0;
        }
      }

      process_can();
      tick_prev = HAL_GetTick();
    }
  }
}
