/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2002 Björn Stenberg
 *
 * 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 <stdbool.h>
#include <string.h>
#include <stdio.h>
#include "backlight.h"
#include "button.h"
#include "lcd.h"
#include "lang.h"
#include "icons.h"
#include "font.h"
#include "mpeg.h"
#include "usb.h"
#include "settings.h"
#include "status.h"
#include "playlist.h"
#include "sprintf.h"
#include "kernel.h"
#include "power.h"
#include "system.h"
#include "powermgmt.h"
#include "adc.h"

#ifdef HAVE_LCD_BITMAP
#define BMPHEIGHT_usb_logo 32
#define BMPWIDTH_usb_logo 100
static unsigned char usb_logo[] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08,
    0x04, 0x04, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x81, 0x81, 0x81, 0x81,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0xf1, 0x4f, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0xc0, 
    0x00, 0x00, 0xe0, 0x1c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x06, 0x81, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0,
    0xc0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0x70, 0x38, 0x1c, 0x1c,
    0x0c, 0x0e, 0x0e, 0x06, 0x06, 0x06, 0x06, 0x06, 0x0f, 0x1f, 0x1f, 0x1f, 0x1f,
    0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xc0, 0xc0, 0x80, 0x80, 0x00, 0x00,
    0x00, 0x00, 0xe0, 0x1f, 0x00, 0xf8, 0x06, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
    0x02, 0x02, 0x02, 0x82, 0x7e, 0x00, 0xc0, 0x3e, 0x01, 
    0x70, 0x4f, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
    0x40, 0x40, 0x40, 0x40, 0x40, 0x80, 0x00, 0x07, 0x0f, 0x1f, 0x1f, 0x1f, 0x1f,
    0x0f, 0x07, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x07, 0x0f,
    0x1f, 0x3f, 0x7b, 0xf3, 0xe3, 0xc3, 0x83, 0x83, 0x83, 0x83, 0xe3, 0xe3, 0xe3,
    0xe3, 0xe3, 0xe3, 0x03, 0x03, 0x03, 0x3f, 0x1f, 0x1f, 0x0f, 0x0f, 0x07, 0x02,
    0xc0, 0x3e, 0x01, 0xe0, 0x9f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
    0x80, 0x80, 0xf0, 0x0f, 0x80, 0x78, 0x07, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0c, 0x10, 0x20, 0x40, 0x40, 0x80, 0x80,
    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
    0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x87, 0x87, 0x87,
    0x87, 0x87, 0x87, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xf0,
    0x0f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
    0x04, 0x04, 0x04, 0x04, 0x07, 0x00, 0x00, 0x00, 0x00, 
};
#endif

void usb_display_info(void)
{
    lcd_clear_display();

#ifdef HAVE_LCD_BITMAP
    /* lcd_bitmap() only supports 16 pixels height! */
    lcd_bitmap(usb_logo, 6, 16,
               BMPWIDTH_usb_logo, 8, false);
    lcd_bitmap(usb_logo+BMPWIDTH_usb_logo, 6, 24,
               BMPWIDTH_usb_logo, 8, false);
    lcd_bitmap(usb_logo+BMPWIDTH_usb_logo*2, 6, 32,
               BMPWIDTH_usb_logo, 8, false);
    lcd_bitmap(usb_logo+BMPWIDTH_usb_logo*3, 6, 40,
               BMPWIDTH_usb_logo, 8, false);
    status_draw(true);
    lcd_update();
#else
    lcd_puts(0, 0, "[USB Mode]");
    status_set_param(false);
    status_set_audio(false);
    status_set_usb(true);
    status_draw(false);
#endif
}

void usb_screen(void)
{
#ifndef SIMULATOR
    backlight_on();
    usb_acknowledge(SYS_USB_CONNECTED_ACK);
    usb_display_info();
    while(usb_wait_for_disconnect_w_tmo(&button_queue, HZ)) {
        if(usb_inserted()) {
            status_draw(false);
        }
    }
#ifdef HAVE_LCD_CHARCELLS
    status_set_usb(false);
#endif

    backlight_on();
#endif
}


/* some simulator dummies */
#ifdef SIMULATOR
#define BATTERY_SCALE_FACTOR 7000
unsigned short adc_read(int channel)
{
   (void)channel;
   return 100;
}
#endif


