// Copyright 2013 Emilie Gillet.
//
// Author: Emilie Gillet (emilie.o.gillet@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// 
// See http://creativecommons.org/licenses/MIT/ for more information.

#include <stm32f10x_conf.h>
#include <string.h>

#include "stmlib/system/bootloader_utils.h"
#include "stmlib/system/flash_programming.h"
#include "stmlib/system/system_clock.h"
#include "stmlib/utils/ring_buffer.h"

#include "yarns/drivers/display.h"
#include "yarns/drivers/encoder.h"
#include "yarns/drivers/midi_io.h"
#include "yarns/drivers/system.h"

using namespace yarns;
using namespace stmlib;

System sys;
Display display;
Encoder encoder;
MidiIO midi_io;
RingBuffer<uint8_t, 128> midi_in_buffer;

extern "C" {
  
void HardFault_Handler(void) { while (1); }
void MemManage_Handler(void) { while (1); }
void BusFault_Handler(void) { while (1); }
void UsageFault_Handler(void) { while (1); }
void NMI_Handler(void) { }
void SVC_Handler(void) { }
void DebugMon_Handler(void) { }
void PendSV_Handler(void) { }

}

extern "C" {

void SysTick_Handler() {
  static uint8_t counter;
  if ((++counter & 7) == 0) {
    system_clock.Tick();  // Tick global ms counter.
    uint32_t ms_clock = system_clock.milliseconds();
    if ((ms_clock & 0x3f) == 0 && display.mutable_buffer()[0] >= '\x98') {
      display.mutable_buffer()[0] = '\x98' + ((ms_clock >> 6) & 7);
    }
    display.RefreshSlow();
    encoder.Debounce();
  }
  display.RefreshFast();
  
  // Try to read some MIDI input.
  if (midi_io.readable()) {
    midi_in_buffer.Overwrite(midi_io.ImmediateRead());
  }
}

}

void PrintByte(uint8_t byte) {
  char str[] = "00";
  str[0] += byte / 10;
  str[1] += byte % 10;
  display.Print(str);
}

static uint32_t current_address;

void ProgramPage(const uint8_t* data, size_t size) {
  FLASH_Unlock();
  FLASH_ErasePage(current_address);
  const uint32_t* words = static_cast<const uint32_t*>(
      static_cast<const void*>(data));
  for (size_t written = 0; written < size; written += 4) {
    FLASH_ProgramWord(current_address, *words++);
    current_address += 4;
  }
}

enum SysExReceptionState {
  MATCHING_HEADER = 0,
  READING_COMMAND = 1,
  READING_DATA = 2,
};

enum SysExReturnCode {
  SYSEX_RESULT_ERROR = 0,
  SYSEX_RESULT_OK = 1,
  SYSEX_RESULT_PAGE_WRITTEN = 2,
  SYSEX_RESULT_DONE = 3
};

static const uint8_t sysex_header[] = {
  0xf0,  // <SysEx>
  0x00, 0x21, 0x02,  // Mutable instruments manufacturer id.
  0x00, 0x0b,  // Product ID for Yarns.
};

static uint32_t bytes_read = 0;
static uint16_t rx_buffer_index = 0;
static SysExReceptionState state = MATCHING_HEADER;
static uint8_t checksum;
static uint8_t sysex_commands[2];
static uint8_t page_index = 0;
static uint8_t rx_buffer[PAGE_SIZE + 1];
static uint8_t total_bytes = 0;

const uint32_t kStartAddress = 0x08001000;

void PrepareReception() {
  bytes_read = 0;
  rx_buffer_index = 0;
  state = MATCHING_HEADER;
  page_index = 0;
  current_address = kStartAddress;
  midi_in_buffer.Init();
  
  display.Print("\x98 ");
}

SysExReturnCode ProcessSysExByte(uint8_t byte) {
  if (byte > 0xf0 && byte != 0xf7) {
    return SYSEX_RESULT_OK;
  }
  ++total_bytes;
  switch (state) {
    case MATCHING_HEADER:
      if (byte == sysex_header[bytes_read]) {
        ++bytes_read;
        if (bytes_read == sizeof(sysex_header)) {
          bytes_read = 0;
          state = READING_COMMAND;
        }
      } else {
        bytes_read = 0;
        return SYSEX_RESULT_ERROR;
      }
      break;

    case READING_COMMAND:
      if (byte < 0x80) {
        sysex_commands[bytes_read++] = byte;
        if (bytes_read == 2) {
          bytes_read = 0;
          rx_buffer_index = 0;
          checksum = 0;
          state = READING_DATA;
        }
      } else {
        state = MATCHING_HEADER;
        bytes_read = 0;
        return SYSEX_RESULT_ERROR;
      }
      break;

    case READING_DATA:
      if (byte < 0x80) {
        if (bytes_read & 1) {
          rx_buffer[rx_buffer_index] |= byte & 0xf;
          if (rx_buffer_index < PAGE_SIZE) {
            checksum += rx_buffer[rx_buffer_index];
          }
          ++rx_buffer_index;
        } else {
          rx_buffer[rx_buffer_index] = (byte << 4);
        }
        ++bytes_read;
      } else if (byte == 0xf7) {
        if (sysex_commands[0] == 0x7f &&
            sysex_commands[1] == 0x00 &&
            bytes_read == 0) {
          return SYSEX_RESULT_DONE;
        } else if (rx_buffer_index == PAGE_SIZE + 1 &&
                   sysex_commands[0] == 0x7e &&
                   sysex_commands[1] == 0x00 &&
                   rx_buffer[rx_buffer_index - 1] == checksum) {
          // Block write.
          ProgramPage(rx_buffer, PAGE_SIZE);
          ++page_index;
        }
        state = MATCHING_HEADER;
        bytes_read = 0;
        return SYSEX_RESULT_PAGE_WRITTEN;
      }
      break;
  }
  return SYSEX_RESULT_OK;
}

void Init() {
  SystemInit();
  
  RCC_APB2PeriphClockCmd(
      RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC |
      RCC_APB2Periph_TIM1 | RCC_APB2Periph_USART1, ENABLE);
  
  system_clock.Init();
  encoder.Init();
  display.Init();
  midi_io.Init();
  
  SysTick_Config(F_CPU / 8000);
}

int main(void) {
  Init();
  PrepareReception();
  bool exit_updater = !encoder.pressed_immediate();
  while (!exit_updater) {
    while (midi_in_buffer.readable() && !exit_updater) {
      SysExReturnCode result = ProcessSysExByte(midi_in_buffer.ImmediateRead());
      switch (result) {
        case SYSEX_RESULT_DONE:
          {
            display.Print("OK");
            system_clock.Delay(1000);
            exit_updater = true;
          }
          break;
          
        case SYSEX_RESULT_PAGE_WRITTEN:
          {
            PrintByte(page_index);
          }
          break;
          
        case SYSEX_RESULT_OK:
          break;
          
        case SYSEX_RESULT_ERROR:
          display.Print("Er");
          system_clock.Delay(1000);
          PrepareReception();
          break;
      }
    }
  }
  Uninitialize();
  JumpTo(kStartAddress);
  while (1) { }
}
