#include <intrins.h>
#include "REG_MG82F6D64.h"
#include "TYPEDEF.h"
#include "stdio.h"
#include "MG82F6D64_COMMON_DRV.h"
#include "MG82F6D64_GPIO_DRV.h"
#include "MG82F6D64_RTC_DRV.h"
#include "MG82F6D64_PW_DRV.h"
#include "MG82F6D64_UART0_DRV.h"
#include "MG82F6D64_TIMER_DRV.h"
#include "MG82F6D64_INT_DRV.h"
#include "MG82F6D64_INT_VECTOR.h"

/**
 ******************************************************************************
 * do-while template
 ******************************************************************************
 */
#define MWT( __stuff__ )  do { __stuff__ } while (0)

/****************************************************************
RTC Perpetual Calendar definitions
****************************************************************/
typedef enum
{
    January,
    February,
    March,
    April,
    May,
    June,
    July,
    August,
    September,
    October,
    November,
    December
} _MonthDef;

typedef enum
{
    Sunday,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
} _WeekendDef;

typedef enum
{
    LeapYear,
    NotLeapYear,
} _isLeapYearDef;

typedef struct
{
    uint32_t year; /*!< Specifies the year */

    _MonthDef month; /*!< Specifies the month. Please reference _MonthDef enum.*/

    uint8_t day; /*!< Specifies the day number of month. */

    uint8_t hour; /*!< Specifies the hour of 24 hours a day */

    uint8_t minute; /*!< Specifies minute */

    uint8_t second; /*!< Specifies second */

    _WeekendDef weekend; /*!< Specifies weekend. Please reference _WeekendDef enum. */

} _PerpetualCalendarDef;

/****************************************************************
Variables
RTC Interrupt => RTCF_Flag
Timer2 Interrupt => TF2_Flag
RTC Perpetual Calendar => RTC_Counter, RTC_CNT_Tmp, MG_Calendar
****************************************************************/
uint8_t RTCF_Flag = 0;
uint8_t TF2_Flag = 0;
uint8_t T2_Counter = 0;
uint32_t RTC_Counter = 0;
uint32_t RTC_CNT_Tmp = 0;
_PerpetualCalendarDef MG_Calendar;

/****************************************************************
 *  Function : delay_ms
 *  When CPU clock at 12 MHz delay time is about 1mS.
 ****************************************************************/
void delay_ms(uint16_t dly)
{
    uint16_t dly1;
    while (dly--)
    {
        dly1 = 627;
        while (dly1--);
    }
}

/****************************************************************
 *  Function : UART0_9600_Init
 *  Use the Timer1(T1OF) as UART0 clock source.
 ****************************************************************/
void UART0_9600_Init(void)
{
    // UART0 Register initial Tx:P31 Rx:P30
    __DRV_URT0_SetSM10(UART0_MODE1_8BIT);
    __DRV_URT0_SerialReception_Cmd(MW_ENABLE);
    TI0 = 1;

    // Use Timer1 Auto reload mode to generate the baud rate 9600
    __DRV_TIMER1_Mode_Select(TIMER1_MODE2_8BIT_AUTORELOAD);
    __DRV_TIMER1_Clock_Source_Select(TIMER1_CLOCK_SOURCE_SYSCLK);
    __DRV_TIMER1_Set8BitIntervalAutoReload(217);
    __DRV_TIMER1_Run_Cmd(MW_ENABLE);
}

_isLeapYearDef isLeapYear(_PerpetualCalendarDef *MG_Calendar)
{
    if ((MG_Calendar->year % 4 == 0 && MG_Calendar->year % 100 != 0) || (MG_Calendar->year % 400 == 0))
        return LeapYear;
    return NotLeapYear;
}

