/* bulb - BlinkenArea ultimate logo board
   version 1.0 date 2015-08-15
   Copyright (C) 2015 Martin Müllenhaupt <mm+bulb@netlair.de>
   Copyleft: GNU public license - http://www.gnu.org/copyleft/gpl.html
*/


#include <avr/io.h>
#include <avr/interrupt.h>

#define F_CPU 8000000UL // 8 MHz
#include <util/delay.h>
#include <stdlib.h>

#define NUM_ZEILEN 6
#define NUM_SPALTEN 7
#define NUM_LEDS (NUM_ZEILEN * NUM_SPALTEN)
#define MAX_TIME 32

#define LED(index) frame[updateBufferIndex][index/NUM_SPALTEN][index%NUM_SPALTEN]
#define LED_INDEX_FROM_MANUAL(index) frame[updateBufferIndex][(index-1)/NUM_SPALTEN][(index-1)%NUM_SPALTEN]

/* 0 = off; 255 = max brightness */
uint8_t frame[2][NUM_ZEILEN][NUM_SPALTEN] = {0};

uint8_t displayBufferIndex = 0;
uint8_t updateBufferIndex = 1;

ISR(TIMER1_COMPA_vect)
{
  static uint8_t zeile = 0;
  static uint8_t time = 0;

  ++time;

  if (time == MAX_TIME)
  {
    ++zeile;
    zeile %= NUM_ZEILEN;
    PORTB = 0;
    PORTD = ~(1 << zeile);
    time = 0;
  }

  uint8_t row_data = 0;
  for(uint8_t spalte = 0; spalte < NUM_SPALTEN; ++spalte)
  {
    if (frame[displayBufferIndex][zeile][spalte] > time)
    {
      row_data |= (1 << spalte);
    }
  }
  PORTB = row_data;
}

void swapBuffers()
{
  updateBufferIndex = ( updateBufferIndex + 1 ) % 2;
  displayBufferIndex = ( displayBufferIndex + 1 ) % 2;
}

void wurm(int8_t length, uint8_t reverse)
{
  for (int8_t frontPosition = reverse ? NUM_LEDS : 0;
       reverse ? frontPosition >= - length - 1 : frontPosition < NUM_LEDS + length;
       reverse ? --frontPosition : ++frontPosition)
  {
    for(int8_t pos = 0; pos < NUM_LEDS; ++pos)
    {
      uint8_t value = 0;
      if (!reverse)
      {
        if (pos <= frontPosition)
        {
          int8_t dist = frontPosition - pos;
          if (dist < length)
          {
            value = MAX_TIME / (1 + 4 * dist);
          }
        }
      }
      else
      {
        if (pos >= frontPosition)
        {
          int8_t dist = pos - frontPosition;
          if (dist < length)
          {
            value = MAX_TIME / (1 + 4 * dist);
          }
        }
      }
      LED(pos) = value;
    }
    swapBuffers();
    _delay_us(20000);
  }
}

#define FIRST_SCANLINE 0
#define LAST_SCANLINE 7

void single_scanline_hori(uint8_t scanposition, uint8_t value)
{
  switch(scanposition)
  {
    case 0:
      for (uint8_t i = 3; i <= 10; ++i)
      {
        LED_INDEX_FROM_MANUAL(i) = value;
      }
      break;
    case 1:
      LED_INDEX_FROM_MANUAL(2) = value;
      LED_INDEX_FROM_MANUAL(11) = value;
      LED_INDEX_FROM_MANUAL(32) = value;
      break;
    case 2:
      LED_INDEX_FROM_MANUAL(1) = value;
      LED_INDEX_FROM_MANUAL(31) = value;
      LED_INDEX_FROM_MANUAL(33) = value;
      LED_INDEX_FROM_MANUAL(12) = value;
      break;
    case 3:
      LED_INDEX_FROM_MANUAL(28) = value;
      LED_INDEX_FROM_MANUAL(29) = value;
      LED_INDEX_FROM_MANUAL(30) = value;
      LED_INDEX_FROM_MANUAL(34) = value;
      LED_INDEX_FROM_MANUAL(13) = value;
      LED_INDEX_FROM_MANUAL(42) = value;
      break;
    case 4:
      LED_INDEX_FROM_MANUAL(27) = value;
      LED_INDEX_FROM_MANUAL(41) = value;
      LED_INDEX_FROM_MANUAL(40) = value;
      LED_INDEX_FROM_MANUAL(35) = value;
      LED_INDEX_FROM_MANUAL(14) = value;
      break;
    case 5:
      LED_INDEX_FROM_MANUAL(26) = value;
      LED_INDEX_FROM_MANUAL(39) = value;
      LED_INDEX_FROM_MANUAL(36) = value;
      LED_INDEX_FROM_MANUAL(37) = value;
      LED_INDEX_FROM_MANUAL(15) = value;
      break;
    case 6:
      LED_INDEX_FROM_MANUAL(25) = value;
      LED_INDEX_FROM_MANUAL(38) = value;
      LED_INDEX_FROM_MANUAL(16) = value;
      break;
    case 7:
      for (uint8_t i = 17; i <= 24; ++i)
      {
        LED_INDEX_FROM_MANUAL(i) = value;
      }
      break;
  }
}

