/* Famicom Dumper/Programmer
 *
 * Copyright notice for this file:
 *  Copyright (C) 2020 Cluster
 *  http://clusterrr.com
 *  clusterrr@clusterrr.com
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "defines.h"
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include <inttypes.h>
#include "usart.h"
#include "comm.h"
#include "dumper.h"
#include "crc.h"

static void (*jump_to_bootloader)(void) = (void*)0xF800;
uint16_t flash_buffer_mask = 0xFFC0;

ISR(USART0_RX_vect)
{
  unsigned char b;
  while (UCSR0A & (1<<RXC0))
  {
    b = UDR0;
    comm_proceed(b);
  }
}

static void set_flash_buffer_size(uint16_t value)
{
  // Set maximum number of bytes in multi-byte program
  uint8_t bit_value = 0;
  while (value > 1)
  {
    value >>= 1;
    bit_value++;
  }
  flash_buffer_mask = 0xFFFF << bit_value;
}

static void set_address(uint16_t address)
{
  unsigned char l = address & 0xFF;
  unsigned char h = (address >> 8) & 0xFF;
  
  PORTA = l;
  PORTC = h;
  
  // PPU /A13
  if ((address >> 13) & 1)
    PORTF &= ~(1<<4);
  else
    PORTF |= 1<<4;
}

static inline void set_romsel(uint16_t address)
{
  if (address & 0x8000)
  {
    ROMSEL_LOW;
  } else {
    ROMSEL_HI;
  }
}

static inline void set_coolboy_rd(uint16_t address)
{
  if (address & 0x8000)
  {
    COOLBOY_PORT |= 1<<COOLBOY_WR_PIN;
    COOLBOY_PORT &= ~(1<<COOLBOY_RD_PIN);
  } else {
    COOLBOY_PORT |= 1<<COOLBOY_WR_PIN;
    COOLBOY_PORT |= 1<<COOLBOY_RD_PIN;
  }
}

static inline void set_coolboy_wr(uint16_t address)
{
  if (address & 0x8000)
  {
    COOLBOY_PORT &= ~(1<<COOLBOY_WR_PIN);
    COOLBOY_PORT |= 1<<COOLBOY_RD_PIN;
  } else {
    COOLBOY_PORT |= 1<<COOLBOY_WR_PIN;
    COOLBOY_PORT |= 1<<COOLBOY_RD_PIN;
  }
}

static unsigned char read_prg_byte(uint16_t address)
{
  PHI2_LOW;
  ROMSEL_HI;
  MODE_READ;
  PRG_READ;  
  set_address(address);
  PHI2_HI;
  set_romsel(address); // set /ROMSEL low if need
  set_coolboy_rd(address); // COOLBOY's /oe low if need
  _delay_us(1);
  uint8_t result = PIND;
  ROMSEL_HI;
  set_coolboy_rd(0);
  return result;
}

static unsigned char read_chr_byte(uint16_t address)
{
  PHI2_LOW;
  ROMSEL_HI;
  MODE_READ;
  set_address(address);
  CHR_READ_LOW;
  _delay_us(1);
  uint8_t result = PIND;
  CHR_READ_HI;
  PHI2_HI;
  return result;
}

static void read_prg_send(uint16_t address, uint16_t len)
{
  LED_GREEN_ON;
  comm_start(COMMAND_PRG_READ_RESULT, len);
  while (len > 0)
  {
    comm_send_byte(read_prg_byte(address));
    len--;
    address++;
  }
  set_address(0);
  LED_GREEN_OFF;
}

static void read_chr_send(uint16_t address, uint16_t len)
{
  LED_GREEN_ON;
  comm_start(COMMAND_CHR_READ_RESULT, len);
  while (len > 0)
  {
    comm_send_byte(read_chr_byte(address));
    len--;
    address++;
  }
  set_address(0);
  LED_GREEN_OFF;
}

static void read_prg_crc_send(uint16_t address, uint16_t len)
{
  LED_GREEN_ON;
  uint16_t crc = 0;
  MODE_READ;
  PRG_READ;  
  PHI2_HI;
  set_romsel(address); // set /ROMSEL low if need
  while (len > 0)
  {
    PORTA = address & 0xFF;
    PORTC = (address >> 8) & 0xFF;
    _delay_us(1);
    crc = calc_crc16(crc, PIND);
    len--;
    address++;
  }
  ROMSEL_HI;
  set_address(0);
  comm_start(COMMAND_PRG_READ_RESULT, 2);
  comm_send_byte(crc & 0xFF);
  comm_send_byte((crc >> 8) & 0xFF);
  LED_GREEN_OFF;
}

static void read_chr_crc_send(uint16_t address, uint16_t len)
{
  LED_GREEN_ON;
  uint16_t crc = 0;
  while (len > 0)
  {
    crc = calc_crc16(crc, read_chr_byte(address));
    len--;
    address++;
  }
  set_address(0);
  comm_start(COMMAND_CHR_READ_RESULT, 2);
  comm_send_byte(crc & 0xFF);
  comm_send_byte((crc >> 8) & 0xFF);
  LED_GREEN_OFF;
}

static void write_prg_byte(uint16_t address, uint8_t data)
{
  PHI2_LOW;
  ROMSEL_HI;
  MODE_WRITE;
  PRG_WRITE;
  PORTD = data;
  set_address(address); // PHI2 low, ROMSEL always HIGH
  _delay_us(1);
  
  PHI2_HI;
  set_romsel(address); // ROMSEL is low if need, PHI2 high  
  set_coolboy_wr(address); // COOLBOY's /we is low if need
  
  _delay_us(1); // WRITING
  
  // PHI2 low, ROMSEL high
  PHI2_LOW;
  ROMSEL_HI;
  set_coolboy_wr(0);
  
  // Back to read mode
  _delay_us(1);
  PRG_READ;
  MODE_READ;
  set_address(0);

  // Set phi2 to high state to keep cartridge unreseted
  PHI2_HI;
}

static void write_chr_byte(uint16_t address, uint8_t data)
{
  PHI2_LOW;
  ROMSEL_HI;
  MODE_WRITE;
  PORTD = data;  
  set_address(address); // PHI2 low, ROMSEL always HIGH
  CHR_WRITE_LOW;
    
  _delay_us(1); // WRITING
  
  CHR_WRITE_HI;
  
  MODE_READ;
  set_address(0);
  PHI2_HI;
}

static void write_prg(uint16_t address, uint16_t len, uint8_t* data)
{
  LED_RED_ON;
  while (len > 0)
  {
    write_prg_byte(address, *data);
    address++;
    len--;
    data++;
  }
  LED_RED_OFF;
}

static void write_chr(uint16_t address, uint16_t len, uint8_t* data)
{
  LED_RED_ON;
  while (len > 0)
  {
    write_chr_byte(address, *data);
    address++;
    len--;
    data++;
  }
  LED_RED_OFF;
}

static inline void write_prg_flash_command(uint16_t address, uint8_t data)
{
  write_prg_byte(address | 0x8000, data);
}

static void erase_flash_sector()
{
  LED_RED_ON;
  write_prg_flash_command(0x0000, 0xF0);
  write_prg_flash_command(0x0AAA, 0xAA);
  write_prg_flash_command(0x0555, 0x55);
  write_prg_flash_command(0x0AAA, 0x80);
  write_prg_flash_command(0x0AAA, 0xAA);
  write_prg_flash_command(0x0555, 0x55);
  write_prg_flash_command(0x0000, 0x30);
  
  uint8_t res;
  int16_t last_res = -1;
  TCNT1 = 0;
  // waiting for result
  while (1)
  {
    if (TCNT1 >= 23437) // 3 seconds
    {
      // timeout
      comm_start(COMMAND_FLASH_ERASE_TIMEOUT, 0);
      break;
    }
    res = read_prg_byte(0x8000);
    if ((last_res == -1) || ((res != (last_res & 0xFF))))
    {
      // in progress
      last_res = res;
      continue;
    }
    // done
    if (res == 0xFF)
    {
       // ok
       comm_start(COMMAND_PRG_WRITE_DONE, 0);
       break;
    } else {
       // error
       comm_start(COMMAND_FLASH_ERASE_ERROR, 1);
       comm_send_byte(res);
       break;
    }
  }
  LED_RED_OFF;
}

static void write_flash(uint16_t address, uint16_t len, uint8_t* data)
{
  LED_RED_ON;
  while (len > 0)
  {
    uint16_t count = 0;
    uint8_t* d = data;
    uint16_t a = address;
    uint16_t last_address;
    uint8_t last_data;
    uint16_t address_base = a & flash_buffer_mask;
    while ((len > 0) && ((a & flash_buffer_mask) == address_base))
    {
      if (*d != 0xFF)
        count++;
      a++;
      len--;
      d++;
    }

    if (count)
    {
      write_prg_flash_command(0x0000, 0xF0);
      write_prg_flash_command(0x0AAA, 0xAA);
      write_prg_flash_command(0x0555, 0x55);
      write_prg_flash_command(0x0000, 0x25);
      write_prg_flash_command(0x0000, count-1);

      while (count > 0)
      {
        if (*data != 0xFF)
        {
          write_prg_flash_command(address, *data);
          last_address = address;
          last_data = *data;
          count--;
        }
        address++;
        data++;
      }
    
      write_prg_flash_command(0x0000, 0x29);

      TCNT1 = 0;
      // waiting for result
      while (1)
      {
        if (TCNT1 >= 7812) // 1 second
        {
          // timeout
          comm_start(COMMAND_FLASH_WRITE_TIMEOUT, 0);
          LED_RED_OFF;
          return;
        }
        uint8_t read_1 = read_prg_byte(last_address | 0x8000);
        uint8_t read_2 = read_prg_byte(last_address | 0x8000);
        uint8_t read_3 = read_prg_byte(last_address | 0x8000);
        if (((read_1 ^ read_2) & (1 << 6)) && ((read_2 ^ read_3) & (1 << 6)))
        {
          if (read_1 & (1 << 1))
          {
            comm_start(COMMAND_FLASH_WRITE_ERROR, 3);
            comm_send_byte(read_1);
            comm_send_byte(read_2);
            comm_send_byte(read_3);
            LED_RED_OFF;
            return;
          } else if (read_1 & (1 << 5)) {
            comm_start(COMMAND_FLASH_WRITE_TIMEOUT, 3);
            comm_send_byte(read_1);
            comm_send_byte(read_2);
            comm_send_byte(read_3);
            LED_RED_OFF;
            return;
          }
        } else {
          read_1 = read_prg_byte(last_address | 0x8000);
          read_2 = read_prg_byte(last_address | 0x8000);
          if (read_1 == read_2 && read_2 == last_data)
            break; // ok
        }
      }
    }

    address = a;
    data = d;
  }
  comm_start(COMMAND_PRG_WRITE_DONE, 0);
  LED_RED_OFF;
}

static uint8_t transfer_fds_byte(uint8_t *output, uint8_t input, uint8_t *end_of_head)
{
  TCNT1 = 0;
  while (!IRQ_FIRED)
  {
    // waiting for interrupt
    // timeout 5 secs
    if (TCNT1 >= 39060)
    {
      write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop
      comm_start(COMMAND_FDS_TIMEOUT, 0);
      return 0;
    }
  }
  if (output)
    *output = read_prg_byte(FDS_DATA_READ);
  write_prg_byte(FDS_DATA_WRITE, input); // clear interrupt
  uint8_t status = read_prg_byte(FDS_DISK_STATUS);
  if (end_of_head)
    *end_of_head |= (status >> 6) & 1;
  TCNT1 = 0;
  while (IRQ_FIRED)
  {
    // is interrupt flag cleared?
    // timeout 5 secs
    if (TCNT1 >= 39060)
    {
      write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop
      comm_start(COMMAND_FDS_TIMEOUT, 0);
      return 0;
    }
  }
  return 1;
}

static uint8_t read_fds_block_send(uint16_t length, uint8_t send, uint8_t *crc_ok, uint8_t *end_of_head, uint16_t *file_size, uint32_t gap_delay)
{
  uint8_t data;
  uint8_t status;
  uint32_t b;

  write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_CONTROL_MOTOR_ON); // motor on without transfer
  if (gap_delay < 30000)
    DELAY_CLOCK(gap_delay);
  else
    DELAY_KILO_CLOCK(gap_delay / 1000);
  if (send)
  {
    LED_GREEN_ON;
    comm_start(COMMAND_FDS_READ_RESULT_BLOCK, length + 2);
  }
  // start transfer, enable IRQ
  write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_CONTROL_MOTOR_ON | FDS_CONTROL_TRANSFER_ON | FDS_CONTROL_IRQ_ON);
  for (b = 0; b < length; b++)
  {
    if (!transfer_fds_byte(&data, 0, end_of_head))
      return 0;
    if (file_size)
    {
      if (b == 13)
        *file_size |= data;
      else if (b == 14)
        *file_size |= data << 8;
    }
    if (send)
      comm_send_byte(data);
  }
  if (!transfer_fds_byte(0, 0, end_of_head))
    return 0;
  write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_CONTROL_MOTOR_ON | FDS_CONTROL_TRANSFER_ON | FDS_CONTROL_IRQ_ON | FDS_CONTROL_CRC); // enable CRC control
  if (!transfer_fds_byte(0, 0, end_of_head))
    return 0;
  status = read_prg_byte(FDS_DISK_STATUS);
  *crc_ok &= ((status >> 4) & 1) ^ 1;
  *end_of_head |= (status >> 6) & 1;
  if (send)
  {
    comm_send_byte(*crc_ok); // CRC check result
    comm_send_byte(*end_of_head); // end of head meet?
  }
  LED_GREEN_OFF;
  return 1; // success
}

static uint8_t write_fds_block(uint8_t *data, uint16_t length, uint32_t gap_delay)
{
  uint8_t end_of_head = 0;
  LED_RED_ON;
  write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_CONTROL_MOTOR_ON); // motor on without transfer
  read_prg_byte(FDS_DRIVE_STATUS); // check if disk is inserted
  write_prg_byte(FDS_CONTROL, FDS_CONTROL_WRITE | FDS_CONTROL_MOTOR_ON); // enable writing without transfer
  if (gap_delay < 30000)
    DELAY_CLOCK(gap_delay);
  else
    DELAY_KILO_CLOCK(gap_delay / 1000);
  write_prg_byte(FDS_DATA_WRITE, 0x00); // write $00
  // start transfer, enable IRQ
  write_prg_byte(FDS_CONTROL, FDS_CONTROL_WRITE | FDS_CONTROL_MOTOR_ON | FDS_CONTROL_TRANSFER_ON | FDS_CONTROL_IRQ_ON);
  transfer_fds_byte(0, 0x80, &end_of_head);  // write $80
  while (length)
  {
    if (end_of_head)
    {
      write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop
      comm_start(COMMAND_FDS_END_OF_HEAD, 0);
      return 0;
    }
    if (!transfer_fds_byte(0, *data, &end_of_head))
    {
      write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop
      comm_start(COMMAND_FDS_TIMEOUT, 0);
      return 0;
    }
    data++;
    length--;
  }
  if (!transfer_fds_byte(0, 0xFF, &end_of_head))
  {
    write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop
    comm_start(COMMAND_FDS_TIMEOUT, 0);
    return 0;
  }
  if (end_of_head)
  {
    write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop
    comm_start(COMMAND_FDS_END_OF_HEAD, 0);
    return 0;
  }
  write_prg_byte(FDS_CONTROL, FDS_CONTROL_WRITE | FDS_CONTROL_MOTOR_ON | FDS_CONTROL_TRANSFER_ON | FDS_CONTROL_IRQ_ON | FDS_CONTROL_CRC);  // enable CRC control
  DELAY_CLOCK(FDS_WRITE_CRC_DELAY);
  TCNT1 = 0;
  while (1)
  {
    uint8_t status = read_prg_byte(FDS_DRIVE_STATUS);
    if (!(status & 2))
      break; // ready
    // timeout 1 sec
    if (TCNT1 >= 7812)
    {
      write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop
      comm_start(COMMAND_FDS_TIMEOUT, 0);
      return 0;
    }
  }  
  LED_RED_OFF;
  return 1;
}

static void fds_transfer(uint8_t block_read_start, uint8_t block_read_count, uint8_t block_write_count, uint8_t *block_write_ids, uint16_t *write_lengths,
    uint8_t *write_data)
{
  uint8_t crc_ok = 1;
  uint8_t end_of_head = 0;
  uint8_t current_block = 0;
  uint8_t current_writing_block = 0;

  write_prg_byte(FDS_IRQ_CONTROL, 0x00); // disable timer IRQ
  write_prg_byte(FDS_MASTER_IO, 0x01); // enable disk registers
  write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset
  uint8_t ram_adapter_connected = 1;
  write_prg_byte(FDS_EXT_WRITE, 0x00); // Ext. connector
  write_prg_byte(0x0000, 0xFF); // To prevent open bus read
  if ((read_prg_byte(FDS_EXT_READ) & 0x7F) != 0x00)
    ram_adapter_connected = 0;
  write_prg_byte(FDS_EXT_WRITE, 0xFF); // Ext. connector
  write_prg_byte(0x0000, 0x00); // To prevent open bus read
  if ((read_prg_byte(FDS_EXT_READ) & 0x7F) != 0x7F)
    ram_adapter_connected = 0;
  if (!ram_adapter_connected)
  {
    comm_start(COMMAND_FDS_NOT_CONNECTED, 0);
    return;
  }
  if (read_prg_byte(FDS_DRIVE_STATUS) & 1)
  {
    comm_start(COMMAND_FDS_DISK_NOT_INSERTED, 0);
    return;
  }  
  // battery test
  write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_CONTROL_MOTOR_ON); // monor on, unreset
  _delay_ms(100);
  if ((read_prg_byte(FDS_EXT_READ) & 0x80) == 0)
  {
    // battery low
    write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop
    comm_start(COMMAND_FDS_BATTERY_LOW, 0);
    return;
  }
  // waiting until drive is rewinded
  TCNT1 = 0;
  uint8_t secs = 0;
  do
  {
    // timeout 15 secs
    if (TCNT1 >= 7812)
    {
      TCNT1 = 0;
      secs++;
      if (secs >= 15)
      {
        write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop
        comm_start(COMMAND_FDS_TIMEOUT, 0);
        return;
      }
    }
  } while (!(read_prg_byte(FDS_DRIVE_STATUS) & 2));
  TCNT1 = 0;
  secs = 0;
  do
  {
    // timeout 15 secs
    if (TCNT1 >= 7812)
    {
      TCNT1 = 0;
      secs++;
      if (secs >= 15)
      {
        write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop
        comm_start(COMMAND_FDS_TIMEOUT, 0);
        return;
      }
    }
  } while (read_prg_byte(FDS_DRIVE_STATUS) & 2);

  // disk info block
  if (block_write_count && (current_block == block_write_ids[current_writing_block]))
  {
    // gap delay while writing = ~28300 bits = (~28300 / 8)bits * ~165cycles = ~583687.5
    uint16_t write_length = write_lengths[current_writing_block];
    if (!write_fds_block(write_data, write_length, FDS_WRITE_GAP_BEFORE_FIRST_BLOCK))
      return;
    write_data += write_length;
    current_writing_block++;
    block_write_count--;
  } else
  {
    // gap delay while reading = ~486974 cycles
    if (!read_fds_block_send(56, (current_block >= block_read_start) && block_read_count, &crc_ok, &end_of_head, 0, FDS_READ_GAP_BEFORE_FIRST_BLOCK))
      return;
  }
  if ((current_block >= block_read_start) && block_read_count)
    block_read_count--;
  current_block++;

  if (crc_ok && !end_of_head && (block_read_count || block_write_count))
  {
    // file amount block
    if (block_write_count && (current_block == block_write_ids[current_writing_block]))
    {
      uint16_t write_length = write_lengths[current_writing_block];
      if (!write_fds_block(write_data, write_length, FDS_WRITE_GAP_BETWEEN_BLOCKS))
        return;
      write_data += write_length;
      current_writing_block++;
      block_write_count--;
    } else
    {
      if (!read_fds_block_send(2, (current_block >= block_read_start) && block_read_count, &crc_ok, &end_of_head, 0, FDS_READ_GAP_BETWEEN_BLOCKS))
        return;
    }
    if ((current_block >= block_read_start) && block_read_count)
      block_read_count--;
    current_block++;
  }

  while (crc_ok && !end_of_head && (block_read_count || block_write_count))
  {
    // file header block
    uint16_t file_size = 0; // size of the next file
    if (block_write_count && (current_block == block_write_ids[current_writing_block]))
    {
      uint16_t write_length = write_lengths[current_writing_block];
      if (!write_fds_block(write_data, write_length, FDS_WRITE_GAP_BETWEEN_BLOCKS))
        return;
      write_data += write_length;
      current_writing_block++;
      block_write_count--;
    } else
    {
      if (!read_fds_block_send(16, (current_block >= block_read_start) && block_read_count, &crc_ok, &end_of_head, &file_size, FDS_READ_GAP_BETWEEN_BLOCKS))
        return;
    }
    if ((current_block >= block_read_start) && block_read_count)
      block_read_count--;
    current_block++;

    if (crc_ok && !end_of_head && (block_read_count || block_write_count))
    {
      // file data block
      if (block_write_count && (current_block == block_write_ids[current_writing_block]))
      {
        uint16_t write_length = write_lengths[current_writing_block];
        if (!write_fds_block(write_data, write_length, FDS_WRITE_GAP_BETWEEN_BLOCKS))
          return;
        write_data += write_length;
        current_writing_block++;
        block_write_count--;
      } else
      {
        if (!read_fds_block_send(file_size + 1, (current_block >= block_read_start) && block_read_count, &crc_ok, &end_of_head, 0, FDS_READ_GAP_BETWEEN_BLOCKS))
          return;
      }
      if ((current_block >= block_read_start) && block_read_count)
        block_read_count--;
      current_block++;
    }
  }

  write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop

  _delay_ms(50);
  if (current_writing_block && !block_write_count && !block_read_count)
  {
    comm_start(COMMAND_FDS_WRITE_DONE, 0);
    return;
  }
  comm_start(COMMAND_FDS_READ_RESULT_END, 0);
}

static void get_mirroring()
{
  comm_start(COMMAND_MIRRORING_RESULT, 4);
  LED_GREEN_ON;
  set_address(0);
  _delay_us(1);
  comm_send_byte((PINE >> 2) & 1);
  set_address(1<<10);
  _delay_us(1);
  comm_send_byte((PINE >> 2) & 1);
  set_address(1<<11);
  _delay_us(1);
  comm_send_byte((PINE >> 2) & 1);
  set_address((1<<10) | (1<<11));
  _delay_us(1);
  comm_send_byte((PINE >> 2) & 1);
  set_address(0);
}

static void init_ports()
{
  DDRB |= (1 << 6) | (1 << 7); // LEDS
  DDRF = 0b10110111; // CPU R/W, IRQ, PPU /RD, PPU /A13, CIRAM /CE, PPU /WR, /ROMSEL, PHI2
  PORTF = 0b11111111; // CPU R/W, IRQ, PPU /RD, PPU /A13, CIRAM /CE, PPU /WR, /ROMSEL, PHI2  
  DDRE &= ~(1<<2); // CIRAM A10
  PORTE |= 1<<2; // CIRAM A10
  MODE_READ;
  set_address(0);
  DDRA = 0xFF; // Address low  
  DDRC = 0xFF; // Address high
  COOLBOY_DDR |= (1<<COOLBOY_RD_PIN) | (1<<COOLBOY_WR_PIN);
  COOLBOY_PORT |= (1<<COOLBOY_RD_PIN) | (1<<COOLBOY_WR_PIN);
}

static void reset_phi2()
{
  LED_RED_ON;
  LED_GREEN_ON;
  PHI2_LOW;
  ROMSEL_HI;
  _delay_ms(100);
  PHI2_HI;
  LED_RED_OFF;
  LED_GREEN_OFF;
}

int main (void)
{
  sei();
  USART_init();
  init_ports();

  LED_RED_OFF;
  LED_GREEN_OFF;  
  
  comm_init();
  comm_start(COMMAND_PRG_STARTED, 0);

  uint32_t address;
  uint32_t length;
  
  unsigned long int t = 0;
  char led_down = 0;
  int led_bright = 0;
  
  while (1)
  {
    // PWM for leds
    TCCR1A |= (1<<COM1C1) | (1<<COM1B1) | (1<<WGM10);
    TCCR1B = 1<<CS10; // no prescaler
    if (t++ >= 10000)
    {
      if (!led_down)
      {
        led_bright++;
        if (led_bright >= 110) led_down = 1;
      } else {
        led_bright--;
        if (!led_bright) led_down = 0;
      }
      if (led_bright >= 100) OCR1B = led_bright - 100;
      if (led_down)
      {
        int led_bright2 = 110-led_bright;
        if (led_bright2 <= 20)
        {
          if (led_bright2 > 10) led_bright2 = 20 - led_bright2;
          OCR1C = led_bright2*2;
        }
      }
      t = 0;
    }
    
    if (comm_recv_done)
    {
      comm_recv_done = 0;
      t = led_down = led_bright = 0;
      // Just timer without PWM for timeouts
      TCCR1A = OCR1B = OCR1C = 0;
      TCCR1B = (1<<CS12) | (1<<CS10); // /1024 prescaler
      
      switch (comm_recv_command)
      {
        case COMMAND_PRG_INIT:
          comm_start(COMMAND_PRG_STARTED, 5);
          comm_send_byte(PROTOCOL_VERSION);
          comm_send_byte((SEND_BUFFER_SIZE - 5) & 0xFF);
          comm_send_byte(((SEND_BUFFER_SIZE - 5) >> 8) & 0xFF);
          comm_send_byte((RECV_BUFFER_SIZE - 5) & 0xFF);
          comm_send_byte(((RECV_BUFFER_SIZE - 5) >> 8) & 0xFF);
          break;
          
        case COMMAND_COOLBOY_READ_REQUEST:
        case COMMAND_PRG_READ_REQUEST:
          address = recv_buffer[0] | ((uint16_t)recv_buffer[1]<<8);
          length = recv_buffer[2] | ((uint16_t)recv_buffer[3]<<8);
          read_prg_send(address, length);
          break;

        case COMMAND_PRG_CRC_READ_REQUEST:
          address = recv_buffer[0] | ((uint16_t)recv_buffer[1]<<8);
          length = recv_buffer[2] | ((uint16_t)recv_buffer[3]<<8);
          read_prg_crc_send(address, length);
          break;

        case COMMAND_PRG_WRITE_REQUEST:
          address = recv_buffer[0] | ((uint16_t)recv_buffer[1]<<8);
          length = recv_buffer[2] | ((uint16_t)recv_buffer[3]<<8);
          write_prg(address, length, (uint8_t*)&recv_buffer[4]);
          comm_start(COMMAND_PRG_WRITE_DONE, 0);
          break;

        case COMMAND_RESET:
          reset_phi2();
          comm_start(COMMAND_RESET_ACK, 0);
          break;
          
        case COMMAND_FLASH_ERASE_SECTOR_REQUEST:
        case COMMAND_COOLBOY_ERASE_SECTOR_REQUEST:
          erase_flash_sector();
          break;

        case COMMAND_FLASH_WRITE_REQUEST:
        case COMMAND_COOLBOY_WRITE_REQUEST:
          address = recv_buffer[0] | ((uint16_t)recv_buffer[1]<<8);
          length = recv_buffer[2] | ((uint16_t)recv_buffer[3]<<8);
          write_flash(address, length, (uint8_t*)&recv_buffer[4]);
          break;
          
        case COMMAND_CHR_INIT:
          comm_start(COMMAND_CHR_STARTED, 0);
          break;
          
        case COMMAND_CHR_READ_REQUEST:
          address = recv_buffer[0] | ((uint16_t)recv_buffer[1]<<8);
          length = recv_buffer[2] | ((uint16_t)recv_buffer[3]<<8);
          read_chr_send(address, length);
          break;

        case COMMAND_CHR_CRC_READ_REQUEST:
          address = recv_buffer[0] | ((uint16_t)recv_buffer[1]<<8);
          length = recv_buffer[2] | ((uint16_t)recv_buffer[3]<<8);
          read_chr_crc_send(address, length);
          break;

        case COMMAND_CHR_WRITE_REQUEST:
          address = recv_buffer[0] | ((uint16_t)recv_buffer[1]<<8);
          length = recv_buffer[2] | ((uint16_t)recv_buffer[3]<<8);
          write_chr(address, length, (uint8_t*)&recv_buffer[4]);
          comm_start(COMMAND_CHR_WRITE_DONE, 0);
          break;

        case COMMAND_FDS_READ_REQUEST:
          fds_transfer(recv_buffer[0], recv_buffer[1], 0, 0, 0, 0);
          break;

        case COMMAND_FDS_WRITE_REQUEST:
          fds_transfer(0, 0, recv_buffer[0], (uint8_t*) &recv_buffer[1], (uint16_t*) &recv_buffer[1 + recv_buffer[0]],
            (uint8_t*) &recv_buffer[1 + recv_buffer[0] + recv_buffer[0] * 2]);
          break;        
        
        case COMMAND_MIRRORING_REQUEST:
          get_mirroring();
          break;
          
        case COMMAND_SET_FLASH_BUFFER_SIZE:
          set_flash_buffer_size(recv_buffer[0] | ((uint16_t) recv_buffer[1] << 8));
          comm_start(COMMAND_SET_VALUE_DONE, 0);
          break;

        case COMMAND_BOOTLOADER:
          cli();
          MCUCSR = 0;
          jump_to_bootloader();
      }

      LED_GREEN_OFF;
      LED_RED_OFF;
    }    
  }
}
