/***************************************************************************
                          gui_edit.c  -  description
                             -------------------
    begin                : Wed Oct 16 2002
    copyright            : (C) 2002 by Michael Speck
    email                : kulkanie@gmx.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include <stdlib.h>
#include <string.h>
#include "gui_widget.h"
#include "gui_edit.h"

extern GuiTheme *gui_theme;
extern SDL_Surface *stk_display;
extern GuiWidget *gui_key_widget;

/*
====================================================================
LOCALS
====================================================================
*/

/*
====================================================================
Global edit variables that are used by the focused widget and
are global as there is only one keyboard they can be related
with.
====================================================================
*/
static int gui_edit_blink_time = 0; /* used to toggle gui_edit_blink
                                twice a second */
static int gui_edit_blink = 0; /* if true a cursor is displayed at 
                           the edit position */
static int gui_edit_delay = 0; 
static int gui_edit_interval = 0; /* after a delay of 300 ms the
keyevent is repeated at an interval of 100 */
static int gui_edit_keysym = -1; /* last pressed keysym */
static int gui_edit_unicode = -1; /* last pressed unicode */

/* FORWARDED */
static void gui_edit_handle_key( 
    GuiWidget *widget, int keysym, int unicode );
static int gui_edit_adjust_cursor( GuiWidget *widget, int offset );
    
/*
====================================================================
Default event handler
====================================================================
*/
static void default_event_handler( 
    GuiWidget *widget, GuiEvent *event )
{
    char *ptr;
    int px, py, i;
    switch ( event->type ) {
        case GUI_DESTROY:
            if ( widget->spec.edit.buffer )
                free( widget->spec.edit.buffer );
            if ( widget->spec.edit.display_buffer )
                free( widget->spec.edit.display_buffer );
            break;
        case GUI_DRAW:
            /* display surface */
            stk_surface_blit( 
                widget->surface, 0, 0, -1, -1, 
                stk_display, 
                widget->screen_region.x,
                widget->screen_region.y );
            /* add text */
            gui_theme->edit_font->align = STK_FONT_ALIGN_LEFT;
            ptr = widget->spec.edit.display_buffer;
            px = widget->screen_region.x + widget->border;
            py = widget->screen_region.y + widget->border + 
                 widget->spec.edit.y_offset;
            for ( i = 0; i < widget->spec.edit.height; 
                  i++, py += gui_theme->edit_font->height,
                  ptr += widget->spec.edit.width + 1 )
                stk_font_write( gui_theme->edit_font,
                    stk_display, px, py, -1, ptr );
            /* add cursor */
            if ( widget == gui_key_widget || widget->focused )
            if ( gui_edit_blink )
                stk_surface_fill( stk_display, 
                    px + widget->spec.edit.x * 
                    gui_theme->edit_font->width,
                    widget->screen_region.y + 
                    widget->border + widget->spec.edit.y_offset + 
                    widget->spec.edit.y * 
                    gui_theme->edit_font->height,
                    1, gui_theme->edit_font->height,
                    0xffffff );
            break;
        case GUI_KEY_PRESSED:
            if ( gui_edit_keysym != event->key.keysym ) {
                gui_edit_delay = 250;
                gui_edit_handle_key( widget, 
                    event->key.keysym,
                    event->key.unicode );
                //stk_sound_play( gui_theme->type_sound );
            }
            break;
        case GUI_KEY_RELEASED:
            gui_edit_keysym = -1;
            break;
        case GUI_FOCUS_OUT:
            gui_edit_blink = 0;
            gui_widget_draw( widget );
            break;
        case GUI_CLICKED:
            stk_sound_play( gui_theme->click_sound );
            px = event->button.x - widget->screen_region.x - 
                widget->border;
            py = event->button.y - widget->screen_region.y -
                widget->border - widget->spec.edit.y_offset;
            gui_edit_adjust_cursor( widget, 
                (py / gui_theme->edit_font->height) * 
                widget->spec.edit.width + 
                (px / gui_theme->edit_font->width) + 
                widget->spec.edit.start - widget->spec.edit.pos + 1 );
            gui_edit_blink = 1;
            gui_edit_blink_time = 0;
            gui_widget_draw( widget );
            break;
    }
}

