/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id:$ 
 *
 * Copyright (C) 2006 Victor Rajewski
 * Some parts based on work by Ivo Burkart 2004
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include "plugin.h"
#include "time.h"
#include "timefuncs.h"

#ifndef SIMULATOR
typedef long int time_t;
#endif

#define ALARM_DEFAULT_OFFSET 3600

time_t time_to_scalar(struct tm* time);
struct tm* time_to_struct(const time_t* time, struct tm* resultp);

PLUGIN_HEADER

static struct plugin_api* rb;

static struct alarm_settings {
    struct tm alarm;
    int snooze;
    int vol_start;
    int vol_end;
    int vol_time;
} settings;

static char default_filename[] = "/.rockbox/rocks/.alarm_settings";

bool status;

int field;

/* current time and alarm*/
struct tm* now_tm;
time_t now;
struct tm* alm_tm;
time_t alm;


void format_int(char *res, int var)
{
    rb->snprintf(res, 3, "%02d", var);
}

void format_time(char *res, struct tm* t)
{
    rb->snprintf(res, 9, "%02d:%02d:%02d", t->tm_hour, t->tm_min, t->tm_sec);
}

void draw(void)
{
    rb->lcd_setfont(FONT_SYSFIXED);
    rb->lcd_clear_display();

    char timestr[9];

    rb->lcd_puts(0,0,"Rockbox Alarm");

    rb->lcd_puts(0,2,"Status: ");
    if (status)
        {rb->lcd_puts(8,2,"Alarm");}
    else
        {rb->lcd_puts(8,2,"Waiting");}

    rb->lcd_puts(0,3,"Time  : ");
    format_time(timestr, now_tm);
    rb->lcd_puts(8,3,timestr);

    rb->lcd_puts(0,4,"Alarm : ");
    format_time(timestr, alm_tm);
    rb->lcd_puts(8,4,timestr);

    rb->lcd_update();
}


void update_time(void)
{
    now_tm = rb->get_time();
    now = time_to_scalar(now_tm);
}

void check_alarm(void)
{
    if ((!status) && now >= alm)
    {
        status = true;
#ifndef SIMULATOR
        rb->sound_set(SOUND_VOLUME, settings.vol_start);
        rb->global_settings->volume = settings.vol_start;
        rb->pcm_play_pause(true);
#endif
    }
}

void save_settings(void)
{
    int fd;
    fd = rb->creat(default_filename, O_WRONLY);
    if(fd >= 0)
    {
        rb->write (fd, &settings, sizeof(struct alarm_settings));
        rb->close(fd);
    }

}

bool load_settings(void)
{
    int fd;
    fd = rb->open(default_filename, O_RDONLY);

    if(fd >= 0)
    {
        rb->read (fd, &settings, sizeof(struct alarm_settings));
        rb->close(fd);
        return true;
    }

    return false;
}



void alarm_set_draw(void) 
{
    char timestr[9];
    rb->lcd_clear_display();
    rb->lcd_puts(0,0,"Set Alarm");
   
    rb->lcd_puts(0,2,"Now: ");
    format_time(timestr, now_tm);
    rb->lcd_puts(8,2,timestr);
    
    rb->lcd_puts(0,3,"Alarm : ");
    
    rb->snprintf(timestr, 3, "%02d", alm_tm->tm_hour);
    rb->lcd_puts_style(8,3,timestr,(int)(field==0));
    rb->lcd_puts(10,3,":");
    rb->snprintf(timestr, 3, "%02d", alm_tm->tm_min);
    rb->lcd_puts_style(11,3,timestr,(int)(field==1));
    rb->lcd_puts(13,3,":");
    rb->snprintf(timestr, 3, "%02d", alm_tm->tm_sec);
    rb->lcd_puts_style(14,3,timestr,(int)(field==2));
    rb->lcd_puts(16,3,"  ");

    rb->lcd_update();
}

/*sets the alarm time to the given HMS on the same day, or next day*/
time_t alarm_set_time(time_t* new_alarm_p) 
{
    time_t new_alarm;
    struct tm new_alarm_tm;
    
    if(new_alarm_p)
    {
        new_alarm = *new_alarm_p;
    }
    else
    {
        new_alarm = alm;
    }
    alm = now;
    time_to_struct(&alm, alm_tm);
    time_to_struct(&new_alarm, &new_alarm_tm);
    alm_tm->tm_hour = new_alarm_tm.tm_hour;
    alm_tm->tm_min = new_alarm_tm.tm_min;
    alm_tm->tm_sec = new_alarm_tm.tm_sec;
    alm = time_to_scalar(alm_tm);
    if(alm <= now)
    {
        alm += 60*60*24;
        time_to_struct(&alm, alm_tm);
    }
    return alm;
}
int wrap_int(int *var, int min, int max)
{
    int temp = 0;

    while ((*var) < min) {(*var) += (max-min+1); temp--;}
    while ((*var) > max) {(*var) -= (max-min+1); temp++;}

    return temp;

}

