/*
 * Copyright (c) 2013 - 2014, Freescale Semiconductor, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of Freescale Semiconductor, Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>

#include "device/fsl_device_registers.h"
#include "fsl_rtc_driver.h"
#include "fsl_interrupt_manager.h"
#include "fsl_clock_manager.h"
#include "fsl_debug_uart.h"
#include "fsl_uart_hal.h"
#include "fsl_sim_hal.h"
#include "fsl_misc_utilities.h"
#include "shell.h"
#include "board.h"

#include <time.h>


/*******************************************************************************
 * Defination
 ******************************************************************************/

/*******************************************************************************
 * Prototypes
 ******************************************************************************/
static int32_t cmd_alarm(int argc, char *const argv[]);
static int32_t cmd_date(int argc, char *const argv[]);
static int32_t cmd_time(int argc, char *const argv[]);
static int32_t cmd_comp(int argc, char *const argv[]);
static uint8_t Getchar(void);
static void Putchar(uint8_t ch);

/*******************************************************************************
 * Variables
 ******************************************************************************/
static cmd_tbl_t cmd_table[] =
{
    {
     .name = "alarm",
     .maxargs = 2,
     .repeatable = 1,
     .cmd = cmd_alarm,
     .usage = "Set an alarm",
     .complete = NULL,
     .help = "Usage: alarm [seconds] - set alarm with time in seconds\r\n"
             "       alarm - get the current alarm\r\n",
    },
    {
     .name = "date",
     .maxargs = 4,
     .repeatable = 1,
     .cmd = cmd_date,
     .usage = "Get and set the datetime",
     .complete = NULL,
     .help = "Usage: date - get datetime\r\n"
             "       date set [YYYY-MM-DD HH:MM:SS] - "
             "set the current datetime\r\n",
    },
    {
     .name = "time",
     .maxargs = 1,
     .repeatable = 1,
     .cmd = cmd_time,
     .usage = "Get precise time in nanosec",
     .complete = NULL,
     .help = "Usage: time - get the current time\r\n",
    },
    {
     .name = "comp",
     .maxargs = 3,
     .repeatable = 1,
     .cmd = cmd_comp,
     .usage = "Compensate demo",
     .complete = NULL,
     .help = "Usage: comp [cycles] [interval] - "
             "set the compansation cycles and interval.\r\n"
             "Check the RTC_CLKOUT pin for compansation result\r\n",
    }
};

extern const cmd_tbl_t CommandFun_Help;

static shell_io_install_t shell_io =
{
    .sh_getc = Getchar,
    .sh_putc = Putchar,
};

 /*******************************************************************************
 * Code
 ******************************************************************************/

 /*!
 * @brief get a char
 */
static uint8_t Getchar(void)
{
    uint8_t ch = 0;
    if (uart_hal_is_receive_data_register_full(BOARD_DEBUG_UART_INSTANCE))
    {
        uart_hal_getchar(BOARD_DEBUG_UART_INSTANCE, &ch);
    }
    /* must return zero for shell if non received */
    return ch;
}
 /*!
 * @brief put a char
 */
static void Putchar(uint8_t ch)
{
    while (!uart_hal_is_transmit_data_register_empty(BOARD_DEBUG_UART_INSTANCE)) { }
    uart_hal_putchar(BOARD_DEBUG_UART_INSTANCE, ch);
}

/*!
 * @brief set alarm command.
 *
 * This function set the alarm which will be
 * trigerred x secs later. The alarm trigger
 * will print a notification on the console.
 *
 * e.g >>alarm 100
 * set the alarm 100s from now on.
 */
static int32_t cmd_alarm(int argc, char *const argv[])
{
    rtc_datetime_t date;
    uint32_t seconds, alarm_sec;

    if (argc == 1) {
        /* alarm with no arguments */
        if (rtc_hal_read_alarm_int_enable())
        {
            rtc_get_alarm(&date);
            printf("Current alarm: %02d:%02d:%02d\r\n",
                            date.hour, date.minute, date.second);
        }
        else
        {
            printf("No alarm armed\r\n");
        }
        return 0;
    }

    if (argc == 2)
    {
        alarm_sec = strtoul(argv[1], 0, 0);
        if ((alarm_sec != ULONG_MAX) && (alarm_sec != 0))
        {
            /* alarm with seconds argument */
            rtc_hal_get_seconds(&seconds);
            alarm_sec += seconds;
            if (seconds > alarm_sec)
            {
                printf("Alarm seconds overflow, please input valid one\r\n");
                return 1;
            }
            /* set the alarm and enable intr */
            rtc_hal_set_alarm(&alarm_sec);
            rtc_hal_config_alarm_int_enable(true);

            /* print the alarm info */
            rtc_get_alarm(&date);
            printf("Current alarm: %02d:%02d:%02d\r\n",
                            date.hour, date.minute, date.second);
            return 0;
        }
    }

    /* print help */
    printf("%s\r\n", cmd_table[0].help);
    return 0;
}

/*!
 * @brief set/get date command.
 *
 * This function get or set the date by
 * the format YYYY-MM-DD hh:mm:ss
 *
 * e.g >>date set 2013-12-01 12:12:00
 * set the datetime to 12/1/2013 12:12:00.
 */