void FillYMD(_PerpetualCalendarDef *MG_Calendar, uint32_t days)
{
    // month days
    const uint8_t MonthDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    uint8_t MaxDayOfMonth;

    // ------------------------------------------------------------------------
    MaxDayOfMonth = MonthDays[MG_Calendar->month];

    if (MG_Calendar->month == February)
        if (isLeapYear(MG_Calendar) == LeapYear)
            MaxDayOfMonth = 29;

    // ------------------------------------------------------------------------
    if (days > MaxDayOfMonth)
    {
        //
        days -= (MaxDayOfMonth - MG_Calendar->day);
        MG_Calendar->month++;

        if (MG_Calendar->month > December)
        {
            MG_Calendar->month = January;
            MG_Calendar->year++;
        }

        MaxDayOfMonth = MonthDays[MG_Calendar->month];

        if (MG_Calendar->month == February)
            if (isLeapYear(MG_Calendar) == LeapYear)
                MaxDayOfMonth = 29;

        // year
        while (1)
        {
            if (days < 365)
                break;

            if (isLeapYear(MG_Calendar) == LeapYear)
                days -= 366;
            else
                days -= 365;

            MG_Calendar->year++;
        }

        // month
        while (days > MaxDayOfMonth)
        {
            // check leap year
            // February has 29 days
            switch (MG_Calendar->month)
            {
            case January:
            case February:
                if (isLeapYear(MG_Calendar) == LeapYear){
                    days -= 29;
                }
                else{
                    days -= 28;
                }
                break;
            case March:
            case April:
            case May:
            case June:
            case July:
            case August:
            case September:
            case October:
            case December:
                days -= 31;
                break;
            case November:
                days -= 30;
                break;
            default:
                break;
            }

            MG_Calendar->month++;

            if (MG_Calendar->month > December)
            {
                MG_Calendar->month = January;
                MG_Calendar->year++;
            }

            MaxDayOfMonth = MonthDays[MG_Calendar->month];

            if (MG_Calendar->month == February)
                if (isLeapYear(MG_Calendar) == LeapYear)
                    MaxDayOfMonth = 29;
        }

        // ------------------------------------------------------------------------
        // remain days
        MG_Calendar->day = days;
    }
    else
    {
        MG_Calendar->day += days;

        if (MG_Calendar->day > MaxDayOfMonth)
        {
            MG_Calendar->day -= MaxDayOfMonth;
            MG_Calendar->month++;

            if (MG_Calendar->month > December)
            {
                MG_Calendar->month = January;
                MG_Calendar->year++;
            }
        }
    }
}

void getWeekday(_PerpetualCalendarDef *MG_Calendar)
{
    uint8_t C, Y, M, D;
    const uint8_t MonthWeekendTable[] = {0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5};

    // ------------------------------------------------------------------------
    C = (MG_Calendar->year / 100);             // generate century factor
    Y = (MG_Calendar->year % 100);             // generate year factor
    M = MonthWeekendTable[MG_Calendar->month]; // calculate month factor
    D = (MG_Calendar->day % 7);                // calculate day factor

    // calculate century factor
    C = 2 * (3 - (C % 4));

    // calculate year factor
    Y = ((Y % 28) + ((Y % 28) / 4)) % 7;

    if (isLeapYear(MG_Calendar) == LeapYear)
        if ((MG_Calendar->month == January) || (MG_Calendar->month == February))
            Y--;

    // calaulate weekend
    MG_Calendar->weekend = (_WeekendDef)((C + Y + M + D) % 7);
}


uint8_t Sample_RTC_PerpetualCalendar_Init(_PerpetualCalendarDef *MG_Calendar)
{
    // month days
    const uint8_t MonthDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

    // ------------------------------------------------------------------------
    // check input parameter (month)
    if (MG_Calendar->month > December)
        return 0x01;

    // ------------------------------------------------------------------------
    // check input parameter (day)
    if (MG_Calendar->day == 0)
        return 0x02;

    if (MG_Calendar->month == February)
    {
        // leap year ?
        if (isLeapYear(MG_Calendar) == LeapYear)
        {
            if (MG_Calendar->day > 29)
                return 0x03;
        }
        else if (MG_Calendar->day > 28)
            return 0x04;
    }
    else if (MG_Calendar->day > MonthDays[MG_Calendar->month])
        return 0x05;

    // ------------------------------------------------------------------------
    // check input parameter (hour)
    if (MG_Calendar->hour > 23)
        return 0x06;

    // ------------------------------------------------------------------------
    // check input parameter (minute)
    if (MG_Calendar->minute > 59)
        return 0x07;

    // ------------------------------------------------------------------------
    // check input parameter (second)
    if (MG_Calendar->second > 59)
        return 0x08;

    return 0;
}

unsigned char Sample_RTC_CaptureConvert(_PerpetualCalendarDef *MG_Calendar)
{
    uint32_t RTC_CNT;
    uint32_t TempC;

    // ------------------------------------------------------------------------
    // Get RTC timer value
    RTC_CNT = (RTC_Counter * 64 + (RTCTM & 0x3F)) - RTC_CNT_Tmp; // Get capture register data
    RTC_CNT_Tmp = (RTC_Counter * 64 + (RTCTM & 0x3F));

    // ------------------------------------------------------------------------
    // addition base time
    TempC = ((uint32_t)MG_Calendar->hour * 3600);
    TempC += ((uint32_t)MG_Calendar->minute * 60);
    TempC += ((uint32_t)MG_Calendar->second);
    RTC_CNT += TempC;

    // ------------------------------------------------------------------------
    // convert second, mintu, hour
    MG_Calendar->second = RTC_CNT % 60;
    MG_Calendar->minute = (RTC_CNT / 60) % 60;
    MG_Calendar->hour = (RTC_CNT / 3600) % 24;

    // ------------------------------------------------------------------------
    // Fill days, month, year
    TempC = RTC_CNT / 86400;
    FillYMD(MG_Calendar, TempC);

    // ------------------------------------------------------------------------
    // Get Weekend
    getWeekday(MG_Calendar);

    return 0;
}