/*
====================================================================
Render the display buffer from the edit string and the first
character displayed of it. No screen update.
====================================================================
*/
static void gui_edit_update_display_buffer( GuiWidget *widget )
{
    int i, length, left;
    char *src, *dest;
    memset( widget->spec.edit.display_buffer, 0, 
        (widget->spec.edit.width+1) * widget->spec.edit.height *
        sizeof( char ) );
    if ( widget->spec.edit.multi_line ) {
        src = widget->spec.edit.buffer + widget->spec.edit.start;
        left = strlen( src );
        dest = widget->spec.edit.display_buffer;
        for ( i = 0; i < widget->spec.edit.height; i++ ) {
            length = widget->spec.edit.width;
            if ( length > left )
                length = left;
            strncpy( dest, src, length );
            dest += widget->spec.edit.width + 1;
            src += length;
            left -= length;
            if ( left == 0 ) 
                break;
        }
    }
    else {
        strncpy( widget->spec.edit.display_buffer, 
            widget->spec.edit.buffer + widget->spec.edit.start,
            widget->spec.edit.width );
    }
}
/*
====================================================================
Adjust edit cursor position by offset and return True 
if the display buffer has changed. Do not update display.
====================================================================
*/
static int gui_edit_adjust_cursor( GuiWidget *widget, int offset )
{
    int new_y, changed = 0;
    /* adjust position */
    widget->spec.edit.pos += offset;
    if ( widget->spec.edit.pos < 0 ) widget->spec.edit.pos = 0;
    if ( widget->spec.edit.pos > widget->spec.edit.length )
        widget->spec.edit.pos = widget->spec.edit.length;
    /* don't blink cursor while moving */
    gui_edit_blink_time = 0; 
    /* update display position */
    if ( widget->spec.edit.multi_line ) {
        new_y = widget->spec.edit.pos / widget->spec.edit.width;
        /* if this position is out of screen modify 'line'
           and 'start' */
        if ( new_y < widget->spec.edit.line || 
             new_y >= widget->spec.edit.line + widget->spec.edit.height ) {
            if ( new_y < widget->spec.edit.line )
                widget->spec.edit.line = new_y;
            else
                widget->spec.edit.line = new_y - widget->spec.edit.height + 1;
            widget->spec.edit.start = 
                widget->spec.edit.line * widget->spec.edit.width;
            /* update the display buffer */
            gui_edit_update_display_buffer( widget );
            changed = 1;
        }
        widget->spec.edit.x = 
            widget->spec.edit.pos % widget->spec.edit.width;
        widget->spec.edit.y = new_y - widget->spec.edit.line;
    }
    else {
        if ( widget->spec.edit.pos < widget->spec.edit.start ) {
            widget->spec.edit.start = widget->spec.edit.pos;
            widget->spec.edit.x = 0;
            /* update the display buffer */
            gui_edit_update_display_buffer( widget );
            changed = 1;
        }
        else
        if ( widget->spec.edit.pos >= 
                widget->spec.edit.start + widget->spec.edit.width ) {
            widget->spec.edit.start = widget->spec.edit.pos - 
                widget->spec.edit.width;
            widget->spec.edit.x = widget->spec.edit.width;
            /* update the display buffer */
            gui_edit_update_display_buffer( widget );
            changed = 1;
        }
        else {
            /* within text */
            widget->spec.edit.x = 
                widget->spec.edit.pos - widget->spec.edit.start;
        }
    }
    return changed;
}
/*
====================================================================
Handle the pressed key, modify the edit buffer and update the
displayed edit if visible.
====================================================================
*/
static void gui_edit_handle_key( 
    GuiWidget *widget, int keysym, int unicode )
{
    int i, changed = 0, old_length = widget->spec.edit.length;
    switch ( keysym ) {
        case SDLK_RIGHT:
            gui_edit_adjust_cursor( widget, 1 );
            changed = 1;
            break;
        case SDLK_LEFT:
            gui_edit_adjust_cursor( widget, -1 );
            changed = 1;
            break;
        case SDLK_DOWN:
            gui_edit_adjust_cursor( 
                widget, widget->spec.edit.width );
            changed = 1;
            break;
        case SDLK_UP:
            gui_edit_adjust_cursor( 
                widget, -widget->spec.edit.width );
            changed = 1;
            break;
        case SDLK_HOME:
            gui_edit_adjust_cursor( 
                widget, -widget->spec.edit.pos );
            changed = 1;
            break;
        case SDLK_END:
            widget->spec.edit.pos = 0;
            gui_edit_adjust_cursor( 
                widget, widget->spec.edit.length );
            changed = 1;
            break;
        case SDLK_BACKSPACE:
            if ( widget->spec.edit.pos > 0 ) {
                --widget->spec.edit.pos;
                for ( i = widget->spec.edit.pos; 
                      i < widget->spec.edit.length - 1; i++ )
                    widget->spec.edit.buffer[i] = 
                        widget->spec.edit.buffer[i + 1];
                widget->spec.edit.buffer[i] = 0;
                widget->spec.edit.length--;
                if ( !gui_edit_adjust_cursor( widget, 0 ) )
                    gui_edit_update_display_buffer( widget );
                changed = 1;
            }
            break;
        case SDLK_DELETE:
            if ( widget->spec.edit.pos < widget->spec.edit.length ) {
                for ( i = widget->spec.edit.pos; 
                      i < widget->spec.edit.length - 1; i++ )
                    widget->spec.edit.buffer[i] = 
                        widget->spec.edit.buffer[i + 1];
                widget->spec.edit.buffer[i] = 0;
                widget->spec.edit.length--;
                gui_edit_update_display_buffer( widget );
                changed = 1;
            }
            break;
        default:
            if ( widget->spec.edit.filter[unicode] && widget->spec.edit.pos < widget->spec.edit.size )
                if ( widget->spec.edit.length < widget->spec.edit.size  ) {
                    for ( i = widget->spec.edit.size - 1; 
                          i > widget->spec.edit.pos; i-- )
                        widget->spec.edit.buffer[i] = 
                            widget->spec.edit.buffer[i - 1];
                    widget->spec.edit.buffer[widget->spec.edit.pos++] = unicode;
                    widget->spec.edit.length++;
                    if ( !gui_edit_adjust_cursor( widget, 0 ) )
                        gui_edit_update_display_buffer( widget );
                    changed = 1;
                }
        break;
    }
    if ( changed ) {
        gui_edit_blink_time = 0;
        gui_edit_blink = 1;
        gui_widget_draw( widget );
        gui_edit_interval = 0;
        gui_edit_keysym = keysym;
        gui_edit_unicode = unicode;
        if ( old_length != widget->spec.edit.length ) {
            gui_widget_call_user_event_handler( 
                widget, gui_event_get_simple( GUI_CHANGED ) );
        }
    }
    else
        gui_edit_keysym = -1;
}

