#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;

  for (uint8_t i = 0; i < display->digits_count; i++) {
    display->digits[i] = '\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;
}

uint16_t seven_segment_convert(char character, uint16_t digit, uint8_t dot);
uint16_t convert_num_digit(char character, uint8_t digits_count, uint8_t digit, uint8_t dot);

uint16_t seven_segment_convert(char character, uint16_t digit, uint8_t dot) {
  uint8_t data;

  if (character >= '0' && character <= '9') {
    data = numbers_seven_segments[character - '0'];
  } else {
    switch (character) {
    case 'E':
      data = 0b01111001;
      break;
    case 'r':
      data = 0b01010000;
      break;
    default:
      data = 0;
      break;
    }
  }

  return ~(data | dot << 7);
}

uint16_t convert_num_digit(char character, uint8_t digits_count, uint8_t digit, uint8_t dot) {
  uint16_t seven_segments = seven_segment_convert(character, 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 digit = 0;
  for (uint8_t i = 0; i < display->digits_count; i++) {
    digit = (number % 10);
    display->digits[display->digits_count - 1 - i] = digit + '0';
    number /= 10;
  }
}

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_digits_set(display_t *display, char* digits) {
  for (uint8_t digit = 0; digit < display->digits_count; digit++) {
    display->digits[digit] = digits[digit];
  }
}

void display_digit_set(display_t *display, uint8_t digit, uint8_t number) {
  display->digits[digit] = number;
}

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->digits[display->current_digit], 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;
}
