#include <inttypes.h>
#include <avr/io.h>
#include <avr/delay.h>
#include <stdlib.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>


#include "types.h"
#include "lcd.h"
#include "uart.h"
#include "watchdog.h"
#include "eepromaccess.h"


/* The enable pin of the LCD had to be connected to a different port than the
   others. 
   The named bits are ordered to represent the connection of the different pins
   to the port, which enables us to copy the whole structure directly to the 
   appropriate port. */ 
struct LCD_Connector
{
   unsigned rs:1;  // pc0
   unsigned e :1;  // pd5
   unsigned db:4;  // pc2..5
   unsigned   :2;
   static void set( LCD_Connector lcdc )
   {
      ((LCD_Connector&) PORTC) = lcdc;
      if ( lcdc.e == 1 )
         sbi ( PORTD,5 );
      else
         cbi ( PORTD, 5 );
   };

   static const char lines = 2;
};

#ifdef debug
LCD<LCD_Connector>* llcd;
#endif

#include "ds1620.h"

/* The system feature 3 DS1620 chips. Having multiple chips makes the interface 
   class more complex, but requires NO changes on the DS1620 handling template */
class DS1620interface
{
      static char globalNum;
      char num;

      static const char DQ[3] ;
      static const char RST[3] ;
      static const char clk = 7; // PD7;

   protected:
      inline DS1620interface ();

      void setRST( bool rst )
      {
         if ( rst )
            sbi(PORTB, RST[num]);
         else
            cbi(PORTB, RST[num]);
      };
      bool getRST () { return bit_is_set(PINB, RST[num]); };

      void setDQ( bool dq )
      {
         if ( dq )
            sbi(PORTB, DQ[num]);
         else
            cbi(PORTB, DQ[num]);
      };
      bool getDQ () { return bit_is_set(PINB,DQ[num]) > 0; };

      void setReadModeDQ( bool read )
      {
         if ( read ) {
            cbi ( DDRB, DQ[num] );
         } else {
            sbi ( DDRB, DQ[num]);
         }
      };

      void setCLK( bool clk )
      {
         if ( clk )
            sbi(PORTD, 7);
         else
            cbi(PORTD, 7);
      };
};

const char DS1620interface::DQ[3] = { 0, 1, 4 };
const char DS1620interface::RST[3] = { 2, 3, 5} ;
char DS1620interface::globalNum = 0;

DS1620interface::DS1620interface()
{
   num= globalNum++;

   // disable pull-up
   PORTB &= ~( (1 << DQ[0]) + (1 << DQ[1]) + (1 << DQ[2]));
}




//! implements the flow control on the serial port. 
class TCUart: protected UART { 
   public:
      TCUart() : UART( 11 ) {}; // Set the baudrate to 19,200 bps using a 3.6864MHz crystal
      u08  receive();
      void send( u08 data );
      bool dataAvail() { return UART::dataAvail(); };
};

void TCUart :: send( u08 data )
{
   transmitByte( data );
   u08 expect = data ^0xf;
   u08 a = receiveByte();

   if ( a != expect ) {
     #ifdef debug	
      llcd->clear();
      llcd->print("s:");
      llcd->printHex(data);
      llcd->print("g:");
      llcd->printHex(a);
      llcd->print("x:");
      llcd->printHex(expect);
     #endif
      while ( a != expect ); // trigger watchdog reset on confirmation byte mismatch
   }
}

u08 TCUart::receive()
{
   u08 d = receiveByte();
   transmitByte(d^0xf);
   return d;
}




//! the menu page that is currently being displayed
volatile u08 menuPage = 1;


/** signal handler for external interrupt int0
    The rotary encoder used as userinterface triggers the external interrupt
*/    
SIGNAL(SIG_INTERRUPT0)     
{
   static const char upperMenupageLimit = 4;
   static const char lowerMenupageLimit = 1;
   
   static u08 last = 1;

   u08 enc = 0;
   if ( bit_is_set(PIND,2) )
      enc += 1;
   if ( bit_is_set(PIND,4) )
      enc += 2;

   if ( (enc == 2 && last == 1) || (enc == 1 && last == 2) )
      menuPage++;
   else
      if ( (enc == 0 && last == 3)  || ( enc == 3 && last == 0 ) )
         menuPage--;

   if ( menuPage < lowerMenupageLimit )
      menuPage = upperMenupageLimit;
   else
      if ( menuPage > upperMenupageLimit )
         menuPage = lowerMenupageLimit;

   last = enc;

}



inline void enableInterrupts()
{
   GIMSK |= (1<<INT0)  | (1<<INT1);
   MCUCR |= (1<<ISC11) | (1<<ISC00) ; // | (1<<ISC11) | (1<<ISC01);
   sei();                 /* enable interrupts */
}


//! print the 9 Bit temperature values
inline void printTemp ( LCD<LCD_Connector>& lcd, s16 temp )
{
   lcd.printDec( s08(temp>>1) );
   lcd.print('.');
   if ( temp & 1)
      lcd.print( '5' );
   else
      lcd.print( '0' );
   lcd.print(' ');
}




//! the structure of the controllers EEPROM
struct EEPROM {
    struct {
        char max;
        char min;
    } sensorLimit[3];
    char sensorName[3][5];
    union {
        struct {
            char yellowOn;
            char yellowOff;
            char redOn;
            char redOff;
        } threshholds[3];
        char relaisLimit[3][4];
    };
};
static const EEPROM* eeprom = 0;



/**  the core functions of the Temperature Controller:
     setting output ports, which control the fan and LED status */
