// Copyright 2013 Olivier 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 .
//
// -----------------------------------------------------------------------------
//
// Simple FSK bootloader
#include 
#include 
#include 
#include "avrlib/gpio.h"
#include "avrlib/serial.h"
#include "avrlib/watchdog_timer.h"
#include "avr_audio_bootloader/fsk/decoder.h"
using namespace avrlib;
using namespace avr_audio_bootloader;
Gpio led_1_a;
Gpio led_1_k;
Gpio led_2_a;
Gpio led_2_k;
Gpio in_1;
Gpio switch_1;
Decoder decoder;
uint16_t page = 0;
uint8_t rx_buffer[SPM_PAGESIZE + 4];
int main(void) __attribute__ ((naked)) __attribute__ ((section (".init9")));
inline void Init() {
  cli();  
  switch_1.set_mode(DIGITAL_INPUT);
  switch_1.High();
  led_1_a.set_mode(DIGITAL_OUTPUT);
  led_1_k.set_mode(DIGITAL_OUTPUT);
  led_2_a.set_mode(DIGITAL_OUTPUT);
  led_2_k.set_mode(DIGITAL_OUTPUT);
  in_1.set_mode(DIGITAL_INPUT);
  in_1.High();
}
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 FlashLeds(bool error) {
  for (uint8_t i = 0; i < 8; ++i) {
    _delay_ms(20);
    if (error) {
      led_1_a.High();
      led_1_k.Low();
    } else {
      led_1_a.Low();
      led_1_k.High();
    }
    _delay_ms(20);
    led_1_a.Low();
    led_1_k.Low();
  }
}
uint32_t crc32 (uint32_t crc, uint8_t* buffer, uint8_t length) {
  crc = crc ^ ~0UL;
  for (uint8_t i = 0; i < length; ++i)  {
    crc = crc ^ *buffer++;
    for (uint8_t j = 0; j < 8; j++)  { 
      if (crc & 1) {
        crc = (crc >> 1) ^ 0xEDB88320; 
      } else {
        crc = crc >> 1; 
      } 
    }
  }
  crc = crc ^ ~0UL;
  return crc;
}
inline void LoaderLoop() {
  uint8_t page_byte = 0;
  decoder.Init();
  decoder.Sync();
  decoder.set_packet_destination(rx_buffer);
  page = 0;
  TCCR2A = 0;
  TCCR2B = 2;
  while (1) {
    // Sample the clock input at 15625 Hz and feed to the FSK decoder.
    if (TCNT2 >= (8000000 / 8 / 15625 - 1)) {
      TCNT2 = 0;
      led_2_a.set_value(page_byte & 1);
      led_2_k.set_value(!(page_byte & 1));
      switch (decoder.PushSample(in_1.value())) {
        case DECODER_STATE_ERROR_SYNC:
          FlashLeds(true);
          decoder.Sync();
          break;
        case DECODER_STATE_END_OF_TRANSMISSION:
          return;
          break;
        case DECODER_STATE_PACKET_RECEIVED:
          {
#ifdef DO_NOT_CHECK_CRC
            uint32_t crc = 0;
            uint32_t expected_crc = 0;    
#else            
            uint32_t crc = crc32(0, rx_buffer, SPM_PAGESIZE);
            uint32_t expected_crc = \
                (static_cast(rx_buffer[SPM_PAGESIZE + 0]) << 24) | \
                (static_cast(rx_buffer[SPM_PAGESIZE + 1]) << 16) | \
                (static_cast(rx_buffer[SPM_PAGESIZE + 2]) <<  8) | \
                (static_cast(rx_buffer[SPM_PAGESIZE + 3]) <<  0);
#endif  // DO_NOT_CHECK_CRC               
            if (crc == expected_crc) {
              WriteBufferToFlash();
              page += SPM_PAGESIZE;
              ++page_byte;
            } else {
              FlashLeds(true);
            }
          }
          decoder.Sync();
          break;
        default:
          break;
      }
    }
  }
}
int main(void) {
  ResetWatchdog();
  Init();
  if (!switch_1.value()) {
    FlashLeds(false);
    LoaderLoop();
    FlashLeds(false);
  }
  void (*main_entry_point)(void) = 0x0000;
  main_entry_point();
}