// Copyright 2011 Emilie Gillet.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// -----------------------------------------------------------------------------
//
// Bootloader supporting MIDI SysEx update.
//
// Caveat: assumes the firmware flashing is always done from first to last
// block, in increasing order. Random access flashing is not supported!

#include <avr/boot.h>
#include <avr/pgmspace.h>
#include <avr/delay.h>

#include "avrlib/gpio.h"
#include "avrlib/serial.h"
#include "avrlib/watchdog_timer.h"

#include "avr_audio_bootloader/crc32.h"
#include "avr_audio_bootloader/fsk/decoder.h"

#include "grids/hardware_config.h"

using namespace avrlib;
using namespace grids;
using namespace avr_audio_bootloader;

MidiInput midi;
Inputs inputs;
Leds leds;
Decoder decoder;

uint16_t page = 0;
uint8_t rx_buffer[SPM_PAGESIZE + 4];

void (*main_entry_point)(void) = 0x0000;

inline void Init() {
  cli();
  leds.set_mode(DIGITAL_OUTPUT);
  inputs.set_mode(DIGITAL_INPUT);
  inputs.EnablePullUpResistors();
}

void WriteBufferToFlash() {
  uint16_t i;
  const uint8_t* p = rx_buffer;
  eeprom_busy_wait();

  boot_page_erase(page);
  boot_spm_busy_wait();

  for (i = 0; i < SPM_PAGESIZE; i += 2) {
    uint16_t w = *p++;
    w |= (*p++) << 8;
    boot_page_fill(page + i, w);
  }

  boot_page_write(page);
  boot_spm_busy_wait();
  boot_rww_enable();
}

void FlashLedsOk() {
  for (uint8_t i = 0; i < 8; ++i) {
    _delay_ms(50);
    leds.Write(0xf);
    _delay_ms(50);
    leds.Write(LED_CLOCK);
  }
}

void FlashLedsError() {
  for (uint8_t i = 0; i < 5; ++i) {
    _delay_ms(120);
    leds.Write(0xf);
    _delay_ms(120);
    leds.Write(0x0);
  }
}

// MIDI loader -----------------------------------------------------------------

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

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

inline void LoaderLoop() {
  uint8_t byte;
  uint16_t bytes_read = 0;
  uint16_t rx_buffer_index;
  uint8_t state = MATCHING_HEADER;
  uint8_t checksum;
  uint8_t sysex_commands[2];
  uint8_t status = 0;
  uint8_t page_byte = 0;

  midi.Init();
  decoder.Init();
  decoder.Sync();
  decoder.set_packet_destination(rx_buffer);
  page = 0;

  TCCR2A = 0;
  TCCR2B = 2;

  while (1) {
    leds.Write(LED_CLOCK | (LED_BD >> ((page_byte + 3) & 0x3)));

    // Sample the clock input at 20kHz and feed to the FSK decoder.
    if (TCNT2 >= (F_CPU / 8 / 40000 - 1)) {
      TCNT2 = 0;
      
      switch (decoder.PushSample(inputs.Read() & INPUT_CLOCK)) {
        case DECODER_STATE_ERROR_SYNC:
          FlashLedsError();
          decoder.Sync();
          break;

        case DECODER_STATE_END_OF_TRANSMISSION:
          return;
          break;

        case DECODER_STATE_PACKET_RECEIVED:
          {
            uint32_t crc = crc32(0, rx_buffer, SPM_PAGESIZE);
            uint32_t expected_crc = \
                (static_cast<uint32_t>(rx_buffer[SPM_PAGESIZE + 0]) << 24) | \
                (static_cast<uint32_t>(rx_buffer[SPM_PAGESIZE + 1]) << 16) | \
                (static_cast<uint32_t>(rx_buffer[SPM_PAGESIZE + 2]) <<  8) | \
                (static_cast<uint32_t>(rx_buffer[SPM_PAGESIZE + 3]) <<  0);
            if (crc == expected_crc) {
              WriteBufferToFlash();
              page += SPM_PAGESIZE;
              ++page_byte;
            } else {
              FlashLedsError();
            }
          }
          decoder.Sync();
          break;

        default:
          break;
      }
    }
    
    // Poll the MIDI input.
    if (midi.readable()) {
      byte = midi.ImmediateRead();
      if (byte > 0xf0 && byte != 0xf7) {
        continue;
      }
      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;
          }
          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;
            status = 0;
            bytes_read = 0;
          }
          break;

        case READING_DATA:
          if (byte < 0x80) {
            if (bytes_read & 1) {
              rx_buffer[rx_buffer_index] |= byte & 0xf;
              if (rx_buffer_index < SPM_PAGESIZE) {
                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) {
              // Reset.
              return;
            } else if (rx_buffer_index == SPM_PAGESIZE + 1 &&
                       sysex_commands[0] == 0x7e &&
                       sysex_commands[1] == 0x00 &&
                       rx_buffer[rx_buffer_index - 1] == checksum) {
              // Block write.
              WriteBufferToFlash();
              page += SPM_PAGESIZE;
              ++page_byte;
            } else {
              FlashLedsError();
            }
            state = MATCHING_HEADER;
            bytes_read = 0;
          }
          break;
      }
    }
  }
}

int main(void) {
  ResetWatchdog();
  Init();
  _delay_ms(40);
  if (!(inputs.Read() & INPUT_SW_RESET)) {
    FlashLedsOk();
    LoaderLoop();
    FlashLedsOk();
    FlashLedsOk();
  }
  main_entry_point();
}