class TempControl
{
   public:
      enum FanState { Off = 0, Red = 1, Green = 8, Yellow = 9 };
      static const char latchPort = 6;
      static u08 status ;

      inline TempControl();

      inline void check ( char sensorNum, s08 temp );
      s08 max ( char sensorNum ) { return eepromReadByte( eeprom->sensorLimit[sensorNum].max ); };
      s08 min ( char sensorNum ) { return eepromReadByte( eeprom->sensorLimit[sensorNum].min ); };

   private:
      static FanState state[3];
      inline void setFan ( char num, FanState newState );
};

TempControl::FanState TempControl::state[3] = { TempControl::Off,  TempControl::Off, TempControl::Off };
u08 TempControl::status;

TempControl::TempControl()
{
   PORTC = 0;
   status = 0;

   cbi ( PORTD, latchPort );
   _delay_loop_1(50);
   sbi ( PORTD, latchPort );
   _delay_loop_1(50);
   cbi ( PORTD, latchPort );
}

void TempControl::setFan ( char num, FanState newState )
{
   if (  (status & (9 << num)) != newState ) {
      status &= ~(9 << num);
      status |=  newState << num;
      PORTC = status;

      sbi ( PORTD, latchPort );
      _delay_loop_1(50);
      cbi ( PORTD, latchPort );

      state[num] = newState;
   }
}




void TempControl::check ( char sensorNum, s08 temp )
{
   // checking the temperature thresholds for this fan channel
   FanState st = state[sensorNum];
   switch ( st ) {
      case Green:  if ( temp >= eepromReadByte( eeprom->threshholds[sensorNum].yellowOn ))
                      st = Yellow;
                   break;
      case Yellow: if ( temp >= eepromReadByte( eeprom->threshholds[sensorNum].redOn ))
                      st = Red;
                   else
                      if ( temp < eepromReadByte( eeprom->threshholds[sensorNum].yellowOff ))
                        st = Green;
                   break;
      case Red:    if ( temp < eepromReadByte( eeprom->threshholds[sensorNum].redOff ))
                      st = Yellow ;
                   break;
      case Off:    st = Green;
   }
   setFan(sensorNum, st);

   // check and update min & max values
   if ( temp > max(sensorNum) )
      eepromWriteByte ( eeprom->sensorLimit[sensorNum].max, temp );
      
   s08 mini = min(sensorNum);
   if ( temp < mini || mini == -1 )
      eepromWriteByte ( eeprom->sensorLimit[sensorNum].min, temp );
}


int main( void )
{
   DDRB  = 0xff;
   DDRC  = 0xff;
   PORTD = 0xff;
   DDRD  = 0xff - 4 - 8 - 0x10;


   LCD<LCD_Connector> lcd;

  #ifdef debug
   llcd = &lcd;
  #endif

   lcd.print('L');

   TempControl tempControl;
   lcd.print('T');

   DS1620<DS1620interface> ds[3];
   lcd.print('D');

   TCUart uart;

   enableInterrupts();

   u08 lastPage = menuPage;
   lcd.print('R');
   for (;;) {


      // reading temperature from sensors
      for ( char i = 0; i < 3; i++ )
         tempControl.check ( i, ds[i].readTemp8() );



      // updating the LCD

      if ( menuPage != lastPage ) {
         // if LCD page has changed clear display
         lastPage = menuPage;
         lcd.clear();
      }

      // display/update the active page
      if ( menuPage == 1 ) {
         lcd.goToLine( 0 ) ;
         for ( char i = 0; i < 3; i++ )
            for ( char j = 0; j < 5; j++ )
               lcd.print( eepromReadByte ( eeprom->sensorName[i][j] )) ;

         lcd.goToLine( 1 ) ;

         for ( char i = 0; i < 3; i++ )
            printTemp ( lcd, ds[i].readTemp() );

      } else
         if ( menuPage <= 4 ) {
            lcd.goToLine( 0 );
            u08 sens = menuPage - 2;
            lcd.print("Sensor ");
            lcd.printDec(sens);
            lcd.print(": ");
            printTemp ( lcd, ds[sens].readTemp() );
            lcd.goToLine( 1 );
            lcd.print("Max: ");
            lcd.printDec( tempControl.max(sens) );
            lcd.print(" Min: ");
            lcd.printDec( tempControl.min(sens) );
         }


      // listening on the RS232 port
      if ( uart.dataAvail() ) {

         /* enabling and disabling the Watchdog in the constructor / destructor of a
            class allows for a very convenient timeout mechanism in the programming protocoll.
         */   
         WatchDog wd(7);

         u08 data = uart.receive();
         if ( data == 'p' ) {
            data = uart.receive();
            if ( data == '!' ) {
               do {
                  data = uart.receive();
                  switch ( data )  {
                     case 'w': {
                           // writing to EEPROM
                           wd.reset();
                           u08 adr = uart.receive();
                           eepromWriteByte2(adr, uart.receive() );
                           data = 0;
                           break;
                        }
                     case 'r': {
                           // reading from EEPROM
                           wd.reset();
                           uart.send ( eepromReadByte2(uart.receive() ));
                           data = 0;
                           break;
                        }
                     case 't': {
                           // reading temperature
                           wd.reset();
                           uart.send ( ds[uart.receive()].readTemp8() );
                           data = 0;
                           break;
                        }

                     default: data = 'q';

                  }
               } while ( data != 'q' );
            } // data == !
         } // data == p
      } // uart.dataavail
   }
}