void single_scanline_vert(uint8_t scanposition, uint8_t value)
{
  switch(scanposition)
  {
    case 0:
      for (uint8_t i = 10; i <= 17; ++i)
      {
        LED_INDEX_FROM_MANUAL(i) = value;
      }
      break;
    case 1:
      LED_INDEX_FROM_MANUAL(9) = value;
      LED_INDEX_FROM_MANUAL(34) = value;
      LED_INDEX_FROM_MANUAL(35) = value;
      LED_INDEX_FROM_MANUAL(36) = value;
      LED_INDEX_FROM_MANUAL(18) = value;
      break;
    case 2:
      LED_INDEX_FROM_MANUAL(8) = value;
      LED_INDEX_FROM_MANUAL(33) = value;
      LED_INDEX_FROM_MANUAL(42) = value;
      LED_INDEX_FROM_MANUAL(37) = value;
      LED_INDEX_FROM_MANUAL(19) = value;
      break;
    case 3:
      LED_INDEX_FROM_MANUAL(7) = value;
      LED_INDEX_FROM_MANUAL(32) = value;
      LED_INDEX_FROM_MANUAL(38) = value;
      LED_INDEX_FROM_MANUAL(20) = value;
      break;
    case 4:
      LED_INDEX_FROM_MANUAL(6) = value;
      LED_INDEX_FROM_MANUAL(31) = value;
      LED_INDEX_FROM_MANUAL(39) = value;
      LED_INDEX_FROM_MANUAL(21) = value;
      break;
    case 5:
      LED_INDEX_FROM_MANUAL(5) = value;
      LED_INDEX_FROM_MANUAL(30) = value;
      LED_INDEX_FROM_MANUAL(40) = value;
      LED_INDEX_FROM_MANUAL(22) = value;
      break;
    case 6:
      LED_INDEX_FROM_MANUAL(4) = value;
      LED_INDEX_FROM_MANUAL(29) = value;
      LED_INDEX_FROM_MANUAL(41) = value;
      LED_INDEX_FROM_MANUAL(23) = value;
      break;
    case 7:
      LED_INDEX_FROM_MANUAL(1) = value;
      LED_INDEX_FROM_MANUAL(2) = value;
      LED_INDEX_FROM_MANUAL(3) = value;
      for (uint8_t i = 24; i <= 28; ++i)
      {
        LED_INDEX_FROM_MANUAL(i) = value;
      }
      break;
  }
}

void scanline(uint8_t length, uint8_t horizontal, uint8_t reverse)
{
  int8_t firstCenter = FIRST_SCANLINE - length + 1;
  int8_t lastCenter = LAST_SCANLINE + length - 1;
  for (int8_t centerPosition = reverse ? lastCenter : firstCenter;
       reverse ? centerPosition >= firstCenter : centerPosition <= lastCenter;
       reverse ? -- centerPosition : ++centerPosition)
  {
    for(int8_t linePos = FIRST_SCANLINE; linePos <= LAST_SCANLINE; ++linePos)
    {
      int8_t dist = abs(centerPosition - linePos);
      if (horizontal)
      {
        if (dist < length)
        {
          single_scanline_hori(linePos, MAX_TIME / (4*dist + 1));
        }
        else
        {
          single_scanline_hori(linePos, 0);
        }
      }
      else
      {
        if (dist < length)
        {
          single_scanline_vert(linePos, MAX_TIME / (4*dist + 1));
        }
        else
        {
          single_scanline_vert(linePos, 0);
        }

      }
    }

    swapBuffers();
    _delay_us(30000);
  }

}

void blink()
{
  for(int8_t pos = 0; pos < NUM_LEDS; ++pos)
  {
    LED(pos) = 0;
  }
  swapBuffers();
  _delay_us(100000);

  for(int8_t pos = 0; pos < NUM_LEDS; ++pos)
  {
    LED(pos) = MAX_TIME;
  }
  swapBuffers();
  _delay_us(100000);
}

#define ROT_SIZE 5
void dimRotation()
{
  for(uint8_t position = 0; position < ROT_SIZE-1; ++position)
  {
    for(uint8_t led = 0; led < NUM_LEDS; ++led)
    {
      uint8_t this_led = ((led + position) % NUM_LEDS);

      LED(this_led) = (led%(ROT_SIZE-1)) * (MAX_TIME/(ROT_SIZE-1));
    }
    swapBuffers();
    _delay_us(100000);
  }
}

int main(void)
{
    // PA[01] to output, low
    PORTA = 0;
    DDRA = 0x03;

    // PB[0-6] to output, low - PB7 to input, pull-up enabled
    DDRB = 0x7F;
    PORTB = 0x80;

    // PD[0-5] to output, high - PD6 to input, pull-up enabled
    DDRD = 0x3F;
    PORTD = 0x7F;

    // set clock to 8 MHz
    CLKPR = (1<<CLKPCE);
    CLKPR = 0;

    TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode

    TIMSK |= (1 << OCIE1A); // Enable CTC interrupt

    sei(); //  Enable global interrupts

    OCR1A   = 8; // Set CTC compare value to 1Hz at 1MHz AVR clock, with a prescaler of 64

    TCCR1B |= ((1 << CS10) | (1 << CS11)); // Start timer at Fcpu/64

    for(;;)
    {
      for (uint8_t length = 1; length <= 8; length += 2)
      {
        wurm(length, 0);
        wurm(length, 1);
      }


      for (int8_t horizontal = 1; horizontal >= 0; --horizontal)
      {
        for (uint8_t length = 1; length <= 6; ++length)
        {
          for (uint8_t reverse = 0; reverse <= 1; ++reverse)
          {
            scanline(length, horizontal, reverse);
          }
        }
      }

      blink();
      blink();
      blink();
      for (uint8_t i = 1; i <= 8; ++i)
      {
        dimRotation();
      }
    }
}
