/********************************** (C) COPYRIGHT *******************************
 * File Name          : LED.c
 * Author             : WCH
 * Version            : V1.2
 * Date               : 2022/01/18
 * Description        :
 *********************************************************************************
 * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
 * Attention: This software (modified or not) and binary are used for 
 * microcontroller manufactured by Nanjing Qinheng Microelectronics.
 *******************************************************************************/

/******************************************************************************/
/* Header file contains */
#include "HAL.h"

/* LED control structure */
typedef struct
{
    uint8_t  mode;  /* Operation mode */
    uint8_t  todo;  /* Blink cycles left */
    uint8_t  onPct; /* On cycle percentage */
    uint16_t time;  /* On/off cycle time (msec) */
    uint32_t next;  /* Time for next change */
} HalLedControl_t;

typedef struct
{
    HalLedControl_t HalLedControlTable[HAL_LED_DEFAULT_MAX_LEDS];
    uint8_t         sleepActive;
} HalLedStatus_t;

/***************************************************************************************************
 *                                           GLOBAL VARIABLES
 ***************************************************************************************************/
static uint8_t HalLedState; // LED state at last set/clr/blink update

static uint8_t preBlinkState; // Original State before going to blink mode
                              // bit 0, 1, 2, 3 represent led 0, 1, 2, 3
static HalLedStatus_t HalLedStatusControl;

/***************************************************************************************************
 *                                            LOCAL FUNCTION
 ***************************************************************************************************/
void HalLedOnOff(uint8_t leds, uint8_t mode);

/***************************************************************************************************
 *                                            FUNCTIONS - API
 ***************************************************************************************************/

/*********************************************************************
 * @fn      HAL_LedInit
 *
 * @brief   Initialize LED Service
 *
 * @return  none
 */
void HAL_LedInit(void)
{
    /* Initialize all LEDs to OFF */
    RCC_APB2PeriphClockCmd(LED1_PCENR, ENABLE);
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = LED1_BV;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(LED1_GPIO, &GPIO_InitStructure);
    HalLedSet(HAL_LED_ALL, HAL_LED_MODE_OFF);
    // just test
    HalLedBlink(HAL_LED_1, 10, 30, 4000);
    /* Initialize sleepActive to FALSE */
    HalLedStatusControl.sleepActive = FALSE;
}

/*********************************************************************
 * @fn      HalLedSet
 *
 * @brief   Turn ON/OFF/TOGGLE given LEDs
 *
 * @param   led     - bit mask value of leds to be turned ON/OFF/TOGGLE
 * @param   mode    - BLINK, FLASH, TOGGLE, ON, OFF
 *
 * @return  0
 */
uint8_t HalLedSet(uint8_t leds, uint8_t mode)
{
    uint8_t          led;
    HalLedControl_t *sts;

    switch(mode)
    {
        case HAL_LED_MODE_BLINK:
        {
            /* Default blink, 1 time, D% duty cycle */
            HalLedBlink(leds, 1, HAL_LED_DEFAULT_DUTY_CYCLE, HAL_LED_DEFAULT_FLASH_TIME);
            break;
        }

        case HAL_LED_MODE_FLASH:
        {
            /* Default flash, N times, D% duty cycle */
            HalLedBlink(leds, HAL_LED_DEFAULT_FLASH_COUNT, HAL_LED_DEFAULT_DUTY_CYCLE, HAL_LED_DEFAULT_FLASH_TIME);
            break;
        }

        case HAL_LED_MODE_ON:
        case HAL_LED_MODE_OFF:
        case HAL_LED_MODE_TOGGLE:
        {
            led = HAL_LED_1;
            leds &= HAL_LED_ALL;
            sts = HalLedStatusControl.HalLedControlTable;
            while(leds)
            {
                if(leds & led)
                {
                    if(mode != HAL_LED_MODE_TOGGLE)
                    {
                        sts->mode = mode; /* ON or OFF */
                    }
                    else
                    {
                        sts->mode ^= HAL_LED_MODE_ON; /* Toggle */
                    }
                    HalLedOnOff(led, sts->mode);
                    leds ^= led;
                }
                led <<= 1;
                sts++;
            }
            break;
        }

        default:
            break;
    }
    return (0);
}

/*********************************************************************
 * @fn      HalLedBlink
 *
 * @brief   Blink the leds
 *
 * @param   led         - bit mask value of leds to be turned ON/OFF/TOGGLE
 * @param   numBlinks   - number of blinks
 * @param   percent     - the percentage in each period where the led will be on
 * @param   period      - length of each cycle in milliseconds
 *
 * @return  none
 */