#ifdef HAVE_LCD_BITMAP
void charging_display_info(bool animate)
{
    unsigned char charging_logo[36];
    const int pox_x = (LCD_WIDTH - sizeof(charging_logo)) / 2;
    const int pox_y = 32;
    static unsigned phase = 3;
    unsigned i;
    int battery_voltage;
    int batt_int, batt_frac;
    char buf[32];

    battery_voltage = (adc_read(ADC_UNREG_POWER) * BATTERY_SCALE_FACTOR) / 10000;
    batt_int = battery_voltage / 100;
    batt_frac = battery_voltage % 100;

    snprintf(buf, 32, "  Batt: %d.%02dV %d%%  ", batt_int, batt_frac,
             battery_level());
    lcd_puts(0, 7, buf);

#ifdef HAVE_CHARGE_CTRL

    snprintf(buf, 32, "Charge mode:");
    lcd_puts(0, 2, buf);

    if (charge_state == 1)
        snprintf(buf, 32, str(LANG_BATTERY_CHARGE));
    else if (charge_state == 2)
        snprintf(buf, 32, str(LANG_BATTERY_TOPOFF_CHARGE));
    else if (charge_state == 3)
        snprintf(buf, 32, str(LANG_BATTERY_TRICKLE_CHARGE));
    else
        snprintf(buf, 32, "not charging");

    lcd_puts(0, 3, buf);
    if (charger_enabled)
    {
        backlight_on(); /* using the light gives good indication */
    }
    else
    {
        backlight_off();
        animate = false;
    }
#endif                

    
    /* middle part */
    memset(charging_logo+3, 0x00, 32);
    charging_logo[0] = 0x3C;
    charging_logo[1] = 0x24;
    charging_logo[2] = charging_logo[35] = 0xFF;
    
    if (!animate)
    {   /* draw the outline */
        /* middle part */
        lcd_bitmap(charging_logo, pox_x, pox_y + 8, sizeof(charging_logo), 8, true);
        /* upper line */
        charging_logo[0] = charging_logo[1] = 0x00; 
        memset(charging_logo+2, 0x80, 34);
        lcd_bitmap(charging_logo, pox_x, pox_y, sizeof(charging_logo), 8, false);
        /* lower line */
        memset(charging_logo+2, 0x01, 34);
        lcd_bitmap(charging_logo, pox_x, pox_y + 16, sizeof(charging_logo), 8, false);
    }
    else
    {   /* animate the middle part */
        for (i = 3; i<MIN(sizeof(charging_logo)-1, phase); i++)
        {
            if ((i-phase) % 8 == 0)
            {   /* draw a "bubble" here */
                unsigned bitpos;
                bitpos = (phase + i/8) % 15; /* "bounce" effect */
                if (bitpos > 7) 
                    bitpos = 14 - bitpos;
                charging_logo[i] = 0x01 << bitpos;
            }
        }
        lcd_bitmap(charging_logo, pox_x, pox_y + 8, sizeof(charging_logo), 8, true);
        phase++;
    }
    lcd_update();
}
#else /* not HAVE_LCD_BITMAP */
void charging_display_info(bool animate)
{
    /* ToDo for Player */
    (void)animate;
}
#endif


/* blocks while charging, returns on event:
   1 if charger cable was removed
   2 if Off/Stop key was pressed
   3 if On key was pressed
   4 if USB was connected */
int charging_screen(void)
{
    int button;
    int rc = 0;
#ifdef HAVE_RECORDER_KEYPAD
    const int offbutton = BUTTON_OFF;
#else
    const int offbutton = BUTTON_STOP;
#endif

    ide_power_enable(false); /* power down the disk, else would be spinning */

    lcd_clear_display();
    backlight_on();
    status_draw(true);

#ifdef HAVE_LCD_BITMAP
    charging_display_info(false);
#else
    status_set_playmode(STATUS_STOP);
    lcd_puts(0, 1, "[charging]");
#endif
    
    do
    {
        status_draw(false);
        charging_display_info(true);
        button = button_get_w_tmo(HZ/3);
        if (button == (BUTTON_ON | BUTTON_REL))
            rc = 3;
        else if (button == offbutton)
            rc = 2;
        else
        {
            if (usb_detect())
                rc = 4;
            else if (!charger_inserted())
                rc = 1;
        }
    } while (!rc);

    return rc;
}



