/*
 * MP3/AAC/WAV player with JPEG photo frame feature
 *  for LPC2388 based CQ-FRK-NXP-ARM board
 */
#include <t_services.h>
#include <cqnxparm.h>
#include "kernel_id.h"
#include "nxpmp3.h"
#include "touch.h"
#include "lcd.h"
#include "player.h"
#include "qsteer.h"
#include "diskio.h"

#include "bmpfile/av_beginning.c"
#include "bmpfile/av_rewind.c"
#include "bmpfile/av_play.c"
#include "bmpfile/av_fast_forward.c"
#include "bmpfile/av_end.c"
#include "bmpfile/av_pause.c"
#include "bmpfile/knobadd.c"
#include "bmpfile/knobremove.c"
#include "bmpfile/knob_loop_on.c"
#include "bmpfile/knob_loop_off.c"
#include "bmpfile/control_pause.c"
#include "bmpfile/control_play.c"
#include "bmpfile/arrow_right.c"

/*
 * Defines Menu button positions
 */
#define	BUT_CLOCK	BUT_HOME
#define	BUT_PLAYER	BUT_NOTE
#define	BUT_FMRADIO	BUT_MUSIC

static struct but_def {
 UB xbegin, xend;
 H  button;
} button_list[] = {
   {  10,  50, BUT_CLOCK },	/* Shows date/time and photos */
   {  55,  95, BUT_MAIL },	/* No function assigned, yet */
   { 105, 145, BUT_PLAYER },	/* Music player */
   { 150, 190, BUT_PHONE },	/* No function assigned, yet */
   { 200, 240, BUT_FMRADIO }	/* FM Radio */
};

/* Defines touch buttons on the screen */
struct touch_button {
  UH xpos, ypos;
  UH width, height;
  H  button;
};

/*
 *  Slide Show control buttons (used in photo frame)
 */
enum { SL_PLAY, SL_PREV, SL_NEXT, SL_CONTROL };

struct touch_button slide_button[] = {
  {  0,   0, 240, 170, SL_CONTROL },
  {  0, 170,  36,  30, SL_PLAY },
  {204, 170,  36,  30, SL_NEXT },
};

/*
 *  Music player control buttons
 */
enum { AV_BEGIN, AV_PREV, AV_PLAY, AV_NEXT, AV_END, AV_PLUS, AV_MINUS };

struct touch_button mp3_button[] = {
  {  22, ICON_Y, 32, 32, AV_BEGIN },
  {  59, ICON_Y, 36, 36, AV_PREV },
  { 100, ICON_Y, 40, 40, AV_PLAY },
  { 145, ICON_Y, 36, 36, AV_NEXT },
  { 186, ICON_Y, 32, 32, AV_END },
  {  22, VOL_Y,  32, 32, AV_MINUS },
  { 186, VOL_Y,  32, 32, AV_PLUS },
  {   0,   0,  0,  0, 0 },
};

int find_touch_button(struct touch_button *blist, struct touch_event *ev)
{
   while (blist->width) {
        if ((ev->ev_xpos >= blist->xpos) &&
                (ev->ev_xpos <= blist->xpos + blist->width) &&
            (ev->ev_ypos >= blist->ypos) &&
                (ev->ev_ypos <= blist->ypos + blist->height)) {
                return blist->button;
        }
        blist++;
   }
   return -1;
}

static int check_button(int xpos)
{
    struct but_def *pbd = button_list;
    int i;

    for (i = 0; i < sizeof(button_list)/sizeof(struct but_def); i++) {
        if (xpos >= pbd->xbegin && xpos <= pbd->xend) {
#ifdef DEBUG
            syslog(LOG_NOTICE, "button = %d", pbd->button);
#endif
            return pbd->button;
        }
        pbd++;
    }
    return 0;
}

/*
 * Volume plus (+) button handler. Increment current device volume.
 */
void volume_plus(Player_Status *pst)
{
    if (pst->o_volume < 10) {
        pst->o_volume++;
        (pst->pdevfuncs[pst->o_device]->vol_set)(pst->o_volume);
        display_volume(pst->o_volume);
    }
syslog(LOG_NOTICE, "vol: %d", pst->o_volume);
}

/*
 * Volume minus (-) button handler. Decrement current device volume.
 */
void volume_minus(Player_Status *pst)
{
    if (pst->o_volume > 0) {
        pst->o_volume--;
        (pst->pdevfuncs[pst->o_device]->vol_set)(pst->o_volume);
        display_volume(pst->o_volume);
    }
syslog(LOG_NOTICE, "vol: %d", pst->o_volume);
}

