/* flaneth - flash and ethernet - dartboard mod
 * version 0.1 date 2008-11-09
 * Copyright (C) 2007-2008 Stefan Schuermans <stefan@schuermans.info>
 * Copyleft: GNU public license V2 - http://www.gnu.org/copyleft/gpl.html
 * a BlinkenArea project - http://www.blinkenarea.org/
 */

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

#include "dart.h"
#include "debug.h"
#include "macros.h"
#include "nethelp.h"
#include "udp.h"

// dartboard connection
//  - 7 row pins connected via shottky diode (low passes to dartboard)
//  - 12 column pins connected directly

// IO-Pins
// PB0: col10
// PB2: col9
// PB3: col8
// PC[0-7]: col[0-7]
// PF[0-6]: row[0-6]
// PF7: col11

// constants
#define DartRows (7)
#define DartCols (12)
#define DartFields (DartRows * DartCols)
#define DartDisableTicks (5)
#define DartQueueSize (16)

// if waiting for a field to be hit
volatile unsigned char DartActive = 1;

// current row being read
volatile unsigned char DartRow = DartRows;
volatile unsigned char DartRowBits = 1 << DartRows; // always 1 << DartRow

// field that has been hit
volatile unsigned char DartField = DartFields;

// ticks dartboard is disabled
//  - used after field has been hit to disable board for some time
unsigned char DartDisable = 0;

// destination to send dart UDP packets to
unsigned char DartIp[4];
unsigned short DartPort = 0;

// dart queue
unsigned char DartQueueStart = 0;
unsigned char DartQueueEnd = 0;
unsigned char DartQueue[DartQueueSize];

// 128us interrupt (timer 2 overflow)
SIGNAL( SIG_OVERFLOW2 )
{
  unsigned char b, c, f, col;

  // do nothing if not waiting for a field to be hit
  if( ! DartActive )
    return;

  // read inputs
  b = ~PINB & 0x0D;
  c = ~PINC;
  f = ~PINF & 0x80;

  // check if any input is active
  //  - make common case (no input active) fast
  if( b | c | f ) {

    // no column
    col = DartCols;

    // find active column (any active column if multiple active)
    if( b ) {
      if( b & 0x08 ) col = 8;
      else if( b & 0x04 ) col = 9;
      else if( b & 0x01 ) col = 10;
    } else if( c ) {
      if( c & 0x0F ) {
        if( c & 0x01 ) col = 0;
        else if( c & 0x02 ) col = 1;
        else if( c & 0x04 ) col = 2;
        else if( c & 0x08 ) col = 3;
      } else if( c & 0xF0 ) {
        if( c & 0x10 ) col = 4;
        else if( c & 0x20 ) col = 5;
        else if( c & 0x40 ) col = 6;
        else if( c & 0x80 ) col = 7;
      }
    } else if( f ) {
      if( f & 0x80 ) col = 11;
    }

    // active column found
    if( col < DartCols ) {
      // save field number
      DartField = DartRow * DartCols + col;
      // do not wait for a field to be hit any more
      DartActive = 0;
    }

  } // if( b | c | f )

  // next row
  DartRow++;
  DartRowBits <<= 1;
  if( DartRow >= DartRows ) {
    DartRow = 0;
    DartRowBits = 0x01;
  }

  // select new row
  PORTF = 0xFF & ~DartRowBits;
}

// initialize
void DartInit( void ) // (extern)
{
  // col to input with pull-up
  // row to output, high
  PORTB |= 0x0D;
  DDRB &= ~0x0D;
  PORTC = 0xFF;
  DDRC = 0x00;
  PORTF = 0xFF;
  DDRF = 0x7F;

  // configure timer 2 to 128us interval
  TCCR2 = 0<<FOC2 | 0<<WGM21 | 0<<WGM20 | // normal mode
          0<<COM21 | 0<<COM20 | // no waveform generation
          0<<CS22 | 1<<CS21 | 0<<CS20; // 1/8 of sysclock (16MHz) -> overflow every 128us

  // enable timer 2 overflow interrupt
  TIMSK |= 1<<TOIE2;
}