#ifdef HAVE_RECORDER_KEYPAD
/* returns:
   0 if no key was pressed
   1 if a key was pressed (or if ON was held down long enough to repeat)
   2 if USB was connected */
int on_screen(void)
{
    static int pitch = 1000;
    bool exit = false;
    bool used = false;

    while (!exit) {

        if ( used ) {
            char* ptr;
            char buf[32];
            int w, h;

            lcd_clear_display();
            lcd_setfont(FONT_SYSFIXED);
    
            ptr = str(LANG_PITCH_UP);
            lcd_getstringsize(ptr,&w,&h);
            lcd_putsxy((LCD_WIDTH-w)/2, 0, ptr);
            lcd_bitmap(bitmap_icons_7x8[Icon_UpArrow],
                       LCD_WIDTH/2 - 3, h*2, 7, 8, true);

            snprintf(buf, sizeof buf, "%d.%d%%", pitch / 10, pitch % 10 );
            lcd_getstringsize(buf,&w,&h);
            lcd_putsxy((LCD_WIDTH-w)/2, h, buf);

            ptr = str(LANG_PITCH_DOWN);
            lcd_getstringsize(ptr,&w,&h);
            lcd_putsxy((LCD_WIDTH-w)/2, LCD_HEIGHT - h, ptr);
            lcd_bitmap(bitmap_icons_7x8[Icon_DownArrow],
                       LCD_WIDTH/2 - 3, LCD_HEIGHT - h*3, 7, 8, true);

            ptr = str(LANG_PAUSE);
            lcd_getstringsize(ptr,&w,&h);
            lcd_putsxy((LCD_WIDTH-(w/2))/2, LCD_HEIGHT/2 - h/2, ptr);
            lcd_bitmap(bitmap_icons_7x8[Icon_Pause],
                       (LCD_WIDTH-(w/2))/2-10, LCD_HEIGHT/2 - h/2, 7, 8, true);

            lcd_update();
        }

        /* use lastbutton, so the main loop can decide whether to
           exit to browser or not */
        switch (button_get(true)) {
            case BUTTON_UP:
            case BUTTON_ON | BUTTON_UP:
            case BUTTON_ON | BUTTON_UP | BUTTON_REPEAT:
                used = true;
                pitch++;
                if ( pitch > 2000 )
                    pitch = 2000;
                mpeg_set_pitch(pitch);
                break;

            case BUTTON_DOWN:
            case BUTTON_ON | BUTTON_DOWN:
            case BUTTON_ON | BUTTON_DOWN | BUTTON_REPEAT:
                used = true;
                pitch--;
                if ( pitch < 500 )
                    pitch = 500;
                mpeg_set_pitch(pitch);
                break;

            case BUTTON_ON | BUTTON_PLAY:
                mpeg_pause();
                used = true;
                break;

            case BUTTON_PLAY | BUTTON_REL:
                mpeg_resume();
                used = true;
                break;

            case BUTTON_ON | BUTTON_PLAY | BUTTON_REL:
                mpeg_resume();
                exit = true;
                break;

            case BUTTON_ON | BUTTON_RIGHT:
                if ( pitch < 2000 ) {
                    pitch += 20;
                    mpeg_set_pitch(pitch);
                }
                break;

            case BUTTON_RIGHT | BUTTON_REL:
                if ( pitch > 500 ) {
                    pitch -= 20;
                    mpeg_set_pitch(pitch);
                }
                break;

            case BUTTON_ON | BUTTON_LEFT:
                if ( pitch > 500 ) {
                    pitch -= 20;
                    mpeg_set_pitch(pitch);
                }
                break;

            case BUTTON_LEFT | BUTTON_REL:
                if ( pitch < 2000 ) {
                    pitch += 20;
                    mpeg_set_pitch(pitch);
                }
                break;

#ifdef SIMULATOR
            case BUTTON_ON:
#else
            case BUTTON_ON | BUTTON_REL:
            case BUTTON_ON | BUTTON_UP | BUTTON_REL:
            case BUTTON_ON | BUTTON_DOWN | BUTTON_REL:
#endif
                exit = true;
                break;

            case BUTTON_ON | BUTTON_REPEAT:
                used = true;
                break;

            case SYS_USB_CONNECTED:
                usb_screen();
                return 2;
        }
    }

    lcd_setfont(FONT_UI);

    if ( used )
        return 1;
    else
        return 0;
}

