/*
 * UVLedTimer.c
 *
 * Created: 2011-7-16 19:41:51
 * Created by: kaz
 * $Author: kaz $
 * $Date: 2011-07-23 17:35:25 +0200 (Sat, 23 Jul 2011) $
 * $Rev: 38 $
 * $Id: UVLedTimer.c 38 2011-07-23 15:35:25Z kaz $
 */

#include <stdlib.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <util/delay.h>

#define KEYREPEATTICKCOUNTDIV 8 /* must be 2^x */
#define ONESECTICKCOUNT 56 /* should be n*KEYREPEATTICKCOUNTDIV */
#define DIGITTIMEINMS (1000.0/ONESECTICKCOUNT/4.0)

#define OUT_PORT  PORTD
#define INPUTANDSEVENSEG_PORT  PORTB

#define SEVENSEG_DIG1_PIN  5
#define SEVENSEG_DIG2_PIN  4
#define SEVENSEG_DIG3_PIN  3
#define SEVENSEG_DIG4_PIN  2
#define UVDRIVER_PIN   0

#define SEVENSEG_A_PIN  0
#define SEVENSEG_B_PIN  1
#define SEVENSEG_C_PIN  2
#define SEVENSEG_D_PIN  3
#define SEVENSEG_E_PIN  4
#define SEVENSEG_F_PIN  5
#define SEVENSEG_G_PIN  6
#define SEVENSEG_DP_PIN  7
#define KEY_ST_PIN  0
#define KEY_INC_PIN 1
#define KEY_DEC_PIN 2
#define KEY_PAUSE_PIN 3

#define DDR(x) (*(&x - 1))  /* data direction register of port x */
#define PIN(x) (*(&x - 2))  /* input register of port x */

//       a
//     ------
//    |      |
//  f |      | b
//    |  g   |
//     ------
//    |      |
//  e |      | c
//    |      |
//     ------
//       d


static const char segments[10] PROGMEM  =
{
    _BV(SEVENSEG_A_PIN)|_BV(SEVENSEG_B_PIN)|_BV(SEVENSEG_C_PIN)|_BV(SEVENSEG_D_PIN)|_BV(SEVENSEG_E_PIN)|_BV(SEVENSEG_F_PIN)                    , // 0
                        _BV(SEVENSEG_B_PIN)|_BV(SEVENSEG_C_PIN)                                                                                , // 1
    _BV(SEVENSEG_A_PIN)|_BV(SEVENSEG_B_PIN)|                    _BV(SEVENSEG_D_PIN)|_BV(SEVENSEG_E_PIN)|                    _BV(SEVENSEG_G_PIN), // 2
    _BV(SEVENSEG_A_PIN)|_BV(SEVENSEG_B_PIN)|_BV(SEVENSEG_C_PIN)|_BV(SEVENSEG_D_PIN)|                                        _BV(SEVENSEG_G_PIN), // 3
                        _BV(SEVENSEG_B_PIN)|_BV(SEVENSEG_C_PIN)|                                        _BV(SEVENSEG_F_PIN)|_BV(SEVENSEG_G_PIN), // 4
    _BV(SEVENSEG_A_PIN)|                    _BV(SEVENSEG_C_PIN)|_BV(SEVENSEG_D_PIN)|                    _BV(SEVENSEG_F_PIN)|_BV(SEVENSEG_G_PIN), // 5
    _BV(SEVENSEG_A_PIN)|                    _BV(SEVENSEG_C_PIN)|_BV(SEVENSEG_D_PIN)|_BV(SEVENSEG_E_PIN)|_BV(SEVENSEG_F_PIN)|_BV(SEVENSEG_G_PIN), // 6
    _BV(SEVENSEG_A_PIN)|_BV(SEVENSEG_B_PIN)|_BV(SEVENSEG_C_PIN)                                                                                , // 7
    _BV(SEVENSEG_A_PIN)|_BV(SEVENSEG_B_PIN)|_BV(SEVENSEG_C_PIN)|_BV(SEVENSEG_D_PIN)|_BV(SEVENSEG_E_PIN)|_BV(SEVENSEG_F_PIN)|_BV(SEVENSEG_G_PIN), // 8
    _BV(SEVENSEG_A_PIN)|_BV(SEVENSEG_B_PIN)|_BV(SEVENSEG_C_PIN)|_BV(SEVENSEG_D_PIN)|                    _BV(SEVENSEG_F_PIN)|_BV(SEVENSEG_G_PIN), // 9
};

typedef struct
{
    char RepeatCount: 7;
    char IsPressed: 1;
} TKeyState;



unsigned int remainSaved EEMEM;