/*
====================================================================
PUBLICS
====================================================================
*/

/*
====================================================================
Create a multi-line editable widget. By checking the measurements 
of the standard font the number of lines and the line width is 
determined.
  'text': is copied and displayed when first shown
  'size': limit of characters (excluding the \0)
====================================================================
*/
GuiWidget* gui_edit_create(
    GuiWidget *parent, int x, int y, int width, int height,
    void (*user_event_handler)(GuiWidget*,GuiEvent*),
    int border, int multi_line, int size, char *text )
{
    GuiWidget *widget = gui_widget_create( 
        parent, GUI_EDIT, x, y, width, height, 
        default_event_handler, user_event_handler );
    /* events */
    gui_widget_enable_event( widget, GUI_CHANGED );
    /* create surface, wallpaper and frame it */
    widget->surface = stk_surface_create( 
        SDL_SWSURFACE, width, height );
    SDL_SetColorKey( widget->surface, 0,0 );
    stk_surface_apply_wallpaper( widget->surface, 0,0,-1,-1,
        gui_theme->widget_wallpaper, STK_OPAQUE );
    widget->border = stk_surface_apply_frame(
        widget->surface, 0, 0, -1, -1, 
        gui_theme->widget_frame );
    /* size w/o border */
    widget->width -= 2 * widget->border;
    widget->height -= 2 * widget->border;
    /* grab keys */
    widget->grab_keys = 1;
    /* prepare text */
    gui_edit_set_filter( widget, GUI_EDIT_DEFAULT );
    widget->spec.edit.multi_line = multi_line;
    widget->border += border;
    widget->spec.edit.width = 
        (width - widget->border*2) / 
         gui_theme->edit_font->width;
    if ( multi_line )
        widget->spec.edit.height = 
            (height - widget->border*2) / 
             gui_theme->edit_font->height;
    else
        widget->spec.edit.height = 1;
    widget->spec.edit.display_buffer = 
        calloc( (widget->spec.edit.width+1) * widget->spec.edit.height, 
            sizeof( char ) );
    if ( widget->spec.edit.display_buffer == 0 )
        GUI_ABORT( "Out Of Memory" );
    gui_edit_resize_buffer( widget, size );
    gui_edit_set_text( widget, text );
    /* center single-line edit */
    if ( !multi_line )
        widget->spec.edit.y_offset = 
            (height - 2 * widget->border - 
            gui_theme->edit_font->height) / 2;
    /* done */
    return widget;
}