bool f2_screen(void)
{
    bool exit = false;
    bool used = false;
    int w, h;
    char buf[32];
    int oldrepeat = global_settings.repeat_mode;

    lcd_setfont(FONT_SYSFIXED);
    lcd_getstringsize("A",&w,&h);

    while (!exit) {
        char* ptr=NULL;

        lcd_clear_display();

        /* Shuffle mode */
        lcd_putsxy(0, LCD_HEIGHT/2 - h*2, str(LANG_SHUFFLE));
        lcd_putsxy(0, LCD_HEIGHT/2 - h, str(LANG_F2_MODE));
        lcd_putsxy(0, LCD_HEIGHT/2, 
                   global_settings.playlist_shuffle ? 
                   str(LANG_ON) : str(LANG_OFF));
        lcd_bitmap(bitmap_icons_7x8[Icon_FastBackward], 
                   LCD_WIDTH/2 - 16, LCD_HEIGHT/2 - 4, 7, 8, true);

        /* Directory Filter */
        switch ( global_settings.dirfilter ) {
            case SHOW_ALL:
                ptr = str(LANG_FILTER_ALL);
                break;

            case SHOW_SUPPORTED:
                ptr = str(LANG_FILTER_SUPPORTED);
                break;

            case SHOW_MUSIC:
                ptr = str(LANG_FILTER_MUSIC);
                break;
                
            case SHOW_PLAYLIST:
                ptr = str(LANG_FILTER_PLAYLIST);
                break;
        }

        snprintf(buf, sizeof buf, "%s:", str(LANG_FILTER));
        lcd_getstringsize(buf,&w,&h);
        lcd_putsxy((LCD_WIDTH-w)/2, LCD_HEIGHT - h*2, buf);
        lcd_getstringsize(ptr,&w,&h);
        lcd_putsxy((LCD_WIDTH-w)/2, LCD_HEIGHT - h, ptr);
        lcd_bitmap(bitmap_icons_7x8[Icon_DownArrow],
                   LCD_WIDTH/2 - 3, LCD_HEIGHT - h*3, 7, 8, true);

        /* Repeat Mode */
        switch ( global_settings.repeat_mode ) {
            case REPEAT_OFF:
                ptr = str(LANG_OFF);
                break;

            case REPEAT_ALL:
                ptr = str(LANG_REPEAT_ALL);
                break;

            case REPEAT_ONE:
                ptr = str(LANG_REPEAT_ONE);
                break;
        }

        lcd_getstringsize(str(LANG_REPEAT),&w,&h);
        lcd_putsxy(LCD_WIDTH - w, LCD_HEIGHT/2 - h*2, str(LANG_REPEAT));
        lcd_putsxy(LCD_WIDTH - w, LCD_HEIGHT/2 - h, str(LANG_F2_MODE));
        lcd_putsxy(LCD_WIDTH - w, LCD_HEIGHT/2, ptr);
        lcd_bitmap(bitmap_icons_7x8[Icon_FastForward], 
                   LCD_WIDTH/2 + 8, LCD_HEIGHT/2 - 4, 7, 8, true);

        lcd_update();

        switch (button_get(true)) {
            case BUTTON_LEFT:
            case BUTTON_F2 | BUTTON_LEFT:
                global_settings.playlist_shuffle =
                    !global_settings.playlist_shuffle;

                if(mpeg_status() & MPEG_STATUS_PLAY)
                {
                    if (global_settings.playlist_shuffle)
                        playlist_randomise(current_tick, true);
                    else
                        playlist_sort(true);
                }
                used = true;
                break;

            case BUTTON_DOWN:
            case BUTTON_F2 | BUTTON_DOWN:
                global_settings.dirfilter++;
                if ( global_settings.dirfilter >= NUM_FILTER_MODES )
                    global_settings.dirfilter = 0;
                used = true;
                break;

            case BUTTON_RIGHT:
            case BUTTON_F2 | BUTTON_RIGHT:
                global_settings.repeat_mode++;
                if ( global_settings.repeat_mode >= NUM_REPEAT_MODES )
                    global_settings.repeat_mode = 0;
                used = true;
                break;

            case BUTTON_F2 | BUTTON_REL:
                if ( used )
                    exit = true;
                used = true;
                break;

            case BUTTON_F2 | BUTTON_REPEAT:
                used = true;
                break;

            case SYS_USB_CONNECTED:
                usb_screen();
                return true;
        }
    }

    settings_save();
    lcd_setfont(FONT_UI);
    if ( oldrepeat != global_settings.repeat_mode )
        mpeg_flush_and_reload_tracks();

    return false;
}

