/*----------------------------------------------------------------------------
**
**	Lights.c 
**	
** Controls the LED patterns. The LEDs are in three phases with 20 or so
** LEDs in each phase. Each phase is controlled by a full bridge, and there
** are two control lines for each phase, 'Colour' and 'Brightness'.
** 'Colour' is the polarity, which when 0 results in a colour RED, and when 
** 1 results in GREEN. It is connected to a PWM output pin however, so colours
** in between green and red can be generated simply by adjusting the PWM
** ratio. The 'Brightness' pins are also on PWM outputs, and so the 
** brightness can be adjusted also.
**
**--------------------------------------------------------------------------*/
                  
#include <ctype.h>
#include "Onboard.h"
#include "Hardware.h"
#include "Commands.h"
                       
#define NUM_PHASES                  3
#define NUM_SEQUENCES               8
#define MAX_CONTROL_STRING_LENGTH   60
                
// Phase enumertaion
enum
{
    A, B, C
};

// Controlling globals
static char AutoRepeat = FALSE;
static char CurrentSequence = -1;
static char RequiredSequence = -1;
typedef struct
{      
    unsigned char Start;
    unsigned char End;
    unsigned int  Duration;
} TFade;                    

typedef struct
{           
    int     CurrentColour;
    int     CurrentBrightness;                    
    int     SequenceStringIndex;
    int     Delay;
    TFade   Colour;
    TFade   Brightness;
} TControl;

TControl Control[NUM_PHASES] =
{
    {0, 0, 0, 0, {0,0,0}},
    {0, 0, 0, 0, {0,0,0}},
    {0, 0, 0, 0, {0,0,0}}
};

/*----------------------------------------------------------------------------
//
// LIGHT SEQUENCE COMMANDS
//
//  Ccc     -      Set to colour 'cc'
//  Bbb     -      Set brightness to 'bb'
//  Wtt     -      Wait for 'tt' time units.
//  Fbbcctt -      Fade to colour 'cc' and brightness 'bb' in time 
//                 'tt' time units. If 'bb' or 'cc' is '==',
//                 leave same as before.
//
//
// Each instruction is terminated by a space or the end of string. Extra 
// spaces in the sequence strings are ignored. Time units are in 100ms, so
// 32 represents 3.2 seconds.
// Colour values are 00 for green and FF for red. 80 is amber.
// Brightness is 00 for off and FF for fully on.
//                 
----------------------------------------------------------------------------*/
static const char c[3][2][2] = {{"00","00"},{"00","00"},{"00","00"}};

static const char Sequences[NUM_SEQUENCES][NUM_PHASES][MAX_CONTROL_STRING_LENGTH] =
{    
    // Sequence 0
    // Emergeny red flash at ~1.7 Hz. Performed with autorepeat ON.
    {
        {"B00 CFF W03 BFF CFF W03"},    // Phase A
        {"B00 CFF W03 BFF CFF W03"},    // Phase B
        {"B00 CFF W03 BFF CFF W03"}     // Phase C
    },

    // Sequence 1
    // Start off and build up over 1 second to full red, 
    // then repeat for green and amber. Same all phases.
    {
        {"B00 Cff Fffff10 B00 C80 Fff8010 B00 C00 Fff0010"},    // Phase A
        {"B00 Cff Fffff10 B00 C80 Fff8010 B00 C00 Fff0010"},    // Phase B
        {"B00 Cff Fffff10 B00 C80 Fff8010 B00 C00 Fff0010"}     // Phase C
    },
     
    // Sequence 2          
    // Ripple red amber and green around perimiter, all at full brightness.
    // Fade green->amber->red->amber-> etc. 1 cycle takes 2 seconds.
    {
        {"Bff C00 Fff8005 Fffff05 Fff8005 Fff0005"},    // Phase A
        {"Bff C80 Fffff05 Fff8005 Fff0005 Fff8005"},    // Phase A
        {"Bff Cff Fff8005 Fff0005 Fff8005 Fffff05"}     // Phase A
    },

    // Sequence 3
    // "Throbbing" red pulse on all phases. Fade on in 0.3s,
    // fade off in 0.7s, so pulse is at 1Hz.
    {
        {"B00 Cff Fffff03 F00ff07"},    // Phase A
        {"B00 Cff Fffff03 F00ff07"},    // Phase B
        {"B00 Cff Fffff03 F00ff07"}     // Phase C
    },

    // Sequence 4
    // Undefined
    {
        {""},    // Phase A
        {""},    // Phase B
        {""}     // Phase C
    },

    // Sequence 5
    // Undefined
    {
        {""},    // Phase A
        {""},    // Phase B
        {""}     // Phase C
    },

    // Sequence 6
    // Undefined
    {
        {""},    // Phase A
        {""},    // Phase B
        {""}     // Phase C
    },

    // Sequence 7
    // Undefined
    {
        {""},    // Phase A
        {""},    // Phase B
        {""}     // Phase C
    },
};

