/***************************************************************************
*             __________               __   ___.
*   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
*   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
*   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
*   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
*                     \/            \/     \/    \/            \/
*
* Copyright (C) 2005 Kevin Ferrare
*
* Mystify demo plugin
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/

#include "plugin.h"
#include "lib/pluginlib_exit.h"

#include "lib/pluginlib_actions.h"
#include "lib/helper.h"


#define DEFAULT_WAIT_TIME 3
#define DEFAULT_NB_POLYGONS 7
#define NB_POINTS 4
#define MAX_STEP_RANGE 7
#define MIN_STEP_RANGE 3
#define MAX_POLYGONS 40
#define MIN_POLYGONS 1

/* Key assignement */

#ifdef HAVE_SCROLLWHEEL

#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
    || (CONFIG_KEYPAD == IPOD_3G_PAD) \
    || (CONFIG_KEYPAD == IPOD_4G_PAD)
#define DEMYSTIFY_QUIT                      PLA_UP
#else
#define DEMYSTIFY_QUIT                      PLA_CANCEL
#endif

#define DEMYSTIFY_INCREASE_SPEED            PLA_SCROLL_FWD
#define DEMYSTIFY_DECREASE_SPEED            PLA_SCROLL_BACK
#define DEMYSTIFY_INCREASE_SPEED_REPEAT     PLA_SCROLL_FWD_REPEAT
#define DEMYSTIFY_DECREASE_SPEED_REPEAT     PLA_SCROLL_BACK_REPEAT

#define DEMYSTIFY_ADD_POLYGON               PLA_RIGHT
#define DEMYSTIFY_REMOVE_POLYGON            PLA_LEFT
#define DEMYSTIFY_ADD_POLYGON_REPEAT        PLA_RIGHT_REPEAT
#define DEMYSTIFY_REMOVE_POLYGON_REPEAT     PLA_LEFT_REPEAT

#else

#define DEMYSTIFY_QUIT                      PLA_CANCEL

#define DEMYSTIFY_INCREASE_SPEED            PLA_RIGHT
#define DEMYSTIFY_DECREASE_SPEED            PLA_LEFT
#define DEMYSTIFY_INCREASE_SPEED_REPEAT     PLA_RIGHT_REPEAT
#define DEMYSTIFY_DECREASE_SPEED_REPEAT     PLA_LEFT_REPEAT

#define DEMYSTIFY_ADD_POLYGON               PLA_UP
#define DEMYSTIFY_REMOVE_POLYGON            PLA_DOWN
#define DEMYSTIFY_ADD_POLYGON_REPEAT        PLA_UP_REPEAT
#define DEMYSTIFY_REMOVE_POLYGON_REPEAT     PLA_DOWN_REPEAT

#endif
const struct button_mapping *plugin_contexts[]
= {pla_main_ctx,
#if defined(HAVE_REMOTE_LCD)
    pla_remote_ctx,
#endif
};

#ifdef HAVE_LCD_COLOR
struct line_color
{
    int r,g,b;
    int current_r,current_g,current_b;
};
#endif

/******************************* Globals ***********************************/

/*
 * Compute a new random step to make the point bounce the borders of the screen
 */

static int get_new_step(int step)
{
    if(step>0)
        return -(MIN_STEP_RANGE + rb->rand() % (MAX_STEP_RANGE-MIN_STEP_RANGE));
    else
        return (MIN_STEP_RANGE + rb->rand() % (MAX_STEP_RANGE-MIN_STEP_RANGE));
}

/*
 * Point Stuffs
 */

struct point
{
    int x;
    int y;
};

/*
 * Polygon Stuffs
 */

struct polygon
{
    struct point points[NB_POINTS];
};

/*
 * Generates a random polygon (which fits the screen size though)
 */
static void polygon_init(struct polygon * polygon, struct screen * display)
{
    int i;
    for(i=0;i<NB_POINTS;++i)
    {
        polygon->points[i].x=(rb->rand() % (display->getwidth()));
        polygon->points[i].y=(rb->rand() % (display->getheight()));
    }
}

/*
 * Draw the given polygon onto the screen
 */

static void polygon_draw(struct polygon * polygon, struct screen * display)
{
    int i;
    for(i=0;i<NB_POINTS-1;++i)
    {
        display->drawline(polygon->points[i].x, polygon->points[i].y,
                         polygon->points[i+1].x, polygon->points[i+1].y);
    }
    display->drawline(polygon->points[0].x, polygon->points[0].y,
                     polygon->points[NB_POINTS-1].x,
                     polygon->points[NB_POINTS-1].y);
}