static int32_t cmd_date(int argc, char *const argv[])
{
    rtc_datetime_t datetime;
    uint32_t result;

    switch (argc)
    {
    case 1:
        /* get datetime without args */
        rtc_get_datetime(&datetime);
        printf("Current datetime: %04hd-%02hd-%02hd %02hd:%02hd:%02hd\r\n",
                        datetime.year, datetime.month, datetime.day,
                        datetime.hour, datetime.minute, datetime.second);
        break;
    case 4:
        /* set the current datetime */
        result = sscanf(argv[2], "%04hd-%02hd-%02hd",
	                &datetime.year, &datetime.month, &datetime.day);
        if (result != 3)
        {
            printf("Invalid input format, use help\r\n");
            return 1;
        }
        result = sscanf(argv[3], "%02hd:%02hd:%02hhd",
                        &datetime.hour, &datetime.minute, &datetime.second);
        if (result != 3)
        {
            printf("Invalid input format, use help\r\n");
            return 1;
        }
        /* set the datetime */
        result = rtc_set_datetime(&datetime, true);
        if (!result)
        {
            printf("Invalid input time range\r\n");
            return 1;
        }
        break;
    default:
        printf("%s\r\n", cmd_table[1].help);
        break;
    }
    return 0;
}

/*!
 * @brief get the current precise time.
 *
 * This function get the current precise
 * time with the resolution of 30us
 *
 * e.g >> time
 * show the current time in a 30us resolution
 */
static int32_t cmd_time(int argc, char *const argv[])
{
    uint32_t us;
    time_t timeval;
    uint16_t prescaler;
    struct tm *ptm;
    float convert_us;

    if (argc != 1)
    {
        printf("%s\r\n", cmd_table[2].help);
        return 0;
    }

    /* get the seconds and prescaler */
    rtc_hal_get_seconds(&us);
    rtc_hal_get_prescaler(&prescaler);

    /* convert to time structure */
    timeval = us;
    ptm = gmtime(&timeval);
    /* convert prescaler to us */
    prescaler &= 0x7FFF;
    convert_us = 1000000/32768 * prescaler;
    us = (uint32_t)convert_us;
    /* show time */
    printf("Current time: %02d:%02d:%02d %dms %dus\r\n",
                    ptm->tm_hour, ptm->tm_min, ptm->tm_sec,
                    us/1000, us%1000);
    return 0;
}

/*!
 * @brief demo the compansation of RTC
 *
 * This function set the compansation value
 * and it's interval value. Demo the compansation
 * result by the RTC_CLKOUT pin.
 *
 * e.g >> comp 32896 20
 * Timer prescaler overflow every 32896 clock cycles,
 * with 20s compensation interval.
 */
static int32_t cmd_comp(int argc, char *const argv[])
{
    uint32_t interval, cycles;
    uint8_t value;

    if (argc == 1)
    {
        rtc_hal_get_compensation_interval(&value);
        interval = value;
        rtc_hal_get_time_compensation(&value);
        cycles = 32896 - (uint8_t)(value - 0x80);
        printf("Compensation, cycles: %d, interval: %d\r\n",
                        cycles, interval);
        return 0;
    }
    if (argc == 3)
    {
        cycles = strtoul(argv[1], 0, 0);
        interval = strtoul(argv[2], 0, 0);
        if ((cycles <= 32896) && (cycles >= 32641) && (interval < 256))
        {
            /* set compensation interval and cycles */
            value = (uint8_t)interval;
            rtc_hal_set_compensation_interval(&value);
	    value = (uint8_t)((32896 - cycles) + 0x80);
            rtc_hal_set_time_compensation(&value);
            return 0;
        }
    }

    printf("%s\r\n", cmd_table[3].help);
    return 0;
}

/*!
 * @brief RTC alarm interrupt handler
 */
static void rtc_alarm_isr(void)
{
    rtc_datetime_t date;
    uint32_t seconds = 0;

    /* disable alarm interrupt */
    rtc_hal_config_alarm_int_enable(false);

    rtc_get_alarm(&date);
    printf("\r\nAlarm!! - %02d:%02d:%02d\r\n",
                        date.hour, date.minute, date.second);
    /* clear the SR[TAF] */
    rtc_hal_set_alarm(&seconds);
}

/*!
 * @brief RTC driver and hal init
 */
static void rtc_drv_init(void)
{
    uint32_t seconds;

    /* rtc hal init */
    rtc_hal_init_config_t rtc_init_configs = {
        .enableOscillatorLoadConfg = 2,
        .disableClockOutToPeripheral = false,
        .enable32kOscillator = true,
        .enableWakeupPin = false,
        .startSecondsCounterAt = 0,
        .prescalerAt = 1,
        .alarmCounterAt = 0,
        .compensationInterval = 0,
        .timeCompensation = 0,
        .enableInterrupts = 0,
    };

    /* rtc drv init */
    rtc_user_config_t rtc_drv_configs = {
        .general_config = &rtc_init_configs,
        .start_at_datetime = NULL,
    };

    /* enable clock to get the seconds */
    clock_manager_set_gate(kClockModuleRTC, 0, true);

    /* check if the RTC counter is kept before running */
    rtc_hal_get_seconds(&seconds);
    if (seconds == 0)
    {
        /* we can only reset the RTC[TSR] when it's zero */
        rtc_init_configs.startSecondsCounterAt = 1;
    }
    /* init the rtc module */
    rtc_init(&rtc_drv_configs);
    /*Register rtc isr callback function.*/
    rtc_register_isr_callback_function(0, rtc_alarm_isr);
    /* start counter */
    rtc_start_time_counter();
}

/*!
 * @brief main demo function.
 */
int main(void)
{
    hardware_init();

    configure_rtc_pin_mux(0);
    /* select the 1Hz for RTC_CLKOUT */
    clock_hal_set_clock_source(kSimClockRtcClkoutSel, 0);
    /* init the uart for shell */
    dbg_uart_init();

    /* install the shell io function */
    shell_io_install(&shell_io);
    /* register shell commands */
    shell_register_function(&CommandFun_Help);
    shell_register_function_array(cmd_table, ARRAY_SIZE(cmd_table));

    rtc_drv_init();

    /* run shell loop */
    while(1)
    {
        shell_main_loop("RTC Demo>");
    }
}