/*
====================================================================
Resize the edit buffer and clear any text. (no update)
====================================================================
*/
void gui_edit_resize_buffer( GuiWidget *widget, int size )
{
    if ( widget->type != GUI_EDIT ) return;
    widget->spec.edit.size = size;
    if ( widget->spec.edit.buffer )
        free( widget->spec.edit.buffer );
    widget->spec.edit.buffer = calloc( size + 1, sizeof( char ) );
    if ( widget->spec.edit.buffer == 0 )
        GUI_ABORT( "Out Of Memory" );
}
/*
====================================================================
Set the edit's text. This resets the edit cursor's position to
end of text and updates displayed edit if visible.
====================================================================
*/
void gui_edit_set_text( GuiWidget *widget, char *text )
{
    if ( widget->type != GUI_EDIT ) return;
    /* copy text */
    snprintf( widget->spec.edit.buffer, widget->spec.edit.size + 1, text );
    widget->spec.edit.length = strlen( widget->spec.edit.buffer );
    /* reset */
    /* first character in first line */
    widget->spec.edit.pos = 0;
    widget->spec.edit.x = widget->spec.edit.y = 0;
    widget->spec.edit.start = widget->spec.edit.line = 0; 
    /* jump to end */
    if ( !gui_edit_adjust_cursor( widget, widget->spec.edit.length ) )
        gui_edit_update_display_buffer( widget );
    /* update */
    if ( widget->visible )
        gui_widget_draw( widget );
}
/*
====================================================================
Copy characters from 'start' to 'length' of the edit string to 
'buffer' (at maximum limit characters including \0). If 'length' 
is -1 the characters copied are those from 'start' to end of text.
====================================================================
*/
int gui_edit_get_text( 
    GuiWidget *widget, char *buffer, int limit, 
    int start, int length )
{
    char *ptr;
    if ( widget->type != GUI_EDIT ) return 0;
    if ( start < 0 ) start = 0;
    ptr = widget->spec.edit.buffer + start;
    if ( length == -1 ) 
        length = strlen( ptr );
    if ( length > limit )
        length = limit;
    if ( length )
        snprintf( buffer, limit, widget->spec.edit.buffer );
    else
        buffer[0] = 0;
    return 1;
}
/*
====================================================================
Update the blinking cursor flag (no update) of the edit and in 
case a key is pressed call gui_edit_handle_key().
====================================================================
*/
void gui_edit_update( GuiWidget *widget, int ms )
{
    gui_edit_blink_time += ms;
    if ( gui_edit_blink_time > 500 ) {
        gui_edit_blink_time = 0;
        gui_edit_blink = !gui_edit_blink;
        gui_widget_draw( widget );
    }
    if ( gui_edit_keysym != -1 ) {
        if ( gui_edit_delay > 0 ) {
            gui_edit_delay -= ms;
            if ( gui_edit_delay <= 0 )
                gui_edit_delay = 0;
        }
        if ( gui_edit_delay == 0 ) {
            if ( gui_edit_interval > 0 ) {
                gui_edit_interval -= ms;
                if ( gui_edit_interval < 0 )
                    gui_edit_interval = 0;
            }
            if ( gui_edit_interval == 0 ) {
                gui_edit_handle_key( widget, 
                    gui_edit_keysym, gui_edit_unicode );
                gui_edit_interval = 30;
            }
        }
    }
}
/*
====================================================================
Select a character set.
  default: all non-whitespaces >=32 && <= 128
  alpha: A-Z,a-z
  numerical: 0-9
  alphanumerical: A-Z,a-z,0-9
====================================================================
*/
void gui_edit_set_filter( GuiWidget *widget, int type )
{
    int i, j;
    if ( widget->type != GUI_EDIT ) return;
    memset( widget->spec.edit.filter, 0, 
        sizeof( widget->spec.edit.filter ) );
    switch ( type ) {
        case GUI_EDIT_DEFAULT:
            for ( i = 32; i <= 128; i++ )
                widget->spec.edit.filter[i] = 1;
            break;
        case GUI_EDIT_ALPHANUMERICAL:        
        case GUI_EDIT_ALPHANUMERICAL2:        
        case GUI_EDIT_ALPHA:
            for ( i = 65, j = 97; i <= 90; i++, j++ ) {
                widget->spec.edit.filter[i] = 1; 
                widget->spec.edit.filter[j] = 1;
            }
            if ( type == GUI_EDIT_ALPHANUMERICAL || type == GUI_EDIT_ALPHANUMERICAL2 ) {
                widget->spec.edit.filter[45] = 1;
                for ( i = 48; i <= 57; i++ )
                    widget->spec.edit.filter[i] = 1;
            }
            if ( type == GUI_EDIT_ALPHANUMERICAL2 )
                widget->spec.edit.filter[95] = 1;
            break;
        case GUI_EDIT_NUMERICAL:
            for ( i = 48; i <= 57; i++ )
                widget->spec.edit.filter[i] = 1;
            widget->spec.edit.filter[45] = 1;
            break;
    }
}