#define Hex(x)      ((toupper(x) > '9') ? (toupper(x) - 'A' + 10) : (x - '0'))

/*----------------------------------------------------------------------------
//
//  StrToDec
//      
// Convert a pointer to an ASCII hexadecimal 2-digit value to the value. 
//
// Parameters:
//  Ptr             Pointer to string of 6 characters as defined in sequence 
//                  string.
//
// Return Value:
//  -1              Character was '=' meaning same as current value.
//  0-255           Conversion of hexadecimal value.
//
// Modification Record:
//  27-Jul-00   Paul Hills      First version.
-----------------------------------------------------------------------------*/
int StrToDec(char *Ptr)
{   
    if (*Ptr == '=')
        return -1;
    else               
        return (Hex(*Ptr) * 16 + Hex(*(Ptr+1)));
}


/*----------------------------------------------------------------------------
//
//  SetColour
//      
// Sets the phase colour to the requested value, by adjusting the PWM ratio. 
//
// Modification Record:
//  27-Jul-00   Paul Hills      First version.
-----------------------------------------------------------------------------*/
void SetColour(char Phase, unsigned char Colour)
{
    switch (Phase)
    {
        case A: LED_A_COLOUR = Colour;  break;
        case B: LED_B_COLOUR = Colour;  break;
        case C: LED_C_COLOUR = Colour;  break;
    }
}

/*----------------------------------------------------------------------------
//
//  SetBrightness
//      
// Sets the phase brightness to the requested value, by adjusting the PWM ratio. 
//
// Modification Record:
//  27-Jul-00   Paul Hills      First version.
-----------------------------------------------------------------------------*/
void SetBrightness(char Phase, unsigned char Brightness)
{
    switch (Phase)
    {
        case A: LED_A_BRIGHTNESS = Brightness;  break;
        case B: LED_B_BRIGHTNESS = Brightness;  break;
        case C: LED_C_BRIGHTNESS = Brightness;  break;
    }
}

/*----------------------------------------------------------------------------
//
//  ConfigureFade
//      
// Sets the fade parameters to the required values. Note that the brightness
// and colour fade over the same time period.
//
// Modification Record:
//  27-Jul-00   Paul Hills      First version.
-----------------------------------------------------------------------------*/
void ConfigureFade(char Phase, char *Ptr)
{
    int     Duration, DestColour, DestBrightness;
    
    Duration =   (*(Ptr+0) - '0') * 100  +  (*(Ptr+1) - '0') * 10;  // In 10ms units
    DestColour =     StrToDec(Ptr+2);
    DestBrightness = StrToDec(Ptr+0);
    
    // Check for SAME AS CURRENT values
    if (DestColour == -1)
        DestColour = Control[Phase].CurrentColour;
    if (DestBrightness == -1)
        DestBrightness = Control[Phase].CurrentBrightness;
    
    // Set the brightness fade parameters
    Control[Phase].Brightness.Start = Control[Phase].CurrentBrightness;
    Control[Phase].Brightness.End = DestBrightness;
    Control[Phase].Brightness.Duration = Duration;

    // Set the colour fade parameters
    Control[Phase].Colour.Start = Control[Phase].CurrentColour;
    Control[Phase].Colour.End = DestColour;
    Control[Phase].Colour.Duration = Duration;
                                   
    // Set delay for the duration of this fade.
    Control[Phase].Delay = Duration;
}