TKeyState startStopKey;
TKeyState incKey;
TKeyState decKey;
TKeyState pauseKey;
unsigned int remain; // in sec
char isActive;
char isPaused;
unsigned int ticksAfterLastKeyEvent;

ISR(TIMER1_COMPA_vect)
{
    if (remain > 0)
    {
        remain--;
    }
}

int GetRemain()
{
    int value;
    ATOMIC_BLOCK(ATOMIC_FORCEON)
    {
        value = remain;
    }
    return value;
}

void IncrementRemain(char value)
{
    ATOMIC_BLOCK(ATOMIC_FORCEON)
    {
        remain += value;
        if (remain >= 60 * 99)
            remain = 60 * 100 - 1;
    }
}

void DecrementRemain(char value)
{
    ATOMIC_BLOCK(ATOMIC_FORCEON)
    {
        if (remain >= value)
            remain -= value;
        else
            remain = 0;
    }
}

void ResetRemain()
{
    ATOMIC_BLOCK(ATOMIC_FORCEON)
    {
        remain = 0;
    }
}

void UpdateKeyState(TKeyState* keyState, char state)
{
    if (state)
    {
        if (keyState->RepeatCount == 0)
        {
            keyState->IsPressed = 1;
            keyState->RepeatCount = 1;
        }
        else
        {
            keyState->IsPressed = 0;
        }
    }
    else
    {
        keyState->RepeatCount = 0;
        keyState->IsPressed = 0;
    }
}

void UpdateKeyStateRepeat(TKeyState* keyState, char state)
{
    if (state)
    {
        if (keyState->RepeatCount == 0)
        {
            keyState->IsPressed = 1;
            keyState->RepeatCount = 1;
        }
        else
        {
            keyState->RepeatCount++;
            if (keyState->RepeatCount >= ONESECTICKCOUNT && keyState->RepeatCount % KEYREPEATTICKCOUNTDIV == 0) // apprx. wait for ~1s, than ~7 keystrokes in every sec
            {
                keyState->IsPressed = 1;
            }
            else if (keyState->RepeatCount == 127)
            {
                keyState->RepeatCount = ONESECTICKCOUNT - KEYREPEATTICKCOUNTDIV + (127 % KEYREPEATTICKCOUNTDIV);
            }
            else
            {
                keyState->IsPressed = 0;
            }
        }
    }
    else
    {
        keyState->RepeatCount = 0;
        keyState->IsPressed = 0;
    }
}

void ReadKeyStates(char isStandby)
{
    UpdateKeyState(&startStopKey, bit_is_clear(PIN(INPUTANDSEVENSEG_PORT), KEY_ST_PIN));
    UpdateKeyStateRepeat(&incKey, bit_is_clear(PIN(INPUTANDSEVENSEG_PORT), KEY_INC_PIN));
    UpdateKeyStateRepeat(&decKey, bit_is_clear(PIN(INPUTANDSEVENSEG_PORT), KEY_DEC_PIN));
    UpdateKeyState(&pauseKey, bit_is_clear(PIN(INPUTANDSEVENSEG_PORT), KEY_PAUSE_PIN));
    if (bit_is_clear(PIN(INPUTANDSEVENSEG_PORT), KEY_ST_PIN) 
        || bit_is_clear(PIN(INPUTANDSEVENSEG_PORT), KEY_INC_PIN)
        || bit_is_clear(PIN(INPUTANDSEVENSEG_PORT), KEY_DEC_PIN)
        || bit_is_clear(PIN(INPUTANDSEVENSEG_PORT), KEY_PAUSE_PIN))
    {
        ticksAfterLastKeyEvent = 0;
        if (isStandby)
        {
            startStopKey.IsPressed = 0;
            incKey.IsPressed = 0;
            decKey.IsPressed = 0;
            pauseKey.IsPressed = 0;
        }
    }
    else if (ticksAfterLastKeyEvent < 65530)
    {
        ticksAfterLastKeyEvent++;
    }
}

void WriteDigit(char value, char pin)
{
    INPUTANDSEVENSEG_PORT = ~value;
    DDR(INPUTANDSEVENSEG_PORT) = 0xFF; // output
    OUT_PORT &= ~pin; // activate 4th (last) digit
    _delay_ms(DIGITTIMEINMS);
    OUT_PORT |= pin; // inactivate 4th (last) digit
    DDR(INPUTANDSEVENSEG_PORT) = 0; // input
    INPUTANDSEVENSEG_PORT = 0xFF; // activate all pull-up resistors
}

__inline char GetSegmentValue(int value)
{
	return pgm_read_byte(&segments[value]);
}