bool f3_screen(void)
{
    bool exit = false;
    bool used = false;

    lcd_setfont(FONT_SYSFIXED);

    while (!exit) {
        int w,h;
        char* ptr;

        ptr = str(LANG_F3_STATUS);
        lcd_getstringsize(ptr,&w,&h);
        lcd_clear_display();

        lcd_putsxy(0, LCD_HEIGHT/2 - h*2, str(LANG_F3_SCROLL));
        lcd_putsxy(0, LCD_HEIGHT/2 - h, str(LANG_F3_BAR));
        lcd_putsxy(0, LCD_HEIGHT/2, 
                   global_settings.scrollbar ? str(LANG_ON) : str(LANG_OFF));
        lcd_bitmap(bitmap_icons_7x8[Icon_FastBackward], 
                   LCD_WIDTH/2 - 16, LCD_HEIGHT/2 - 4, 7, 8, true);

        lcd_putsxy(LCD_WIDTH - w, LCD_HEIGHT/2 - h*2, ptr);
        lcd_putsxy(LCD_WIDTH - w, LCD_HEIGHT/2 - h, str(LANG_F3_BAR));
        lcd_putsxy(LCD_WIDTH - w, LCD_HEIGHT/2, 
                   global_settings.statusbar ? str(LANG_ON) : str(LANG_OFF));
        lcd_bitmap(bitmap_icons_7x8[Icon_FastForward], 
                   LCD_WIDTH/2 + 8, LCD_HEIGHT/2 - 4, 7, 8, true);

        /* Invert */
        lcd_putsxy((LCD_WIDTH-w)/2, LCD_HEIGHT - h*2, str(LANG_INVERT));
        lcd_putsxy((LCD_WIDTH-w)/2, LCD_HEIGHT - h, 
                   global_settings.invert ?
                   str(LANG_INVERT_LCD_INVERSE) : str(LANG_INVERT_LCD_NORMAL));
        lcd_bitmap(bitmap_icons_7x8[Icon_DownArrow],
                   LCD_WIDTH/2 - 3, LCD_HEIGHT - h*3, 7, 8, true);

        lcd_update();

        switch (button_get(true)) {
            case BUTTON_LEFT:
            case BUTTON_F3 | BUTTON_LEFT:
                global_settings.scrollbar = !global_settings.scrollbar;
                used = true;
                break;

            case BUTTON_RIGHT:
            case BUTTON_F3 | BUTTON_RIGHT:
                global_settings.statusbar = !global_settings.statusbar;
                used = true;
                break;

            case BUTTON_DOWN:
            case BUTTON_F3 | BUTTON_DOWN:
                global_settings.invert = !global_settings.invert;
                lcd_set_invert_display(global_settings.invert);
                used = true;
                break;

            case BUTTON_F3 | BUTTON_REL:
                if ( used )
                    exit = true;
                used = true;
                break;

            case BUTTON_F3 | BUTTON_REPEAT:
                used = true;
                break;

            case SYS_USB_CONNECTED:
                usb_screen();
                return true;
        }
    }

    settings_save();
    if (global_settings.statusbar)
        lcd_setmargins(0, STATUSBAR_HEIGHT);
    else
        lcd_setmargins(0, 0);
    lcd_setfont(FONT_UI);

    return false;
}
#endif

#ifdef HAVE_LCD_BITMAP
#define SPACE 3 /* pixels between words */
#define MAXLETTERS 128 /* 16*8 */
#define MAXLINES 10
#else
#define SPACE 1 /* one letter space */
#undef LCD_WIDTH
#define LCD_WIDTH 11
#undef LCD_HEIGHT
#define LCD_HEIGHT 2
#define MAXLETTERS 22 /* 11 * 2 */
#define MAXLINES 2
#endif