/*
 * Display (or hide) slide control bar of photo frame
 */
void display_slide_control(Player_Status *pst)
{
    if (pst->slide_mode & SLIDE_CONTROL) {
        lcd_fill_rect( 0, 180, LCD_WIDTH, 16, C_WHITE);
        if (pst->slide_mode & SLIDE_SHOW) {
            lcd_load_icon( 10, 180, &icon_control_pause);
        } else {
            lcd_load_icon( 10, 180, &icon_control_play);
        }
        lcd_load_icon(214, 180, &icon_arrow_right);
    } else {
        lcd_fill_rect( 0, 180, LCD_WIDTH, 16, C_BLACK);
    }
}

/*
 * Event handler called from main_task.
 */
static void feed_event(Player_Status *pst, struct touch_event *ev)
{
    int button;
    struct touch_event *nev;
    TMODE tmode;

    tmode = pst->tmode;

    switch (tmode) {
    case TMODE_CLOCK: /* Clock mode */
        switch (ev->ev_type) {
        case EV_MTICK:  /* Mid night event. Update 'date' display. */
            show_date(0);
            break;
        case EV_TICK:   /* 1 sec event */
            update_temp();
            show_time(0);
            if (pst->slide_no > 0 && (pst->slide_mode & SLIDE_SHOW)) {
                /* slide show mode */
                if (pst->slide_timer == SLIDE_TIMER) {
                    snd_dtq(DTQ_PCOM, (VP_INT)C_SLIDE);
                    pst->slide_timer = 0;
                } else {
                    pst->slide_timer++;
                }
            }
            break;
        case EV_HTICK:  /* 0.5 sec event */
            hide_colon();
            break;
        case EV_UP:
            button = find_touch_button(slide_button, ev);
            switch (button) {
            case SL_PLAY: /* Slide play button */
                /* Ignore this event if slide control bar is not enabled. */
                if (!(pst->slide_mode & SLIDE_CONTROL))
                    break;
                /* Toggle slide mode and play/pause button shown. */
                pst->slide_mode ^= SLIDE_SHOW;
                if (pst->slide_mode & SLIDE_SHOW) {
                    lcd_load_icon( 10, 180, &icon_control_pause);
                } else {
                    lcd_load_icon( 10, 180, &icon_control_play);
                }
                /* Fall through... */
            case SL_NEXT: /* Next slide button */
                if (!(pst->slide_mode & SLIDE_CONTROL))
                    break;
                pst->slide_timer = 0;
                snd_dtq(DTQ_PCOM, (VP_INT)C_SLIDE);
                break;
            case SL_CONTROL: /* Slide control bar on/off */
                pst->slide_mode ^= SLIDE_CONTROL;
                display_slide_control(pst);
                break;
            }
            break;
        case EV_QSTEER: /* Q-STEER IR controller event */
            switch (ev->ev_button) {
            case IR_CMD_NEXT:
            case IR_CMD_PREV:
                /* toggle slide mode */
                pst->slide_mode ^= SLIDE_SHOW;
                if (!(pst->slide_mode & SLIDE_CONTROL)) {
                    if (pst->slide_mode & SLIDE_SHOW) {
                        lcd_load_icon( 10, 180, &icon_control_pause);
                    } else {
                        lcd_load_icon( 10, 180, &icon_control_play);
                    }
                }
                /* Fall through... */
            case IR_CMD_RIGHT:      /* Act as next slide */
            case IR_CMD_LEFT:
                pst->slide_timer = 0;
                snd_dtq(DTQ_PCOM, (VP_INT)C_SLIDE);
                break;
            case IR_CMD_FFWD:      /* Change to Player mode */
            case IR_CMD_FBWD:      /* Change to FM radio mode */
                if (get_mpf(MPF_TOUCH, (VP *)&nev) == E_OK) {
                    nev->ev_type = EV_BUTTON;
                    nev->ev_button = (ev->ev_button == IR_CMD_FFWD)?
                                        BUT_PLAYER : BUT_FMRADIO;
                    snd_dtq(DTQ_TOUCH, nev);
                }
                break;
            }
        }
        break;
    case TMODE_MP3: /* MP3 Player mode */
        switch (ev->ev_type) {
        case EV_QSTEER:
            switch (ev->ev_button) {
            case IR_CMD_RIGHT:      /* Act as PLAY/PAUSE */
                if (pst->p_state == S_PLAYING) {
                    lcd_load_icon(100, ICON_Y, &icon_av_play);
                    snd_dtq(DTQ_PCOM, (VP_INT)C_PAUSE);
                } else {
                    lcd_load_icon(100, ICON_Y, &icon_av_pause);
                    snd_dtq(DTQ_PCOM, (VP_INT)C_PLAY);
                }
                break;
            case IR_CMD_LEFT:       /* Act as REWIND/PAUSE */
                if (pst->p_state == S_PLAYING) {
                    lcd_load_icon(100, ICON_Y, &icon_av_play);
                    snd_dtq(DTQ_PCOM, (VP_INT)C_PAUSE);
                } else {
                    lcd_load_icon(100, ICON_Y, &icon_av_pause);
                    snd_dtq(DTQ_PCOM, (VP_INT)C_REWIND);
                }
                break;
            case IR_CMD_PREV:
                snd_dtq(DTQ_PCOM, (VP_INT)C_PREV);
                break;
            case IR_CMD_PREV_AL:
                snd_dtq(DTQ_PCOM, (VP_INT)C_PREV_ALBUM);
                break;
            case IR_CMD_NEXT:
                snd_dtq(DTQ_PCOM, (VP_INT)C_NEXT);
                break;
            case IR_CMD_NEXT_AL:
                snd_dtq(DTQ_PCOM, (VP_INT)C_NEXT_ALBUM);
                break;
            case IR_CMD_FWD:
                volume_plus(pst);
                break;
            case IR_CMD_BWD:
                volume_minus(pst);
                break;
            case IR_CMD_FFWD:
            case IR_CMD_FBWD:
                if (get_mpf(MPF_TOUCH, (VP *)&nev) == E_OK) {
                    nev->ev_type = EV_BUTTON;
                    nev->ev_button = (ev->ev_button == IR_CMD_FFWD)?
                                        BUT_FMRADIO : BUT_CLOCK;
                    snd_dtq(DTQ_TOUCH, nev);
                }
                break;
            }
            break;
        case EV_UP:
            button = find_touch_button(mp3_button, ev);
syslog(LOG_NOTICE, "MP3: button = %d", button);
            switch (button) {
            case AV_BEGIN:
                snd_dtq(DTQ_PCOM, (VP_INT)C_PREV_ALBUM);
                break;
            case AV_PREV:
                if (pst->p_state == S_PLAYING) {
                    snd_dtq(DTQ_PCOM, (VP_INT)C_PREV);
                } else {
                    snd_dtq(DTQ_PCOM, (VP_INT)C_REWIND);
                }
                break;
            case AV_PLAY:
                if (pst->p_state == S_PLAYING) {
                    lcd_load_icon(100, ICON_Y, &icon_av_play);
                    snd_dtq(DTQ_PCOM, (VP_INT)C_PAUSE);
                } else {
                    lcd_load_icon(100, ICON_Y, &icon_av_pause);
                    snd_dtq(DTQ_PCOM, (VP_INT)C_PLAY);
                }
                break;
            case AV_NEXT:
                snd_dtq(DTQ_PCOM, (VP_INT)C_NEXT);
                break;
            case AV_END:
                snd_dtq(DTQ_PCOM, (VP_INT)C_NEXT_ALBUM);
                break;
            case AV_PLUS:
                volume_plus(pst);
                break;
            case AV_MINUS:
                volume_minus(pst);
                break;
            }
            break;
        case EV_TICK:
            show_progress_bar();
            break;
        }
        break;
    case TMODE_FM: /* FM Radio mode */
        switch (ev->ev_type) {
        case EV_QSTEER:
            switch (ev->ev_button) {
            case IR_CMD_RIGHT:  /* Seek to next available station */
            case IR_CMD_NEXT:
                ar1000_find_station(0);
                break;
            case IR_CMD_LEFT:   /* Seek to previous available station */
            case IR_CMD_PREV:
                ar1000_find_station(1);
                break;
            case IR_CMD_FWD:
                volume_plus(pst);
                break;
            case IR_CMD_BWD:
                volume_minus(pst);
                break;
            case IR_CMD_FFWD:
            case IR_CMD_FBWD:
                if (get_mpf(MPF_TOUCH, (VP *)&nev) == E_OK) {
                    nev->ev_type = EV_BUTTON;
                    nev->ev_button = (ev->ev_button == IR_CMD_FFWD)?
                                        BUT_CLOCK : BUT_PLAYER;
                    snd_dtq(DTQ_TOUCH, nev);
                }
                break;
            }
            break;
        case EV_UP:
            button = find_touch_button(mp3_button, ev);
            switch (button) {
            case AV_NEXT:
                ar1000_find_station(0);
                break;
            case AV_PREV:
                ar1000_find_station(1);
                break;
            case AV_PLUS:
                volume_plus(pst);
                break;
            case AV_MINUS:
                volume_minus(pst);
                break;
            }
            break;
        case EV_TICK:
            show_time(1);
            break;
        }
        break;
    }
}