void main()
{
    /****************************************************************
     *  UART0 Initialized
     ****************************************************************/
    UART0_9600_Init();

    /****************************************************************
     *  Calendar Initialized
     ****************************************************************/
    MG_Calendar.year = 2022;
    MG_Calendar.month = April;
    MG_Calendar.day = 8;
    MG_Calendar.hour = 16;
    MG_Calendar.minute = 8;
    MG_Calendar.second = 50;
    Sample_RTC_PerpetualCalendar_Init(&MG_Calendar);

    /****************************************************************
     *  Interrupt Initialized
     *  1. Enable RTCF interrupt (RTCFIE) = Enable;
     *  2. Enable System Flag interrupt (ESF) = Enable;
     *  3. Enable Timer2 interrupt (ET2) = Enable;
     *  4. Global enables all interrupts (EA) = Enable;
     ****************************************************************/
    __DRV_RTC_IT_Cmd(MW_ENABLE);
    __DRV_INT_SystemFlag_IT_Enable();
    __DRV_TIMER2_IT_Cmd(MW_ENABLE);
    __DRV_INT_ITEA_Enable();

    /****************************************************************
     *  GPIO Initialized
     *  1. P5.5 used for LED_D4
     *  2. P6.0 used for ECKI
     ****************************************************************/
    __DRV_GPIO_P5ModeSelect(P55_PushPull);
    __DRV_GPIO_P6ModeSelect(P60_OpenDrainPullUp);

    /****************************************************************
     *  RTC Initialized
     *  1. RTCKO Speed = ECKI / 32768
     *  2. Clear counter & reload counter & RTC flag
     *  3. RTC = Enable
     ****************************************************************/
    __DRV_RTC_ClockSource_Select(RTC_CLK_P60);
    __DRV_RTC_SetPrescaler(RTC_RTCCS_DIV_32768);
    __DRV_RTC_SetReloaderValue(0);
    __DRV_RTC_SetCounterValue(0);
    __DRV_RTC_ClearFlag();
    __DRV_RTC_Cmd(MW_ENABLE);

    /****************************************************************
     *  Timer2 Initialized
     *  Interrupt time = 1 Sec
     *  For update Time every second.
     ****************************************************************/
    __DRV_TIMER2_Set16BitCounter(55535);
    __DRV_TIMER2_Set16Bit_AutoReloadValue(55535);
    __DRV_TIMER2_Run_Cmd(MW_ENABLE);

    printf("RTC Perpetual Calendar initial finish\n");

    while (1)
    {
        /****************************************************************
        *  When the timer2 interrupt(TF2) occurred and to update time.
        *  If the system is working LED will flash.
        ****************************************************************/
        if (TF2_Flag)
        {
            Sample_RTC_CaptureConvert(&MG_Calendar); //Update the RTC Time.
            printf("%ld/%d/%d ", (uint32_t)MG_Calendar.year, (uint16_t)MG_Calendar.month + 1, (uint16_t)MG_Calendar.day);
            printf("%d:%d:%d ", (uint16_t)MG_Calendar.hour, (uint16_t)MG_Calendar.minute, (uint16_t)MG_Calendar.second);
            printf("Weekend:%d\n", (uint16_t)MG_Calendar.weekend);
            TF2_Flag = 0;
        }
        __DRV_GPIO_InversePinP5(P55);
        delay_ms(50);
    }
}

void SYSFlag_RTC_ISR() interrupt SYSFLAG_ISR_VECTOR
{
    // To check RTC Flag
    if ((PCON1 & RTCF) == RTCF)
    {
        RTCF_Flag = 1;
        RTC_Counter += 1;
        __DRV_RTC_ClearFlag();
    }
}

void TIMER2_TF2_EXF2_TF2L_ISR(void) interrupt TIMER2_ISR_VECTOR
{
    if (TF2 == 1)
    {
        T2_Counter += 1;
        DRV_TIMER2_ClearTF2();
    }
    if (T2_Counter == 100)
    {
        TF2_Flag = 1;
        T2_Counter = 0;
    }
}