/*----------------------------------------------------------------------------
//
//  GetInstruction
//      
// Read the next instruction from the sequence string, and place it in the
// destination string.
//
// Modification Record:
//  27-Jul-00   Paul Hills      First version.
-----------------------------------------------------------------------------*/
char GetInstruction(char Phase, char *Dest)
{            
    char *Ptr;
    char Chr;
    
    Ptr = Dest;
    
    // Get past any preceding spaces
    while (Sequences[Phase][CurrentSequence][Control[Phase].SequenceStringIndex] == ' ')
        Control[Phase].SequenceStringIndex++;

    // Now starting at the next instruction...                    
    while (TRUE)
    {
        Chr = Sequences[Phase][CurrentSequence][Control[Phase].SequenceStringIndex];

        // The next space indicates the end of the instruction
        if (Chr == ' ')           
            return;
        
        // Check for end of string
        else if (Chr == 0)
        {
            if (AutoRepeat)
            {  
                // If auto-repeat, start from the beginning of the string again.
                Control[Phase].SequenceStringIndex = 0;
                Ptr = Dest;
            }
            return FALSE;
        }
            
        // Another instruction character
        else
            *Ptr++ = toupper(Chr);
    }                   
    
    return TRUE;
}


/*----------------------------------------------------------------------------
//
//  RunSequence
//      
//  Lighting sequence. This takes the data structures above, and uses them
// to run through a sequence string controlling the lights.
//
// Modification Record:
//  27-Jul-00   Paul Hills      First version.
-----------------------------------------------------------------------------*/
void RunSequence(void)
{                                   
    unsigned char   NextColour;
    unsigned char   NextBrightness;
    char            Inst[20];
    int             Phase;
                             
    //
    // Don't bother if not running a sequence at the moment.
    //
    
    if (CurrentSequence == -1)
        return;
    

    //
    // For each of the phases...
    //
    
    for (Phase=0 ; Phase<NUM_PHASES ; Phase++)
    {                                            
        // If not decrementing through a delay...
        if (Control[Phase].Delay == 0)
        {                             
            //
            // Get next instruction from string and decode it.
            //
                
            if (GetInstruction(Phase, Inst))
            {
                if (Inst[0] == 'C')
                    SetColour(Phase, StrToDec(Inst+1));
                if (Inst[0] == 'B')
                    SetBrightness(Phase, StrToDec(Inst+1));
                if (Inst[0] == 'F')
                    ConfigureFade(Phase, Inst+1);
                if (Inst[0] == 'W')
                    Control[Phase].Delay = (*(Inst+1) - '0') * 100  +  
                                           (*(Inst+2) - '0') * 10;  // In 10ms units.
            }
            else
            {
                // Sequence has terminated
                CurrentSequence = -1;
            }
        }        
        else
        {      
            //
            // Continue executing current instruction - fade or delay.
            // First work out what the next value for colour and brightness will
            // be, based on the fase characteristics.
            //
                                                                                     
            NextColour = Control[Phase].Colour.Start + 
                        (Control[Phase].Colour.End - Control[Phase].Colour.Start) *
                        (Control[Phase].Colour.Duration - Control[Phase].Delay) /
                         Control[Phase].Colour.Duration;
            NextBrightness = Control[Phase].Brightness.Start + 
                            (Control[Phase].Brightness.End - Control[Phase].Brightness.Start) *
                            (Control[Phase].Brightness.Duration - Control[Phase].Delay) /
                             Control[Phase].Brightness.Duration;
            SetColour(Phase, NextColour);
            SetBrightness(Phase, NextBrightness);
            Control[Phase].Delay--;
        }                          
    }
}


/*----------------------------------------------------------------------------
//
//  ProcessLights
//      
//  Interpret commands and trigger light sequences
//  
// Modification Record:
//  27-Jul-00   Paul Hills      First version.
-----------------------------------------------------------------------------*/
void ProcessLights(void)
{                 
    // See if a new sequence is required
    RequiredSequence = SpecialsRequest[LIGHT_SEQUENCE_0] << 0 + 
                       SpecialsRequest[LIGHT_SEQUENCE_1] << 1 +
                       SpecialsRequest[LIGHT_SEQUENCE_2] << 2;

    // Check for reset
    if (SpecialsRequest[RESET_LIGHTS_SEQUENCE])
        RequiredSequence = -1;    
        
    // Change to new light sequence
    if (RequiredSequence != CurrentSequence)
    {                  
        // Start a new sequence
        Control[A].SequenceStringIndex = 0;
        Control[B].SequenceStringIndex = 0;
        Control[C].SequenceStringIndex = 0;
        Control[A].Delay = 0;
        Control[B].Delay = 0;
        Control[C].Delay = 0;    
        CurrentSequence = RequiredSequence;
    }                          
    
    if (SpecialsRequest[LIGHTS_AUTO_REPEAT])
        AutoRepeat = TRUE;
    
    RunSequence();
}