/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */


#include "../AVRCommon/Internal.h"


//
// Compatibility macros
//
#if !defined(TIMSK1) && defined(TIMSK)
    #define TIMSK1 TIMSK
#endif


namespace eCxx {


//
// A simple AVR timer/counter 1 wrapper
//
static uint8_t            saveTCCR1A            = 0;
static uint8_t            saveTCCR1B            = 0;

static bool               timer1Initialized     = false;

static eCxx::VoidCallback timer1CompareHandlerA = nullptr;
static eCxx::VoidCallback timer1CompareHandlerB = nullptr;


void TimerCounter1::start(ClockPrescaler prescaler)
{
    // Check if it is already initialized
    if(timer1Initialized) return;

    // Save the original configuration
    saveTCCR1A = TCCR1A;
    saveTCCR1B = TCCR1B;

    // Set configuration
    TCCR1A = 0;
    TCCR1B = _BV(WGM12) | static_cast<uint8_t>(prescaler);

    // Enable interrupt(s) as needed
    if(timer1CompareHandlerA) TIMSK1 |= _BV(OCIE1A);
    if(timer1CompareHandlerB) TIMSK1 |= _BV(OCIE1B);

    // Set flag
    timer1Initialized = true;
}


void TimerCounter1::stop()
{
    // Check if it is not initialized yet
    if(!timer1Initialized) return;

    // Disable interrupt(s) as needed
    if(timer1CompareHandlerA) TIMSK1 &= ~_BV(OCIE1A);
    if(timer1CompareHandlerB) TIMSK1 &= ~_BV(OCIE1B);

    // Restore the original configuration
    TCCR1A = saveTCCR1A;
    TCCR1B = saveTCCR1B;

    // Clear flag
    timer1Initialized = false;
}


void TimerCounter1::setPeriod(uint16_t value)
{
    // Check if it is not initialized yet
    if(!timer1Initialized) return;

    // Write 16-bit value atomically
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        OCR1AH = static_cast<uint8_t>(value >> 8     );
        OCR1AL = static_cast<uint8_t>(value &  0x00FF);
    }
}


uint16_t TimerCounter1::getPeriod()
{
    // Check if it is not initialized yet
    if(!timer1Initialized) return 0;

    // Read 16-bit value atomically
    uint8_t low;
    uint8_t high;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        low  = OCR1AL;
        high = OCR1AH;
    }

    return ( static_cast<uint16_t>(high) << 8 ) | low;
}


void TimerCounter1::attachPeriodicInterrupt(eCxx::VoidCallback userFunc)
{
    // Check if it is not initialized yet
    if(!timer1Initialized) return;

    // Attach interrupt
    timer1CompareHandlerA = userFunc;
    TIMSK1 |= _BV(OCIE1A);
}


void TimerCounter1::detachPeriodicInterrupt()
{
    // Check if it is not initialized yet
    if(!timer1Initialized) return;

    // Detach interrupt
    TIMSK1 &= ~_BV(OCIE1A);
    timer1CompareHandlerA = nullptr;
}


void TimerCounter1::setCompareMatchValue(uint16_t value)
{
    // Check if it is not initialized yet
    if(!timer1Initialized) return;

    // Write 16-bit value atomically
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        OCR1BH = static_cast<uint8_t>(value >> 8     );
        OCR1BL = static_cast<uint8_t>(value &  0x00FF);
    }
}


void TimerCounter1::attachCompareMatchInterrupt(eCxx::VoidCallback userFunc)
{
    // Check if it is not initialized yet
    if(!timer1Initialized) return;

    // Attach interrupt
    timer1CompareHandlerB = userFunc;
    TIMSK1 |= _BV(OCIE1B);
}


void TimerCounter1::detachCompareMatchInterrupt()
{
    // Check if it is not initialized yet
    if(!timer1Initialized) return;

    // Detach interrupt
    TIMSK1 &= ~_BV(OCIE1B);
    timer1CompareHandlerB = nullptr;
}


//
// Timer/counter 1 interrupt vectors
//
ISR(TIMER1_COMPA_vect)
{
    // Call the user callback if specified
    if(timer1CompareHandlerA) (*timer1CompareHandlerA)();
}


ISR(TIMER1_COMPB_vect)
{
    // Call the user callback if specified
    if(timer1CompareHandlerB) (*timer1CompareHandlerB)();
}


} // namespace eCxx