void splash(int ticks,   /* how long */
            int keymask, /* what keymask aborts the waiting (if any) */
            bool center, /* FALSE means left-justified, TRUE means
                            horizontal and vertical center */
            char *fmt,   /* what to say *printf style */
            ...)
{
    char *next;
    char *store=NULL;
    int x=0;
    int y=0;
    int w, h;
    unsigned char splash_buf[MAXLETTERS];
    va_list ap;
    unsigned char widths[MAXLINES];
    int line=0;
    bool first=true;
#ifdef HAVE_LCD_BITMAP
    int maxw=0;
#endif

    va_start( ap, fmt );
    vsnprintf( splash_buf, sizeof(splash_buf), fmt, ap );

    if(center) {
        
        /* first a pass to measure sizes */
        next = strtok_r(splash_buf, " ", &store);
        while (next) {
#ifdef HAVE_LCD_BITMAP
            lcd_getstringsize(next, &w, &h);
#else
            w = strlen(next);
            h = 1; /* store height in characters */
#endif
            if(!first) {
                if(x+w> LCD_WIDTH) {
                    /* Too wide, wrap */
                    y+=h;
                    line++;
                    if((y > (LCD_HEIGHT-h)) || (line > MAXLINES))
                        /* STOP */
                        break;
                    x=0;
                    first=true;
                }
            }
            else
                first = false;

            /* think of it as if the text was written here at position x,y
               being w pixels/chars wide and h high */

            x += w+SPACE;
            widths[line]=x-SPACE; /* don't count the trailing space */
#ifdef HAVE_LCD_BITMAP
            /* store the widest line */
            if(widths[line]>maxw)
                maxw = widths[line];
#endif
            next = strtok_r(NULL, " ", &store);
        }
#ifdef HAVE_LCD_BITMAP
        /* Start displaying the message at position y. The reason for the
           added h here is that it isn't added until the end of lines in the
           loop above and we always break the loop in the middle of a line. */
        y = (LCD_HEIGHT - (y+h) )/2;
#else
        y = 0; /* vertical center on 2 lines would be silly */
#endif
        first=true;

        /* Now recreate the string again since the strtok_r() above has ruined
           the one we already have! Here's room for improvements! */
        vsnprintf( splash_buf, sizeof(splash_buf), fmt, ap );
    }
    va_end( ap );

    if(center)
    {
        x = (LCD_WIDTH-widths[0])/2;
        if(x < 0)
            x = 0;
    }

#ifdef HAVE_LCD_BITMAP
    /* If we center the display and it wouldn't cover the full screen,
       then just clear the box we need and put a nice little frame and
       put the text in there! */
    if(center && (y > 2)) {
        if(maxw < (LCD_WIDTH -4)) {
            int xx = (LCD_WIDTH-maxw)/2 - 2;
            lcd_clearrect(xx, y-2, maxw+4, LCD_HEIGHT-y*2+4);
            lcd_drawrect(xx, y-2, maxw+4, LCD_HEIGHT-y*2+4);
        }
        else {
            lcd_clearrect(0, y-2, LCD_WIDTH, LCD_HEIGHT-y*2+4);
            lcd_drawline(0, y-2, LCD_WIDTH-1, y-2);
            lcd_drawline(0, LCD_HEIGHT-y+2, LCD_WIDTH-1, LCD_HEIGHT-y+2);
        }
    }
    else
#endif
        lcd_clear_display();
    line=0;
    next = strtok_r(splash_buf, " ", &store);
    while (next) {
#ifdef HAVE_LCD_BITMAP
        lcd_getstringsize(next, &w, &h);
#else
        w = strlen(next);
        h = 1;
#endif
        if(!first) {
            if(x+w> LCD_WIDTH) {
                /* too wide */
                y+=h;
                line++; /* goto next line */
                first=true;
                if(y > (LCD_HEIGHT-h))
                    /* STOP */
                    break;
                if(center)
                    x = (LCD_WIDTH-widths[line])/2;
                else
                    x=0;
            }
        }
        else
            first=false;
#ifdef HAVE_LCD_BITMAP
        lcd_putsxy(x, y, next);
#else
        lcd_puts(x, y, next);
#endif
        x += w+SPACE; /*  pixels space! */
        next = strtok_r(NULL, " ", &store);
    }
    lcd_update();

    if(ticks) {
        if(keymask) {
            int start = current_tick;
            int done = ticks + current_tick + 1;
            while (TIME_BEFORE( current_tick, done)) {
                int button = button_get_w_tmo(ticks - (current_tick-start));
                if((button & keymask) == keymask)
                    break;
            }
        }
        else
            /* unbreakable! */
            sleep(ticks);
    }
}

