#include "led_spi.h"
#include <HardwarePWM.h>

/* Private defines */
#define LED_NUM         4
#define LED_REFRESH_MS  3

#define PinSet(pin)   GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, ((uint16_t)1<<(pin)))
#define PinRes(pin)   GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, ((uint16_t)1<<(pin)))

#define PIN_CLK       14
#define PIN_DOUT      13
#define PIN_LOAD      12
#define PIN_OUTE       0

/* Private typedefs */
typedef enum t_led_pos {
  pos_led1 = 0x1,
  pos_led2 = 0x2,
  pos_led3 = 0x4,
  pos_led4 = 0x8
} led_pos_t;

typedef enum t_segment {
  seg_A = 0x01,
  seg_B = 0x02,
  seg_C = 0x04,
  seg_D = 0x08,
  seg_E = 0x10,
  seg_F = 0x20,
  seg_G = 0x40,
  seg_DP = 0x80
} segment_t;

/* LED Symbol map:
    A
  F   B
    G
  E   C
    D   DP
*/
typedef enum t_led_symb {
  sym_Blank = 0x0,
  sym_0 = (uint8_t)(seg_A | seg_B | seg_C | seg_D | seg_E | seg_F),
  sym_1 = (uint8_t)(seg_B | seg_C),
  sym_2 = (uint8_t)(seg_A | seg_B | seg_G | seg_E | seg_D),
  sym_3 = (uint8_t)(seg_A | seg_B | seg_G | seg_C | seg_D),
  sym_4 = (uint8_t)(seg_F | seg_G | seg_B | seg_C),
  sym_5 = (uint8_t)(seg_A | seg_F | seg_G | seg_C | seg_D),
  sym_6 = (uint8_t)(seg_A | seg_F | seg_G | seg_E | seg_D | seg_C),
  sym_7 = (uint8_t)(seg_A | seg_B | seg_C),
  sym_8 = (uint8_t)(seg_A | seg_B | seg_C | seg_D | seg_E | seg_F | seg_G),
  sym_9 = (uint8_t)(seg_A | seg_B | seg_C | seg_D | seg_G | seg_F),
  sym_DP = seg_DP
} led_symb_t;

/* Private variables */
static const led_pos_t led_pos[LED_NUM] = {
  pos_led1, pos_led2, pos_led3, pos_led4
};
static const led_symb_t led_dig[10] = {
  sym_0, sym_1, sym_2, sym_3, sym_4, sym_5, sym_6, sym_7, sym_8, sym_9
};
static led_symb_t led_bufer[LED_NUM] = {sym_Blank, sym_Blank, sym_Blank, sym_Blank};
static Timer ledTimer;
static uint8_t hw_pwm_pins[1] = {PIN_OUTE};
static HardwarePWM OE_pwm(hw_pwm_pins, 1);
static uint32 MaxDuty;

/* Private functions */
void LED_writeData (led_pos_t pos, led_symb_t data) {
  //PinRes(PIN_LOAD); // down latch
  // software spi
  uint8_t i;
  uint16_t sdata = (pos << 8) | data;
  for (i = 16; i != 0; i--) {
    PinRes(PIN_CLK); // prepare CLK
    if (sdata & 0x8000) {
      // if msb bit 1
      PinSet(PIN_DOUT); // MOSI = 1
    } else {
      // if msb bit 0
      PinRes(PIN_DOUT); // MOSI = 0
    }
    asm("nop;");
    PinSet(PIN_CLK); // lock CLK
    sdata <<= 1;
  }
  asm("nop;");
  PinSet(PIN_LOAD); // up latch
  asm("nop;");
  PinRes(PIN_CLK);
  PinRes(PIN_LOAD);
}

void LED_ShowLed(void) {
  static uint8_t cnt = 0;
  LED_writeData(led_pos[cnt], sym_Blank);
  cnt ++;
  if (cnt >= LED_NUM) {
    cnt = 0;
  }
  LED_writeData(led_pos[cnt], led_bufer[cnt]);
}

/* Exported functions */
void LED_Init (void) {
  Serial.println("BIG LED SPI init!");

  /* in future, replace by pwm */
/*  pinMode(PIN_OUTE, OUTPUT);
  PinRes(PIN_OUTE); // output enable
*/
  MaxDuty = OE_pwm.getMaxDuty();
  LED_SetBright(14);

  /* prepare soft spi */
  pinMode(PIN_DOUT, OUTPUT);
  pinMode(PIN_CLK, OUTPUT);
  pinMode(PIN_LOAD, OUTPUT);

  PinRes(PIN_CLK);
  PinRes(PIN_LOAD);

  /* Start timer for refresh leds */
  ledTimer.initializeMs(LED_REFRESH_MS, LED_ShowLed).start();
}

/**
 * @brief Show two Bin number to LED
 * 
 * @param hbin High/Left Bin value 0..99
 * @param lbin Low/Right Bin Value 0..99
 */
void LED_ShowBin(uint8_t hbin, uint8_t lbin) {
  uint8_t a = hbin / 10;
  uint8_t b = hbin % 10;
  uint8_t c = lbin / 10;
  uint8_t d = lbin % 10;

  if (a > 9) { a = 9; }
  if (b > 9) { b = 9; }
  if (c > 9) { c = 9; }
  if (d > 9) { d = 9; }

  led_bufer[0] = led_dig[a];
  led_bufer[1] = led_dig[b];
  led_bufer[2] = led_dig[c];
  led_bufer[3] = led_dig[d];
}

/**
 * @brief Set value 0..9 in single led
 * 
 * @param pos Led num 0..3
 * @param bin Bin value 0..9
 */
void LED_ShowBinPos(uint8_t pos, uint8_t bin) {
  if (pos >= LED_NUM) { return; }
  if (bin > 9) { bin = 9; }

  led_bufer[pos] = led_dig[bin];
}

/**
 * @brief On semicolon
 */
void LED_SemicolonOn(void) {
  led_bufer[1] = led_symb_t((uint8_t)led_bufer[1] | (uint8_t)sym_DP);
  led_bufer[2] = led_symb_t((uint8_t)led_bufer[2] | (uint8_t)sym_DP);
}

/**
 * @brief Off semicolon
 */
void LED_SemicolonOFF(void) {
  led_bufer[1] = led_symb_t((uint8_t)led_bufer[1] & ~((uint8_t)sym_DP));
  led_bufer[2] = led_symb_t((uint8_t)led_bufer[2] & ~((uint8_t)sym_DP));
}

/**
 * @brief Set LED bright.
 * 
 * @param bright Percent value 0..100
 */
void LED_SetBright(uint8_t bright) {
  if (bright > 100) { bright = 100;}
  bright = 100 - bright;

  uint32 new_duty = MaxDuty * bright / 100;
  OE_pwm.setDutyChan(0, bright);
}
