/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) Jonathan Gordon (2006)
 *
 * 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 "config.h"
#include "stdarg.h"
#include "string.h"
#include "stdio.h"
#include "kernel.h"
#include "system.h"
#include "screen_access.h"
#include "font.h"
#include "debug.h"
#include "misc.h"
#include "settings.h"
#include "scrollbar.h"
#include "lang.h"
#include "splash.h"
#include "action.h"
#include "icon.h"
#include "color_picker.h"
#include "viewport.h"

/* structure for color info */
struct rgb_pick
{
    unsigned color;                 /* native color value            */
    union
    {
        unsigned char rgb_val[6];   /* access to components as array */
        struct
        {
            unsigned char r;        /* native red value              */
            unsigned char g;        /* native green value            */
            unsigned char b;        /* native blue value             */
            unsigned char red;      /* 8 bit red value               */
            unsigned char green;    /* 8 bit green value             */
            unsigned char blue;     /* 8 bit blue value              */
        } __attribute__ ((__packed__)); /* assume byte packing       */
    };
};


/* list of primary colors */
#define SB_PRIM 0
#define SB_FILL 1
static const unsigned prim_rgb[][3] =
{
    /* Foreground colors for sliders */
    {
        LCD_RGBPACK(255,   0,   0),
        LCD_RGBPACK(  0, 255,   0),
        LCD_RGBPACK(  0,   0, 255),
    },
    /* Fill colors for sliders */
    {
        LCD_RGBPACK( 85,   0,   0),
        LCD_RGBPACK(  0,  85,   0),
        LCD_RGBPACK(  0,   0,  85),
    },
};

/* maximum values for components */
static const unsigned char rgb_max[3] =
{
    LCD_MAX_RED,
    LCD_MAX_GREEN,
    LCD_MAX_BLUE
};

/* Unpacks the color value into native rgb values and 24 bit rgb values */
static void unpack_rgb(struct rgb_pick *rgb)
{
    unsigned color = rgb->color;
    rgb->red   = RGB_UNPACK_RED(color);
    rgb->green = RGB_UNPACK_GREEN(color);
    rgb->blue  = RGB_UNPACK_BLUE(color);
    rgb->r     = RGB_UNPACK_RED_LCD(color);
    rgb->g     = RGB_UNPACK_GREEN_LCD(color);
    rgb->b     = RGB_UNPACK_BLUE_LCD(color);
}

/* Packs the native rgb colors into a color value */
static inline void pack_rgb(struct rgb_pick *rgb)
{
    rgb->color = LCD_RGBPACK_LCD(rgb->r, rgb->g, rgb->b);
}

/* Returns LCD_BLACK if the color is above a threshold brightness
   else return LCD_WHITE */
static inline unsigned get_black_or_white(const struct rgb_pick *rgb)
{
    return (2*rgb->red + 5*rgb->green + rgb->blue) >= 1024 ?
        LCD_BLACK : LCD_WHITE;
}

#define MARGIN_TOP              2 /* Top margin of screen                 */
#define MARGIN_BOTTOM           6 /* Bottom margin of screen              */
#define SLIDER_TEXT_MARGIN      2 /* Gap between text and sliders         */
#define TITLE_MARGIN_BOTTOM     4 /* Space below title bar                */
#define SELECTOR_TB_MARGIN      1 /* Margin on top and bottom of selector */
#define SWATCH_TOP_MARGIN       4 /* Space between last slider and swatch */
#define SELECTOR_WIDTH          get_icon_width(display->screen_type)
#define SELECTOR_HEIGHT         8 /* Height of > and < bitmaps            */

/* dunno why lcd_set_drawinfo should be left out of struct screen */
static void set_drawinfo(struct screen *display, int mode,
                         unsigned foreground, unsigned background)
{
    display->set_drawmode(mode);
    if (display->depth > 1)
    {
        display->set_foreground(foreground);
        display->set_background(background);
    }
}

/* Figure out widest label character in case they vary -
   this function assumes labels are one character */