void move_field(int dif)
{
    field += dif;
    wrap_int(&field,0,2);
    alarm_set_draw();
}

void change_field(int dif)
{
    switch (field)
    {
        case 0: {alm_tm->tm_hour += dif; break;}
        case 1: {alm_tm->tm_min += dif; break;}
        case 2: {alm_tm->tm_sec += dif; break;}
    }
    alm = time_to_scalar(alm_tm);
    time_to_struct(&alm, alm_tm);
    alarm_set_draw();
    status = false;
}

/*Menu Callback functions*/
static bool alarm_set(void)
{
    /*bool result = rb->set_time_screen("Set Alarm", alm_tm);
    alm = time_to_scalar(alm_tm);*/

    int oldsec;
    int action;
    
    while (1) 
    {
        update_time();
        if (now_tm->tm_sec != oldsec) 
        {
            alarm_set_draw();
            oldsec = now_tm->tm_sec;
        }
        action = rb->get_action(CONTEXT_SETTINGS, HZ / 2);
        switch (action)
        {
            case ACTION_STD_PREV:
                move_field(-1);
                break;
            case ACTION_STD_NEXT:
                move_field(1);
                break;
            case ACTION_SETTINGS_INC:
                change_field(1);
                break;
            case ACTION_SETTINGS_DEC:
                change_field(-1);
                break;
            case ACTION_STD_OK:
                alarm_set_time(NULL);
                return true;
                break;
            case ACTION_STD_CANCEL:
                return false;
                break;
                /*TODO - add USB handler*/
        }
    }
    return true;
}

static bool snooze_set(void)
{
    return rb->set_int("Snooze time", "min", NULL /*TODO - voice menu???*/,
            &settings.snooze, NULL, 1, 1, 30, NULL);
}

static bool volume_start_set(void)
{
    return rb->set_int("Volume at start of alarm", "dB", NULL /*TODO - voice menu???*/,
            &settings.vol_start, NULL, 1, rb->sound_min(SOUND_VOLUME), 
            rb->sound_max(SOUND_VOLUME), NULL);
}

static bool volume_end_set(void)
{
    return rb->set_int("Volume at end of ramp-up", "dB", NULL /*TODO - voice menu???*/,
            &settings.vol_end, NULL, 1, rb->sound_min(SOUND_VOLUME), 
            rb->sound_max(SOUND_VOLUME), NULL);
}
static bool volume_time_set(void)
{
    return rb->set_int("Volume ramp-up time", "min", NULL /*TODO - voice menu???*/,
            &settings.vol_time, NULL, 5, 1, 60, NULL);
}
/*End Menu Callbacks*/
static bool alarm_options_menu(void) 
{
    int m;
    int result;
    static const struct menu_item items[] = {
        {"Alarm Time", alarm_set },
        {"Snooze Time", snooze_set},
        {"Volume Start", volume_start_set},
        {"Volume End", volume_end_set},
        {"Volume Time", volume_time_set},
    };
    m = rb->menu_init(items, sizeof(items) / sizeof(*items), NULL, NULL, NULL, NULL);
    result=rb->menu_run(m);
    rb->menu_exit(m);
    save_settings();
    return result;
}


void init(void)
{
    status = false;
    field = 0;
    
    update_time();

    if (!(load_settings()))
    {
        /* if can't load a time, set to now + offset*/
        alm = now += ALARM_DEFAULT_OFFSET;
        time_to_struct(&alm, &settings.alarm);
    }
    alm_tm = &settings.alarm;
    alm = time_to_scalar(alm_tm);
    alarm_set_time(NULL);

    
    draw();
#ifndef SIMULATOR
    rb->pcm_play_pause(false);
#endif
}

void deinit(void)
{
    save_settings();
}

enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
{
    (void)parameter;
    bool exit = false;
    int action;
    int oldsec;
    time_t next_ramp_time;
    int ramp_increment = 0 ;

    rb = api;

#ifndef SIMULATOR
    if (!(rb->pcm_is_playing()))
    {
        rb->splash(HZ*2, true, "Play a Song");
        return PLUGIN_ERROR;
    }
#endif

    init();
    next_ramp_time = alm;

    while (!exit)
    {
        update_time();

        check_alarm();
        if(status) 
        {
            if(now >= next_ramp_time) 
            {
                ramp_increment++;
                next_ramp_time = alm + (60 * settings.vol_time * ramp_increment) / 
                            (settings.vol_end - settings.vol_start);
                rb->global_settings->volume++;
                rb->sound_set(SOUND_VOLUME, rb->global_settings->volume);
            }
        }

        /* refresh LCD display */
        if (now_tm->tm_sec != oldsec)
        {
            draw();
            oldsec = now_tm->tm_sec;
        }

        /* check for button press */
        action = rb->get_action(CONTEXT_STD, HZ / 2);
        switch (action)
        {
            case ACTION_STD_MENU:
                alarm_options_menu();

            case ACTION_STD_OK:
            {
                /*TODO - change snooze functionality*/
                if (status)
                {
                    status=false;
#ifndef SIMULATOR
                    rb->pcm_play_pause(false);
#endif
                }
                break;
            }
        
            case ACTION_STD_CANCEL:
            {
                exit = true;
                break;
            }
            default: /*TODO what is this function???
                if(rb->default_event_handler_ex(action, cleanup, NULL)
                   == SYS_USB_CONNECTED)
                    return PLUGIN_USB_CONNECTED;*/
                break;
        }

    }
    
    deinit();

    return PLUGIN_OK;

}

/* Cheap and dodgy imitiations of mktime() and localtime_r()
 * We would all be better served with real implementations of the standard C libs
 * But the dodginess shouldn't matter in this plugin, as it only uses time 
 * within a day of now.
 */
#define EPOCH_YEAR 1970
#define TM_YEAR_BASE 1900

int leapyear(int year){
    return
    ((year & 3) == 0
     && (year % 100 != 0
     || ((year / 100) & 3) == (- (TM_YEAR_BASE / 100) & 3)));
}

struct tm* time_to_struct(const time_t* time, struct tm* resultp)
{
    int i, yday, old_yday=0;
    time_t t = *time;
    
    /*TODO!!! this doesn't take leap years into account!*/
    resultp->tm_year = t/(365*86400) + (EPOCH_YEAR - TM_YEAR_BASE);
    t -= 365*86400*(resultp->tm_year - (EPOCH_YEAR - TM_YEAR_BASE));
    resultp->tm_yday = t/86400;
    t -= resultp->tm_yday * 86400;

    yday = resultp->tm_yday;
    for(i=0; i<12; i++)
    {
        if(yday < 0)
        {
            resultp->tm_mon = i-1;
            resultp->tm_mday = old_yday + 1;
            break;
        }
        old_yday = yday;
        
        /*30 day months*/
        if(i==8 || i==3 || i==5 || i==10)
        {
            yday -= 30;
            continue;
        }
        /*February*/
        if(i==1)
        {
            yday -= 28;
            if(leapyear(resultp->tm_year))
            {
                yday--;
            }
            continue;
        }
        /*31 day months*/
        else
        {
            yday -= 31;
        }

    }
    
    resultp->tm_hour = t/3600;
    t -= 3600*resultp->tm_hour;
    resultp->tm_min = t/60;
    t -= 60*resultp->tm_min;
    resultp->tm_sec = t;

    return resultp;
}
        
time_t time_to_scalar(struct tm* time)
{
    time_t result;
    int i;
    int yday = 0;
    result = time->tm_sec;
    result += 60*time->tm_min;
    result += 3600*time->tm_hour;
    
    
    for(i=0; i<12; i++)
    {
        if(i == time->tm_mon)
        {
            break;
        }
        /*30 day months*/
        if(i==8 || i==3 || i==5 || i==10)
        {
            yday += 30;
            continue;
        }
        /*February*/
        if(i==1)
        {
            yday += 28;
            if(leapyear(time->tm_year))
            {
                yday++;
            }
            continue;
        }
        /*31 day months*/
        else
        {
            yday += 31;
        }
    }
    yday += time->tm_mday - 1;
    result += 86400*yday;
    time->tm_yday = yday;

    /*TODO!!! this doesn't take leap years into account!*/
    result += 365*86400*(time->tm_year - (EPOCH_YEAR - TM_YEAR_BASE));
    return result;
}