/*
 * Polygon moving data Stuffs
 */

struct polygon_move
{
    struct point move_steps[NB_POINTS];
};

static void polygon_move_init(struct polygon_move * polygon_move)
{
    int i;
    for(i=0;i<NB_POINTS;++i)
    {
        polygon_move->move_steps[i].x=get_new_step(-1); 
        /* -1 because we want a positive random step */
        polygon_move->move_steps[i].y=get_new_step(-1);
    }
}

/*
 * Update the given polygon's position according to the given informations in
 * polygon_move (polygon_move may be updated)
 */
static void polygon_update(struct polygon *polygon, struct screen * display, 
                           struct polygon_move *polygon_move)
{
    int i, x, y, step;
    for(i=0;i<NB_POINTS;++i)
    {
        x=polygon->points[i].x;
        step=polygon_move->move_steps[i].x;
        x+=step;
        if(x<=0)
        {
            x=1;
            polygon_move->move_steps[i].x=get_new_step(step);
        }
        else if(x>=display->getwidth())
        {
            x=display->getwidth()-1;
            polygon_move->move_steps[i].x=get_new_step(step);
        }
        polygon->points[i].x=x;

        y=polygon->points[i].y;
        step=polygon_move->move_steps[i].y;
        y+=step;
        if(y<=0)
        {
            y=1;
            polygon_move->move_steps[i].y=get_new_step(step);
        }
        else if(y>=display->getheight())
        {
            y=display->getheight()-1;
            polygon_move->move_steps[i].y=get_new_step(step);
        }
        polygon->points[i].y=y;
    }
}

/*
 * Polygon fifo Stuffs
 */

struct polygon_fifo
{
    int fifo_tail;
    int fifo_head;
    int nb_items;
    struct polygon tab[MAX_POLYGONS];
};

static void fifo_init(struct polygon_fifo * fifo)
{
    fifo->fifo_tail=0;
    fifo->fifo_head=0;
    fifo->nb_items=0;
}

static void fifo_push(struct polygon_fifo * fifo, struct polygon * polygon)
{
    if(fifo->nb_items>=MAX_POLYGONS)
        return;
    ++(fifo->nb_items);

    /*
     * Workaround for gcc (which uses memcpy internally) to avoid link error
     * fifo->tab[fifo->fifo_head]=polygon
     */
    rb->memcpy(&(fifo->tab[fifo->fifo_head]), polygon, sizeof(struct polygon));
    ++(fifo->fifo_head);
    if(fifo->fifo_head>=MAX_POLYGONS)
        fifo->fifo_head=0;
}

static struct polygon * fifo_pop(struct polygon_fifo * fifo)
{
    int index;
    if(fifo->nb_items==0)
        return(NULL);
    --(fifo->nb_items);
    index=fifo->fifo_tail;
    ++(fifo->fifo_tail);
    if(fifo->fifo_tail>=MAX_POLYGONS)
        fifo->fifo_tail=0;
    return(&(fifo->tab[index]));
}

/*
 * Drawing stuffs
 */

static void polygons_draw(struct polygon_fifo * polygons, struct screen * display)
{
    int i, j;
    for(i=0, j=polygons->fifo_tail;i<polygons->nb_items;++i, ++j)
    {
        if(j>=MAX_POLYGONS)
            j=0;
        polygon_draw(&(polygons->tab[j]), display);
    }
}

static void cleanup(void)
{

    backlight_use_settings();
    remote_backlight_use_settings();

}

#ifdef HAVE_LCD_COLOR
static void color_randomize(struct line_color * color)
{
    color->r = rb->rand()%255;
    color->g = rb->rand()%255;
    color->b = rb->rand()%255;
}

static void color_init(struct line_color * color)
{
    color_randomize(color);
    color->current_r=color->r;
    color->current_g=color->g;
    color->current_b=color->b;
}

static void color_change(struct line_color * color)
{
    if(color->current_r<color->r)
        ++color->current_r;
    else if(color->current_r>color->r)
        --color->current_r;
    if(color->current_g<color->g)
        ++color->current_g;
    else if(color->current_g>color->g)
        --color->current_g;
    if(color->current_b<color->b)
        ++color->current_b;
    else if(color->current_b>color->b)
        --color->current_b;

    if(color->current_r==color->r &&
       color->current_g==color->g &&
       color->current_b==color->b)
        color_randomize(color);
}