// convert field number to meaning
void DartField2Meaning( unsigned char field, unsigned char * factor,
                                             unsigned char * value )
{
  switch( field ) {
    case 0: *factor = 3; *value = 13; break;
    case 1: *factor = 1; *value = 18; break;
    case 2: *factor = 1; *value = 10; break;
    case 3: *factor = 1; *value = 15; break;
    case 4: *factor = 2; *value = 15; break;
    case 5: *factor = 3; *value = 5; break;
    case 6: *factor = 2; *value = 2; break;
    case 7: *factor = 1; *value = 2; break;
    case 8: *factor = 1; *value = 17; break;
    case 9: *factor = 1; *value = 3; break;
    case 10: *factor = 1; *value = 8; break;
    case 11: *factor = 3; *value = 7; break;
    case 12: *factor = 1; *value = 4; break;
    case 13: *factor = 3; *value = 6; break;
    case 14: *factor = 3; *value = 10; break;
    case 15: *factor = 3; *value = 15; break;
    case 16: *factor = 0; *value = 0; break;
    case 17: *factor = 1; *value = 5; break;
    case 18: *factor = 0; *value = 0; break;
    case 19: *factor = 1; *value = 2; break;
    case 20: *factor = 1; *value = 17; break;
    case 21: *factor = 1; *value = 3; break;
    case 22: *factor = 1; *value = 19; break;
    case 23: *factor = 1; *value = 7; break;
    case 24: *factor = 1; *value = 13; break;
    case 25: *factor = 1; *value = 6; break;
    case 26: *factor = 1; *value = 10; break;
    case 27: *factor = 1; *value = 15; break;
    case 28: *factor = 2; *value = 25; break;
    case 29: *factor = 1; *value = 12; break;
    case 30: *factor = 1; *value = 25; break;
    case 31: *factor = 3; *value = 2; break;
    case 32: *factor = 3; *value = 17; break;
    case 33: *factor = 3; *value = 3; break;
    case 34: *factor = 3; *value = 19; break;
    case 35: *factor = 1; *value = 16; break;
    case 36: *factor = 3; *value = 4; break;
    case 37: *factor = 1; *value = 6; break;
    case 38: *factor = 1; *value = 1; break;
    case 39: *factor = 1; *value = 20; break;
    case 40: *factor = 2; *value = 10; break;
    case 41: *factor = 3; *value = 12; break;
    case 42: *factor = 2; *value = 17; break;
    case 43: *factor = 1; *value = 9; break;
    case 44: *factor = 1; *value = 14; break;
    case 45: *factor = 1; *value = 11; break;
    case 46: *factor = 1; *value = 19; break;
    case 47: *factor = 3; *value = 16; break;
    case 48: *factor = 1; *value = 13; break;
    case 49: *factor = 3; *value = 18; break;
    case 50: *factor = 3; *value = 1; break;
    case 51: *factor = 3; *value = 20; break;
    case 52: *factor = 2; *value = 6; break;
    case 53: *factor = 1; *value = 5; break;
    case 54: *factor = 2; *value = 3; break;
    case 55: *factor = 3; *value = 9; break;
    case 56: *factor = 3; *value = 14; break;
    case 57: *factor = 3; *value = 11; break;
    case 58: *factor = 3; *value = 8; break;
    case 59: *factor = 1; *value = 7; break;
    case 60: *factor = 1; *value = 4; break;
    case 61: *factor = 1; *value = 18; break;
    case 62: *factor = 1; *value = 1; break;
    case 63: *factor = 1; *value = 20; break;
    case 64: *factor = 2; *value = 13; break;
    case 65: *factor = 1; *value = 12; break;
    case 66: *factor = 2; *value = 19; break;
    case 67: *factor = 1; *value = 9; break;
    case 68: *factor = 1; *value = 14; break;
    case 69: *factor = 1; *value = 11; break;
    case 70: *factor = 1; *value = 8; break;
    case 71: *factor = 1; *value = 16; break;
    case 72: *factor = 2; *value = 4; break;
    case 73: *factor = 2; *value = 18; break;
    case 74: *factor = 2; *value = 1; break;
    case 75: *factor = 2; *value = 20; break;
    case 76: *factor = 2; *value = 5; break;
    case 77: *factor = 2; *value = 12; break;
    case 78: *factor = 2; *value = 7; break;
    case 79: *factor = 2; *value = 9; break;
    case 80: *factor = 2; *value = 14; break;
    case 81: *factor = 2; *value = 11; break;
    case 82: *factor = 2; *value = 8; break;
    case 83: *factor = 2; *value = 16; break;
    default: *factor = 0; *value = 0; break;
  }
}