/*
 * Touch screen may cause bouncing while user press/release the screen.
 * Or, user may cause unintentional release event, since he/she didn't
 * press the screen strong enough. To solve this issue, we'll wait for
 * BOUNCE_TIMEOUT period after detecting the press/release event. If
 * further press/releae event is detected while this period, last
 * event is ignored.
 */
#define	BOUNCE_TIMEOUT	60	/* 60ms */

/*
 *  Main Task
 */
void main_task(VP_INT exinf)
{
    struct touch_event *ev, lastev;
    Player_Status *pst;
    UH button, ev_feed;
    TMO tmout;
    ER ercd;
    TMODE tmode;
    int newst;
    SYSTIM ctime;
    int disk_ins = STA_NODISK;

#ifdef notdef
    /* Initialize peripherals */
    adc_init();
    rtc_init();
    pwm_init();
    ir_init();
    s9705_init();
#endif
    lcd_init(0);
    font_init();
    disk_timer_start();

    pst = get_player();
    pst->pdevfuncs[O_CODEC] = codec_func_init();
    pst->pdevfuncs[O_USB] = usb_func_init();

    lcd_clear_screen(C_BLACK);
    ar1000_init();
    codec_init(1);

    /* We boot with Clock mode */
    pst->tmode = TMODE_CLOCK;
    show_date(0);
    show_time(0);

    lastev.ev_type = 0xff;
    tmout = TMO_FEVR;
    for (;;) {
        ev_feed = 0;
        ercd = trcv_dtq(DTQ_TOUCH, (VP_INT *)&ev, tmout);
        if (ercd == E_TMOUT) {
            /*
             * BOUNCE_TIMEOUT has expired. 
             */
            switch (lastev.ev_type) {
            case EV_DOWN:
                syslog(LOG_NOTICE, "Down");
                ev_feed = 1;
                break;
            case EV_UP:
                syslog(LOG_NOTICE, "Up (%d, %d)", lastev.ev_xpos, lastev.ev_ypos);
                if (lastev.ev_ypos > LCD_HEIGHT) {
                    /*
                     * It is out of LCD visible area, but it may belong to
                     * touch screen button area.
                     */
                    button = check_button(lastev.ev_xpos);
                    if (button) { /* Generate button event */
                        if (get_mpf(MPF_TOUCH, (VP *)&ev) == E_OK) {
                            ev->ev_type = EV_BUTTON;
                            ev->ev_button = button;
                            snd_dtq(DTQ_TOUCH, ev);
                        }
                    }
                } else {
                    ev_feed = 1;
                }
                break;
            }
            /*
             * Inform lastev to the upper layer, if necessary.
             */
            if (ev_feed) feed_event(pst, &lastev);
            tmout = TMO_FEVR;
            lastev.ev_type = 0xff;
            continue;
        }

        /* Some event has received. Handle BOUNCE_TIMEOUT timer */
        switch (ev->ev_type) {
        case EV_DOWN:
        case EV_UP:
            /* Record lastev and start BOUNCE_TIMEOUT timer */
            lastev.ev_type = ev->ev_type;
            lastev.ev_time = ev->ev_time;
            tmout = BOUNCE_TIMEOUT;
            break;
        case EV_MOVE:
            /*
             * We may get EV_MOVE event during BOUNCE_TIMEOUT period.
             */
            if (lastev.ev_type == EV_DOWN) {
                /* We've got EV_MOVE during BUNCE_TIMEOUT period.
                 * Ignore this EV_MOVE and recompute timeout duration.
                 */
                get_tim(&ctime);
                if ((ctime - lastev.ev_time) < BOUNCE_TIMEOUT) {
                    tmout = BOUNCE_TIMEOUT - (ctime - lastev.ev_time);
                }
            } else {
                lastev.ev_type = EV_MOVE;
            }
            lastev.ev_xpos = ev->ev_xpos;
            lastev.ev_ypos = ev->ev_ypos;
#ifdef DEBUG
            syslog(LOG_NOTICE, "(%d, %d)", ev->ev_xpos, ev->ev_ypos);
#endif
            /* Currently, MOVE event is ignored in our application. */
            break;
        case EV_BUTTON:
            button = ev->ev_button;
            syslog(LOG_NOTICE, "button: %x", button);
            /* Terminate any activity in current mode */
            switch (pst->tmode) {
            case TMODE_MP3:
                snd_dtq(DTQ_PCOM, (VP_INT)C_STOP);
                pst->img_flag &= ~IMG_SHOWN;        /* clear shown flag */
                while (pst->p_state == S_PLAYING) {
                    dly_tsk(10);
                }
                break;
            case TMODE_FM:
                finish_tuner_mode();
                break;
            }

            lcd_reset_window();

            /* Start activity in new mode */
            switch (button) {
            case BUT_CLOCK:
                lcd_clear_screen(C_BLACK);
                display_slide_control(pst);
                show_date(0);
                show_time(0);
                pst->tmode = TMODE_CLOCK;
                pst->slide_timer = 0;
                snd_dtq(DTQ_PCOM, (VP_INT)C_SLIDE);
                break;
            case BUT_PLAYER:
                lcd_clear_screen(C_WHITE);
                /* Draw player buttons */
                lcd_load_icon( 22, ICON_Y, &icon_av_beginning);
                lcd_load_icon( 59, ICON_Y, &icon_av_rewind);
                lcd_load_icon(100, ICON_Y, &icon_av_pause);
                lcd_load_icon(145, ICON_Y, &icon_av_fast_forward);
                lcd_load_icon(186, ICON_Y, &icon_av_end);
                /* Draw volume buttons */
                lcd_load_icon( 22, VOL_Y, &icon_knobremove);
                lcd_load_icon(186, VOL_Y, &icon_knobadd);
                /* Draw volume indicator */
                display_volume(pst->o_volume);
                (pst->pdevfuncs[pst->o_device]->vol_set)(pst->o_volume);
                lcd_reset_window();
                pst->tmode = TMODE_MP3;
                snd_dtq(DTQ_PCOM, (VP_INT)C_PLAY);
                break;
            case BUT_FMRADIO:
                lcd_clear_screen(C_WHITE);
                /* Draw tune buttons */
                lcd_load_icon( 59, ICON_Y, &icon_av_rewind);
                lcd_load_icon(145, ICON_Y, &icon_av_fast_forward);
                /* Draw volume buttons */
                lcd_load_icon( 22, VOL_Y, &icon_knobremove);
                lcd_load_icon(186, VOL_Y, &icon_knobadd);
                /* Draw volume indicator */
                display_volume(pst->o_volume);
                (pst->pdevfuncs[pst->o_device]->vol_set)(pst->o_volume);
                ar1000_status();
                start_tuner_mode();
                pst->tmode = TMODE_FM;
                break;
            }
            break;
        case EV_HTICK:  /* 0.5 sec periodic event. Blink LED */
            if (FIO1PIN & LEDMASK) {
                FIO1CLR = LEDMASK;
            } else {
                FIO1SET = LEDMASK;
            }
            ev_feed = 1;
            break;
        case EV_TICK:
        case EV_MTICK:
        case EV_QSTEER:
            ev_feed = 1;
            break;
        case EV_SDCARD:
            newst = ev->ev_button & STA_NODISK;
syslog(LOG_NOTICE, "newst = %x", newst);
            if (disk_ins != newst) {
                disk_ins = newst;
                if (disk_ins & STA_NODISK) {
                    snd_dtq(DTQ_PCOM, (VP_INT)C_REMOVE);
                } else {
                    snd_dtq(DTQ_PCOM, (VP_INT)C_INSERT);
                }
            }
            break;
        }
        if (ev_feed) feed_event(pst, ev);
        rel_mpf(MPF_TOUCH, ev);
    }
}

void ev_watcher(VP_INT exinf)
{
    struct touch_event *ev;
    int evmark;

    for (;;) {
        rcv_dtq(DTQ_EVENT, (VP_INT *)&evmark);
        if (get_mpf(MPF_TOUCH, (VP *)&ev) == E_OK) {
            switch (evmark & MARK_MASK) {
            case TICK_MARK:
                ev->ev_type = evmark & MARK_VALUE;
                ev->ev_xpos = ev->ev_ypos = 0;
                snd_dtq(DTQ_TOUCH, ev);
                break;
            case IR_MARK:
                ev->ev_type = EV_QSTEER;
                ev->ev_button = evmark & MARK_VALUE;
#ifdef DEBUG
                syslog(LOG_NOTICE, "evmar: %x", evmark);
#endif
                snd_dtq(DTQ_TOUCH, ev);
                break;
            case DISK_MARK:
                ev->ev_type = EV_SDCARD;
                ev->ev_button = evmark & MARK_VALUE;
                snd_dtq(DTQ_TOUCH, ev);
                break;
            }
        }
    }
}