static int label_get_max_width(struct screen *display)
{
    int max_width, i;
    char buf[4];
    for (i = 0, max_width = 0; i < 3; i++)
    {
        int width;
        buf[0] = str(LANG_COLOR_RGB_LABELS)[i];
        buf[1] = '\0';
        width = display->getstringsize(buf, NULL, NULL);
        if (width > max_width)
            max_width = width;
    }
    return max_width;
}

static void draw_screen(struct screen *display, char *title,
                        struct rgb_pick *rgb, int row)
{
    unsigned  text_color       = LCD_BLACK;
    unsigned  background_color = LCD_WHITE;
    char      buf[32];
    int       i, char_height, line_height;
    int       max_label_width;
    int       text_x, text_top;
    int       slider_x, slider_width;
    int       value_width;
    bool      display_three_rows;
    struct viewport vp;

    viewport_set_defaults(&vp, display->screen_type);
    display->set_viewport(&vp);

    display->clear_viewport();

    if (display->depth > 1)
    {
        text_color       = display->get_foreground();
        background_color = display->get_background();
    }

    /* Draw title string */
    set_drawinfo(display, DRMODE_SOLID, text_color, background_color);
    vp.flags |= VP_FLAG_ALIGN_CENTER;
    display->putsxy(0, MARGIN_TOP, title);

    /* Get slider positions and top starting position */
    max_label_width = label_get_max_width(display);
    char_height  = display->getcharheight();
    text_top     = MARGIN_TOP + char_height +
                   TITLE_MARGIN_BOTTOM + SELECTOR_TB_MARGIN;
    text_x       = SELECTOR_WIDTH;
    slider_x     = text_x + max_label_width + SLIDER_TEXT_MARGIN;
    slider_width = vp.width - text_x - slider_x - SLIDER_TEXT_MARGIN;
    if (display->depth >= 24)
        display->getstringsize("255", &value_width, NULL);
    else
        display->getstringsize("63", &value_width, NULL);
    slider_width -= value_width;
    line_height  = char_height + 2*SELECTOR_TB_MARGIN;

    /* Find out if there's enough room for three sliders or just
       enough to display the selected slider - calculate total height
       of display with three sliders present */
    display_three_rows =
        vp.height >=
            text_top + line_height*3  + /* Title + 3 sliders     */
            SWATCH_TOP_MARGIN         + /* at least 2 lines      */
            char_height*2             + /*  + margins for bottom */
            MARGIN_BOTTOM;              /* colored rectangle     */

    for (i = 0; i < 3; i++)
    {
        unsigned sb_flags = HORIZONTAL;
        int      mode     = DRMODE_SOLID;
        unsigned fg       = text_color;
        unsigned bg       = background_color;

        if (i == row)
        {
            set_drawinfo(display, DRMODE_SOLID, text_color, background_color);

            if (global_settings.cursor_style != 0)
            {
                /* Draw solid bar selection bar */
                display->fillrect(0,
                                  text_top - SELECTOR_TB_MARGIN,
                                  vp.width,
                                  char_height + SELECTOR_TB_MARGIN*2);

                if (display->depth < 16)
                {
                    sb_flags |= FOREGROUND | INNER_FILL;
                    mode     |= DRMODE_INVERSEVID;
                }
            }
            else if (display_three_rows)
            {
                /* Draw ">    <" around sliders */
                int top = text_top + (char_height - SELECTOR_HEIGHT) / 2;
                screen_put_iconxy(display, 0, top, Icon_Cursor);
                screen_put_iconxy(display,
                                  vp.width - SELECTOR_WIDTH,
                                  top, Icon_Cursor);
            }

            if (display->depth >= 16)
            {
                sb_flags |= FOREGROUND | INNER_BGFILL;
                mode      = DRMODE_FG;
                fg        = prim_rgb[SB_PRIM][i];
                bg        = prim_rgb[SB_FILL][i];
            }
        }
        else if (!display_three_rows)
            continue;

        set_drawinfo(display, mode, fg, bg);

        /* Draw label */
        buf[0] = str(LANG_COLOR_RGB_LABELS)[i];
        buf[1] = '\0';
        vp.flags &= ~VP_FLAG_ALIGNMENT_MASK;
        display->putsxy(text_x, text_top, buf);
        /* Draw color value */
        if (display->depth >= 24)
            snprintf(buf, 4, "%03d", rgb->rgb_val[i]);
        else
            snprintf(buf, 3, "%02d", rgb->rgb_val[i]);
        vp.flags |= VP_FLAG_ALIGN_RIGHT;
        display->putsxy(text_x, text_top, buf);

        /* Draw scrollbar */
        gui_scrollbar_draw(display,                     /* screen */
                           slider_x,                    /* x */
                           text_top + char_height / 4,  /* y */
                           slider_width,                /* width */
                           char_height / 2,             /* height */
                           rgb_max[i],                  /* items */
                           0,                           /* min_shown */
                           rgb->rgb_val[i],             /* max_shown */
                           sb_flags);                   /* flags */

        /* Advance to next line */
        text_top += line_height;
    } /* end for */