// task procedure
void DartTask( void ) // (extern)
{
  unsigned char factor, value, len, idx;
  struct {
    struct UdpPacket udp;
    char text[6];
  } data;

  // a field has been hit
  if( DartField < DartFields ) {

    // convert field number to meaning
    DartField2Meaning( DartField, &factor, &value );
    if( factor <= 0 || value <= 0 ) {
      debug_dart_printf( "field %u hit", DartField );
    } else {
      debug_dart_printf( "%ux%u (field %u) hit", factor, value, DartField );

      // put into queue
      idx = DartQueueEnd + 1;
      if( idx >= DartQueueSize )
        idx = 0;
      if( idx != DartQueueStart ) {
        DartQueue[DartQueueEnd] = DartField;
        DartQueueEnd = idx;
      }

      // UDP packets have been requested
      if( DartPort != 0 ) {
        len = 0;
        data.text[len++] = factor + '0';
        data.text[len++] = 'x';
        if( value >= 10 )
          data.text[len++] = value / 10 + '0';
        data.text[len++] = value % 10 + '0';
        data.text[len++] = '\r';
        data.text[len++] = '\n';
        // send UDP packet
        data.udp.UdpHdr.SrcPort = htons( 501 );
        data.udp.UdpHdr.DestPort = htons( DartPort );
        ip_cpy( data.udp.IpHdr.Dest, DartIp );
        UdpSend( (unsigned char *)&data,
                 sizeof( data ) - sizeof( data.text) + len );
      }

    }

    // reset field
    DartField = DartFields;
    // disable dart board for a short time
    DartDisable = DartDisableTicks;
  }
}

// tick procedure - call every 200ms
void DartTick200( void ) // (extern)
{
  // dart board is disabled
  if( DartDisable > 0 ) {
    // count down ticks
    DartDisable--;
    // disable period elapsed
    if( DartDisable <= 0 ) {
      // switch dart board to active
      DartActive = 1;
    }
  }
}

// a dart request packet has been received
void DartRequest( unsigned char ip[4], unsigned short port ) // (extern)
{
  // save new destination
  ip_cpy( DartIp, ip );
  DartPort = port;
}

// poll for dart event
void DartPoll( unsigned char * factor, unsigned char * value ) // (extern)
{
  unsigned char field, fac, val;

  // dart queue empty
  if( DartQueueStart == DartQueueEnd ) {
    *factor = 0;
    *value = 0;
    return;
  }

  // get entry from queue
  field = DartQueue[DartQueueStart];
  DartQueueStart++;
  if( DartQueueStart >= DartQueueSize )
    DartQueueStart = 0;

  // convert field number to meaning
  DartField2Meaning( field, &fac, &val );
  if( factor <= 0 || value <= 0 ) {
    debug_dart_printf( "from queue: field %u", field );
  } else {
    debug_dart_printf( "from queue: %ux%u (field %u)", fac, val, field );
  }

  *factor = fac;
  *value = val;
}

