#include "display.h"
#include "registers.h"

#define CYCLES 17

const char numbers_seven_segments[11] = {
  0x3F, /* 0 */
  0x06, /* 1 */
  0x5B, /* 2 */
  0x4F, /* 3 */
  0x66, /* 4 */
  0x6D, /* 5 */
  0x7D, /* 6 */
  0x07, /* 7 */
  0x7F, /* 8 */
  0x6F, /* 9 */
  0x40, /* - */
};

uint16_t pow16(uint16_t num, uint8_t pow) {
  for (uint16_t i = 0; i < pow; i++) {
    num *= num;
  }
  return num;
}


void display_init(display_t *display, uint16_t digits, pin_t pin_data,
                  pin_t pin_sftclk, pin_t pin_strobe) {
  display->digits_count = digits;
  display->bcd_digits = 0;

  display->digits_en = ~0;
  display->digit_dots = ~0;

  display->pin_data = pin_data;
  display->pin_sftclk = pin_sftclk;
  display->pin_strobe = pin_strobe;

  display->max_value = pow16(10, digits);
  display->current_cycle = 0;
  display->current_shifter = 0;
  display->current_digit = 0;
}

/**
 * @brief Convert a digit from a regular number to seven segments
 * @param[in] number The number to convert
 * @param[in] digit The digit from least significant, starting with 0
 * @return Description
 */
uint16_t seven_segment_convert(uint16_t number, uint16_t digit, uint8_t dot);

/**
 * @brief Converts a number to serial strem to send via the SIPO register.
 * @param[in] number The bcd number to convert to the values for shift register
 * @param[in] digit The digit from the least significant, starting with 0
 * @return Description
 */
uint16_t convert_num_digit(uint16_t bcd_number, uint8_t digits_count, uint8_t digit, uint8_t dot);

uint16_t seven_segment_convert(uint16_t bcd_number, uint16_t digit, uint8_t dot) {
  return ~(numbers_seven_segments[(bcd_number >> (digit << 2)) & 0xF] | dot << 7);
}

uint16_t convert_num_digit(uint16_t number, uint8_t digits_count, uint8_t digit, uint8_t dot) {
  uint16_t seven_segments = seven_segment_convert(number, digits_count - 1 - digit, dot);
  uint16_t digits = 1 << digit;

  return (seven_segments << 8) | digits;
}

void display_dots(display_t* display, uint8_t dots) {
  display->digit_dots = dots;
}

void display_convert_number(display_t *display, uint32_t number) {
  uint32_t bcd = 0;
  for (uint8_t i = 0; i < display->digits_count; i++) {
    bcd |= (number % 10) << (i << 2);
    number /= 10;
  }
  display->bcd_digits = bcd;
}

void display_enable_digit(display_t *display, uint8_t digit, uint8_t enable) {
  uint16_t digits = 1 << digit;
  if (enable != 0) {
    display->digits_en |= digits;
  } else {
    display->digits_en &= ~digits;
  }
}

void display_digit_set(display_t *display, uint8_t digit, uint8_t number) {
  reg_write_bits_pos(&display->bcd_digits, number, digit << 2, 0xF);
}

display_update_state_t display_update(display_t *display) {
  display_update_state_t state = DISPLAY_UPDATE_SHIFT;

  if (display->current_cycle == 0) {
    // Set up.
    display->current_digit = (display->current_digit + 1) % display->digits_count;

    if (display->digits_en & (1 << display->current_digit)) {
      display->current_shifter = convert_num_digit(
          display->bcd_digits, display->digits_count, display->current_digit,
          (display->digit_dots >> display->current_digit) & 1);
    } else {
      // Still send the data even for disabled digits,
      // to prevent display having different brightness
      // depending on how many digits are currently on.
      display->current_shifter = 0;
    }

    state = DISPLAY_UPDATE_NEW_DIGIT;
  }

  if (display->current_cycle < CYCLES - 1) {
    pin_write(&display->pin_data,
              reg_read_bits_pos(&display->current_shifter,
                                15 - display->current_cycle,
                                1));

    pin_set(&display->pin_sftclk);
    pin_reset(&display->pin_sftclk);
  } else {
    // Send strobe
    pin_set(&display->pin_strobe);
    pin_reset(&display->pin_strobe);

    state = DISPLAY_UPDATE_DISPLAY_DIGIT;
  }

  display->current_cycle = (display->current_cycle + 1) % CYCLES;

  return state;
}