void HalLedBlink(uint8_t leds, uint8_t numBlinks, uint8_t percent, uint16_t period)
{
    uint8_t          led;
    HalLedControl_t *sts;

    if(leds && percent && period)
    {
        if(percent < 100)
        {
            led = HAL_LED_1;
            leds &= HAL_LED_ALL;
            sts = HalLedStatusControl.HalLedControlTable;
            while(leds)
            {
                if(leds & led)
                {
                    /* Store the current state of the led before going to blinking */
                    preBlinkState |= (led & HalLedState);
                    sts->mode = HAL_LED_MODE_OFF; /* Stop previous blink */
                    sts->time = period;           /* Time for one on/off cycle */
                    sts->onPct = percent;         /* % of cycle LED is on */
                    sts->todo = numBlinks;        /* Number of blink cycles */
                    if(!numBlinks)
                    {
                        sts->mode |= HAL_LED_MODE_FLASH; /* Continuous */
                    }
                    sts->next = TMOS_GetSystemClock(); /* Start now */
                    sts->mode |= HAL_LED_MODE_BLINK;   /* Enable blinking */
                    leds ^= led;
                }
                led <<= 1;
                sts++;
            }
            tmos_start_task(halTaskID, LED_BLINK_EVENT, 0);
        }
        else
        {
            HalLedSet(leds, HAL_LED_MODE_ON); /* >= 100%, turn on */
        }
    }
    else
    {
        HalLedSet(leds, HAL_LED_MODE_OFF); /* No on time, turn off */
    }
}

/*********************************************************************
 * @fn      HalLedUpdate
 *
 * @brief   Update leds to work with blink
 *
 * @return  none
 */
void HalLedUpdate(void)
{
    uint8_t          led, pct, leds;
    uint16_t         next, wait;
    uint32_t         time;
    HalLedControl_t *sts;

    next = 0;
    led = HAL_LED_1;
    leds = HAL_LED_ALL;
    sts = HalLedStatusControl.HalLedControlTable;

    /* Check if sleep is active or not */
    if(!HalLedStatusControl.sleepActive)
    {
        while(leds)
        {
            if(leds & led)
            {
                if(sts->mode & HAL_LED_MODE_BLINK)
                {
                    time = TMOS_GetSystemClock();
                    if(time >= sts->next)
                    {
                        if(sts->mode & HAL_LED_MODE_ON)
                        {
                            pct = 100 - sts->onPct;             /* Percentage of cycle for off */
                            sts->mode &= ~HAL_LED_MODE_ON;      /* Say it's not on */
                            HalLedOnOff(led, HAL_LED_MODE_OFF); /* Turn it off */
                            if(!(sts->mode & HAL_LED_MODE_FLASH))
                            {
                                if(sts->todo != 0xff)
                                {
                                    sts->todo--; /* Not continuous, reduce count */
                                }
                                if(!sts->todo)
                                {
                                    sts->mode ^= HAL_LED_MODE_BLINK; /* No more blinks */
                                }
                            }
                        }
                        else
                        {
                            pct = sts->onPct;                  /* Percentage of cycle for on */
                            sts->mode |= HAL_LED_MODE_ON;      /* Say it's on */
                            HalLedOnOff(led, HAL_LED_MODE_ON); /* Turn it on */
                        }
                        if(sts->mode & HAL_LED_MODE_BLINK)
                        {
                            wait = (((uint32_t)pct * (uint32_t)sts->time) / 100);
                            sts->next = time + wait;
                        }
                        else
                        {
                            /* no more blink, no more wait */
                            wait = 0;
                            /* After blinking, set the LED back to the state before it blinks */
                            HalLedSet(led, ((preBlinkState & led) != 0) ? HAL_LED_MODE_ON : HAL_LED_MODE_OFF);
                            /* Clear the saved bit */
                            preBlinkState &= (led ^ 0xFF);
                        }
                    }
                    else
                    {
                        wait = sts->next - time; /* Time left */
                    }
                    if(!next || (wait && (wait < next)))
                    {
                        next = wait;
                    }
                }
                leds ^= led;
            }
            led <<= 1;
            sts++;
        }
        if(next)
        {
            tmos_start_task(halTaskID, LED_BLINK_EVENT, next); /* Schedule event */
        }
    }
}

/*********************************************************************
 * @fn      HalLedOnOff
 *
 * @brief   Turns specified LED ON or OFF
 *
 * @param   led         - LED bit mask
 * @param   mode        - LED_ON,LED_OFF,
 *
 * @return  none
 */
void HalLedOnOff(uint8_t leds, uint8_t mode)
{
    if(leds & HAL_LED_1)
    {
        if(mode == HAL_LED_MODE_ON)
        {
            HAL_TURN_ON_LED1();
        }
        else
        {
            HAL_TURN_OFF_LED1();
        }
    }
    if(leds & HAL_LED_2)
    {
        if(mode == HAL_LED_MODE_ON)
        {
            HAL_TURN_ON_LED2();
        }
        else
        {
            HAL_TURN_OFF_LED2();
        }
    }
    if(leds & HAL_LED_3)
    {
        if(mode == HAL_LED_MODE_ON)
        {
            HAL_TURN_ON_LED3();
        }
        else
        {
            HAL_TURN_OFF_LED3();
        }
    }
    if(leds & HAL_LED_4)
    {
        if(mode == HAL_LED_MODE_ON)
        {
            HAL_TURN_ON_LED4();
        }
        else
        {
            HAL_TURN_OFF_LED4();
        }
    }
    /* Remember current state */
    if(mode)
    {
        HalLedState |= leds;
    }
    else
    {
        HalLedState &= (leds ^ 0xFF);
    }
}

/***************************************************************************************************
 * @fn      HalGetLedState
 *
 * @brief   Dim LED2 - Dim (set level) of LED2
 *
 * @return  led state
 */
uint8_t HalLedGetState()
{
    return HalLedState;
}

/******************************** endfile @ led ******************************/