    /* Format RGB: #rrggbb */
    snprintf(buf, sizeof(buf), str(LANG_COLOR_RGB_VALUE),
                               rgb->red, rgb->green, rgb->blue);
    vp.flags |= VP_FLAG_ALIGN_CENTER;
    if (display->depth >= 16)
    {
        /* Display color swatch on color screens only */
        int top    = text_top + SWATCH_TOP_MARGIN;
        int width  = vp.width - text_x*2;
        int height = vp.height - top - MARGIN_BOTTOM;

        /* Only draw if room */
        if (height >= char_height + 2)
        {
            /* draw the big rectangle */
            display->set_foreground(rgb->color);
            display->fillrect(text_x, top, width, height);

            /* Draw RGB: #rrggbb in middle of swatch */
            set_drawinfo(display, DRMODE_FG, get_black_or_white(rgb),
                         background_color);

            display->putsxy(0, top + (height - char_height) / 2, buf);

            /* Draw border around the rect */
            set_drawinfo(display, DRMODE_SOLID, text_color, background_color);
            display->drawrect(text_x, top, width, height);
        }
    }
    else
    {
        /* Display RGB value only centered on remaining display if room */
        int top = text_top + SWATCH_TOP_MARGIN;
        int height = vp.height - top - MARGIN_BOTTOM;

        if (height >= char_height)
        {
            set_drawinfo(display, DRMODE_SOLID, text_color, background_color);
            display->putsxy(0, top + (height - char_height) / 2, buf);
        }
    }

    display->update_viewport();
    display->set_viewport(NULL);
}