#define COLOR_RGBPACK(color) \
    LCD_RGBPACK((color)->current_r, (color)->current_g, (color)->current_b)

static void color_apply(struct line_color * color, struct screen * display)
{
    if (display->is_color){
        unsigned foreground=
            SCREEN_COLOR_TO_NATIVE(display,COLOR_RGBPACK(color));
        display->set_foreground(foreground);
    }
}
#endif

/*
 * Main function
 */

static int plugin_main(void)
{
    int action;
    int sleep_time=DEFAULT_WAIT_TIME;
    int nb_wanted_polygons=DEFAULT_NB_POLYGONS;
    struct polygon_fifo polygons[NB_SCREENS];
    struct polygon_move move[NB_SCREENS]; /* This describes the movement of the leading
                                             polygon, the others just follow */
    struct polygon leading_polygon[NB_SCREENS];
    FOR_NB_SCREENS(i)
    {
#ifdef HAVE_LCD_COLOR
        struct screen *display = rb->screens[i];
        if (display->is_color)
            display->set_background(LCD_BLACK);
#endif
        fifo_init(&polygons[i]);
        polygon_move_init(&move[i]);
        polygon_init(&leading_polygon[i], rb->screens[i]);
    }

#ifdef HAVE_LCD_COLOR
    struct line_color color;
    color_init(&color);
#endif

    while (true)
    {
        FOR_NB_SCREENS(i)
        {
            struct screen * display=rb->screens[i];
            if(polygons[i].nb_items>nb_wanted_polygons)
            {   /* We have too many polygons, we must drop some of them */
                fifo_pop(&polygons[i]);
            }
            if(nb_wanted_polygons==polygons[i].nb_items)
            {   /* We have the good number of polygons, we can safely drop 
                the last one to add the new one later */
                fifo_pop(&polygons[i]);
            }
            fifo_push(&polygons[i], &leading_polygon[i]);

            /*
            * Then we update the leading polygon for the next round acording to
            * current move (the move may be altered in case of sreen border 
            * collision)
            */
            polygon_update(&leading_polygon[i], display, &move[i]);

            /* Now the drawing part */
#ifdef HAVE_LCD_COLOR
            color_apply(&color, display);
#endif
            display->clear_display();
            polygons_draw(&polygons[i], display);
            display->update();
        }
#ifdef HAVE_LCD_COLOR
        color_change(&color);
#endif
        /* Speed handling*/
        if (sleep_time<0)/* full speed */
            rb->yield();
        else
            rb->sleep(sleep_time);
        action = pluginlib_getaction(TIMEOUT_NOBLOCK,
                                     plugin_contexts, ARRAYLEN(plugin_contexts));
        switch(action)
        {
            case DEMYSTIFY_QUIT:
                return PLUGIN_OK;

            case DEMYSTIFY_ADD_POLYGON:
            case DEMYSTIFY_ADD_POLYGON_REPEAT:
                if(nb_wanted_polygons<MAX_POLYGONS)
                    ++nb_wanted_polygons;
                break;

            case DEMYSTIFY_REMOVE_POLYGON:
            case DEMYSTIFY_REMOVE_POLYGON_REPEAT:
                if(nb_wanted_polygons>MIN_POLYGONS)
                    --nb_wanted_polygons;
                break;

            case DEMYSTIFY_INCREASE_SPEED:
            case DEMYSTIFY_INCREASE_SPEED_REPEAT:
                if(sleep_time>=0)
                    --sleep_time;
                break;

            case DEMYSTIFY_DECREASE_SPEED:
            case DEMYSTIFY_DECREASE_SPEED_REPEAT:
                ++sleep_time;
                break;

            default:
                exit_on_usb(action);
                break;
        }
    }
}

/*************************** Plugin entry point ****************************/

enum plugin_status plugin_start(const void* parameter)
{
    int ret;

    (void)parameter;
    atexit(cleanup);

#if LCD_DEPTH > 1
    rb->lcd_set_backdrop(NULL);
#endif

    backlight_ignore_timeout();
    remote_backlight_ignore_timeout();

    ret = plugin_main();

    return ret;
}