void WriteRemain()
{
    div_t dm = div(GetRemain(), 10);
    WriteDigit(GetSegmentValue(dm.rem), _BV(SEVENSEG_DIG4_PIN));
    dm = div(dm.quot, 6);
    WriteDigit(GetSegmentValue(dm.rem), _BV(SEVENSEG_DIG3_PIN));
    dm = div(dm.quot, 10);
    WriteDigit(GetSegmentValue(dm.rem) | _BV(SEVENSEG_DP_PIN), _BV(SEVENSEG_DIG2_PIN));
    dm = div(dm.quot, 10);
    WriteDigit(GetSegmentValue(dm.rem), _BV(SEVENSEG_DIG1_PIN));
}

void WriteStandbyDots()
{
    char digit = (ticksAfterLastKeyEvent / (ONESECTICKCOUNT / 2)) % 6;
    WriteDigit(digit == 3                  ? _BV(SEVENSEG_DP_PIN) : 0, _BV(SEVENSEG_DIG4_PIN));
    WriteDigit(digit == 2 || digit == 4    ? _BV(SEVENSEG_DP_PIN) : 0, _BV(SEVENSEG_DIG3_PIN));
    WriteDigit(digit == 1 || digit == 5    ? _BV(SEVENSEG_DP_PIN) : 0, _BV(SEVENSEG_DIG2_PIN));
    WriteDigit(digit == 0                  ? _BV(SEVENSEG_DP_PIN) : 0, _BV(SEVENSEG_DIG1_PIN));
}

void LoadRemain()
{
    remain = (int)eeprom_read_word((unsigned int*)&remainSaved);
    if (remain >= 100 * 60)
    {
        remain = 5 * 60;
    }
}

void SaveRemain()
{
    eeprom_write_word((unsigned int*)&remainSaved, (unsigned int)GetRemain());
}

void StartTimerAndUV()
{
    TCCR1B |= _BV(CS11); // start counter with F_CPU/8 prescale
    OUT_PORT |= _BV(UVDRIVER_PIN); // switch on
}

void StopTimerAndUV()
{
    OUT_PORT &= ~_BV(UVDRIVER_PIN); // switch off
    TCCR1B &= ~(_BV(CS11)|_BV(CS10)|_BV(CS12)); // stop counter
}

void InitPorts()
{
    DDR(OUT_PORT) = 0xFF; // set port as output
    OUT_PORT = _BV(SEVENSEG_DIG1_PIN)|_BV(SEVENSEG_DIG2_PIN)|_BV(SEVENSEG_DIG3_PIN)|_BV(SEVENSEG_DIG4_PIN); // all are high => off, others are low => off
    DDR(INPUTANDSEVENSEG_PORT) = 0; // set port as input
    INPUTANDSEVENSEG_PORT = 0xFF; // activate pull-ups
}

void InitTimer()
{
    sei();

    TCCR1B |= _BV(WGM12); // CTC
    TIMSK |= _BV(OCIE1A); // Enable CTC interrupt
    OCR1A = (F_CPU / 8L) - 1; // 1 Hz => 1 tick / 1 sec
}

int main(void)
{
    InitTimer();

    InitPorts();

    LoadRemain();
    isPaused = isActive = 0;
    ticksAfterLastKeyEvent = 0;

    while (1)
    {
        if (isActive)
        {
            ticksAfterLastKeyEvent = 0;
        }
        if (isActive || ticksAfterLastKeyEvent < ONESECTICKCOUNT * 60)
        {
            WriteRemain();
            ReadKeyStates(0);
        }
        else
        {
            WriteStandbyDots();
            ReadKeyStates(1);
        }

        if (isActive && !isPaused && GetRemain() == 0)
        {
            StopTimerAndUV();
            isActive = 0;
            LoadRemain();
        }

        if (startStopKey.IsPressed)
        {
            if (!isActive)
            {
                StartTimerAndUV();
            }
            else
            {
                StopTimerAndUV();
                LoadRemain();
            }
            isActive = !isActive;
            isPaused = 0;
        }
        else if (pauseKey.IsPressed)
        {
            if (isActive)
            {
                if (isPaused)
                {
                    StartTimerAndUV();
                }
                else
                {
                    StopTimerAndUV();
                }
                isPaused = !isPaused;
            }
            else
            {
                SaveRemain();
            }
        }
        else if (incKey.RepeatCount > 0 && decKey.RepeatCount > 0)
        {
            ResetRemain();
        }
        else if (incKey.IsPressed)
        {
            IncrementRemain(incKey.RepeatCount > 10 ? 10 : 1);
        }
        else if (decKey.IsPressed)
        {
            DecrementRemain(decKey.RepeatCount > 10 ? 10 : 1);
        }
    }
}