#ifdef HAVE_TOUCHSCREEN
static int touchscreen_slider(struct screen *display,
                                struct rgb_pick *rgb,
                                int *selected_slider)
{
    short     x, y;
    int       char_height, line_height;
    int       max_label_width, value_width;
    int       text_top, slider_x, slider_width;
    bool      display_three_rows;
    int       button;
    int       pressed_slider;
    struct viewport vp;

    viewport_set_defaults(&vp, display->screen_type);
    display->set_viewport(&vp);

    button = action_get_touchscreen_press_in_vp(&x, &y, &vp);
    if (button == ACTION_UNKNOWN || button == BUTTON_NONE)
        return ACTION_NONE;
    /* Get slider positions and top starting position
     * need vp.y here, because of the statusbar, since touchscreen
     * coordinates are absolute */
    max_label_width = label_get_max_width(display);
    char_height  = display->getcharheight();
    text_top     = MARGIN_TOP + char_height +
                   TITLE_MARGIN_BOTTOM + SELECTOR_TB_MARGIN;
    slider_x     = SELECTOR_WIDTH + max_label_width + SLIDER_TEXT_MARGIN;
    slider_width = vp.width - SELECTOR_WIDTH - slider_x - SLIDER_TEXT_MARGIN;
    if (display->depth >= 24)
        display->getstringsize("255", &value_width, NULL);
    else
        display->getstringsize("63", &value_width, NULL);
    slider_width -= value_width;
    line_height  = char_height + 2*SELECTOR_TB_MARGIN;

    /* same logic as in draw_screen */
    /* Find out if there's enough room for three sliders or just
       enough to display the selected slider - calculate total height
       of display with three sliders present */
    display_three_rows =
        vp.height >=
            text_top + line_height*3  + /* Title + 3 sliders     */
            SWATCH_TOP_MARGIN         + /* at least 2 lines      */
            char_height*2             + /*  + margins for bottom */
            MARGIN_BOTTOM;              /* colored rectangle     */

    display->set_viewport(NULL);

    if (y < text_top)
    {
        if (button == BUTTON_REL)
            return ACTION_STD_CANCEL;
        else
            return ACTION_NONE;
    }

    if (y >= text_top + line_height * (display_three_rows ? 3:1))
    {   /* touching the color area means accept */
        if (button == BUTTON_REL)
            return ACTION_STD_OK;
        else
            return ACTION_NONE;
    }
    /* y is relative to the original viewport */
    pressed_slider = (y - text_top)/line_height;
    if (pressed_slider != *selected_slider)
        *selected_slider = pressed_slider;
    /* add max_label_width to overcome integer division limits,
     * cap value later since that may lead to an overflow */
    if (x < slider_x + (slider_width+max_label_width) && x > slider_x)
    {
        char computed_val;
        x -= slider_x;
        computed_val = (x*rgb_max[pressed_slider]/(slider_width));
        rgb->rgb_val[pressed_slider] =
                        MIN(computed_val,rgb_max[pressed_slider]);
        pack_rgb(rgb);
    }
    return ACTION_NONE;
}
#endif
/***********
 set_color
 returns true if USB was inserted, false otherwise
 color is a pointer to the colour (in native format) to modify
 set banned_color to -1 to allow all
 ***********/
bool set_color(struct screen *display, char *title,
               unsigned *color, unsigned banned_color)
{
    int exit = 0, slider = 0;
    struct rgb_pick rgb;

    rgb.color = *color;

    while (!exit)
    {
        int button;

        unpack_rgb(&rgb);

        if (display != NULL)
        {
            draw_screen(display, title, &rgb, slider);
        }
        else
        {
            FOR_NB_SCREENS(i)
                draw_screen(&screens[i], title, &rgb, slider);
        }

        button = get_action(CONTEXT_SETTINGS_COLOURCHOOSER, TIMEOUT_BLOCK);
#ifdef HAVE_TOUCHSCREEN
        if (button == ACTION_TOUCHSCREEN
                && display->screen_type == SCREEN_MAIN)
            button = touchscreen_slider(display, &rgb, &slider);
#endif

        switch (button)
        {
            case ACTION_STD_PREV:
            case ACTION_STD_PREVREPEAT:
                slider = (slider + 2) % 3;
                break;

            case ACTION_STD_NEXT:
            case ACTION_STD_NEXTREPEAT:
                slider = (slider + 1) % 3;
                break;

            case ACTION_SETTINGS_INC:
            case ACTION_SETTINGS_INCREPEAT:
                if (rgb.rgb_val[slider] < rgb_max[slider])
                    rgb.rgb_val[slider]++;
                pack_rgb(&rgb);
                break;

            case ACTION_SETTINGS_DEC:
            case ACTION_SETTINGS_DECREPEAT:
                if (rgb.rgb_val[slider] > 0)
                    rgb.rgb_val[slider]--;
                pack_rgb(&rgb);
                break;

            case ACTION_STD_OK:
                if (banned_color != (unsigned)-1 &&
                    banned_color == rgb.color)
                {
                    splash(HZ*2, ID2P(LANG_COLOR_UNACCEPTABLE));
                    break;
                }
                *color = rgb.color;
                exit = 1;
                break;

            case ACTION_STD_CANCEL:
                exit = 1;
                break;

            default:
                if (default_event_handler(button) == SYS_USB_CONNECTED)
                    return true;
                break;
        }
    }

    return false;
}
