/*
 *   MediaMVP Server
 *
 *   (C) 2004 Dominic Morris
 *
 *   $Id: menu.c,v 1.21 2005/04/01 11:49:04 dom Exp $
 *   $Date: 2005/04/01 11:49:04 $
 *
 *
 *   Constructs a menu and all such things
 *
 */


#include "program.h"



#define BLACK   0x00000000
#define RED     0x00FC1414
#define GREEN   0x0024FC24
#define YELLOW  0x00FCC024
#define BLUE    0x000000FC
#define CYAN    0x0000FCFC
#define MAGENTA 0x00B000FC
#define WHITE   0x00FCFCFC


typedef struct {
    char    *name;
    int    (*callback_func)(menu_t *menu,void *program, int sel);
    int      num_options;
    char const   **options;
    int      type;
    void    *value_ptr;
} option_t;


struct _menu {
    dongle_t     *dongle;
    render_t     *render;
    program_t        *program;
    void         *param;
    int           numoptions;
    option_t     *options;
    char         *title;
    int           top_offset;
    int           current_selection;
    int           maxrows;                /*!< Number of lines that can be displayed on screen */
    int           flags;
    void         (*param_clearup)(void *ptr);
    menu_t        *parent;
    menu_t        *child;
    char          *colours[4];   
    int          (*colour_func)(menu_t *menu,void *param, int key, int sel);
    int		(*dialcode_func)(menu_t *menu, void *param, int key, int sel);
    char	*current_dialcode;
    int		current_dialcode_selection;
};

#define MENU_ROWS(menu)  ((menu)->maxrows -  4)

static int        menu_keys(void *ptr, int code);



menu_t *new_menu(dongle_t *dongle,render_t *render, program_t *program,menu_t *parent, void *param, int flags)
{
    menu_t       *menu = (menu_t *)malloc(sizeof(*menu));

    menu->dongle = dongle;
    menu->render = render;
    menu->program    = program;
    menu->param  = param;
    menu->title  = NULL;
    menu->top_offset = 0;
    menu->numoptions = 0;
    menu->options = NULL;
    menu->current_selection = 0;
    menu->flags   = flags;
    menu->param_clearup = NULL;
    menu->maxrows = render_get_maxrows(render);
    menu->colours[0] = menu->colours[1] = menu->colours[2] = menu->colours[3] = NULL;

    if ( parent ) {
        menu->parent = parent;
        parent->child = menu;
    } else {
        menu->parent = NULL;
    }
    menu->child = NULL;
    menu->param_clearup = NULL;
    menu->colour_func = NULL;
    menu->dialcode_func = NULL;
    menu->current_dialcode = NULL;
    menu->current_dialcode_selection = 0;

    return menu;
}

void delete_menu_tree(menu_t *menu)
{
    menu_t   *temp;

    while ( menu->child ) {
        menu = menu->child;
    }

    while ( menu->parent ) {
        temp = menu->parent;
        delete_menu(temp);
        menu = temp;
    }

}

void menu_free_dialcode(menu_t *menu)
{
    if(menu->current_dialcode) {
        free(menu->current_dialcode);
        menu->current_dialcode = NULL;
    }
}


void delete_menu(menu_t *menu)
{
    int     i;

    if ( menu->title ) {
        free(menu->title);
        menu->title = NULL;
    }

    for ( i = 0; i < menu->numoptions; i++ ) {
        free(menu->options[i].name);
    }
    if ( menu->options ) {
        free(menu->options);
    }

    if ( menu->param_clearup ) {
        (menu->param_clearup)(menu->param);
    }

    for ( i = 0; i < 3; i++ ) {
        if ( menu->colours[i] ) {
            free(menu->colours[i]);
        }
        menu->colours[i] = NULL;
    }

    /* Delete us from the parent if we've not been replaced... */
    if ( menu->parent ) {
        if ( menu->parent->child == menu ) {
            menu->parent->child = NULL;
        }
    }

    menu_free_dialcode(menu);

    free(menu);
}

void menu_set_title(menu_t *menu, const char *text)
{
    if ( menu->title ) {
        free(menu->title);
    }

    menu->title = strdup(text);
}

void menu_init_dialcode(menu_t *menu, int length)
{
    menu_free_dialcode(menu);
    menu->current_dialcode = (char *)malloc(length + 1);
    memset(menu->current_dialcode, 0, length + 1);
}

char *menu_get_dialcode(menu_t *menu)
{
    return menu->current_dialcode;
}

void menu_set_dialcode(menu_t *menu, char *dialcode)
{
    menu_free_dialcode(menu);
    menu->current_dialcode = (char *)strdup(dialcode);
}

int menu_get_dialcode_selection(menu_t *menu)
{
    return menu->current_dialcode_selection;
}

void menu_set_dialcode_selection(menu_t *menu, int sel)
{
    menu->current_dialcode_selection = sel;
}

void menu_set_dialcode_callback(menu_t *menu, int (dialcode_func)(menu_t *menu, void *param, int key, int sel))
{
    menu->dialcode_func = dialcode_func;
}

void menu_add_option(menu_t *menu, const char *name, int (callback_func)(menu_t *menu,void *param, int sel),
                     int type, int num_options, char const **options, void *value_ptr)
{
    int     i = menu->numoptions++;

    menu->options = (option_t *)realloc(menu->options,menu->numoptions * sizeof(option_t));

    menu->options[i].name = strdup(name);
    menu->options[i].callback_func = callback_func;
    menu->options[i].type = type;
    menu->options[i].num_options = num_options;
    menu->options[i].options = options;
    menu->options[i].value_ptr = value_ptr;
}

void menu_add(menu_t *menu, const char *name, int (*callback_func)(menu_t *menu,void *param, int sel))
{
    int     i = menu->numoptions++;

    menu->options = (option_t *)realloc(menu->options,menu->numoptions * sizeof(option_t));

    menu->options[i].name = strdup(name);
    menu->options[i].callback_func = callback_func;
    menu->options[i].type          = MENU_NONE;
    menu->options[i].num_options = 0;
    menu->options[i].value_ptr = NULL;
}



#ifdef HAVE_LIBXML2
void menu_display_article(menu_t *menu)
{
    char          buf[256];
    const char   *extra;
    render_t     *render = menu->render;
    int           row;
    int           i;
    int           last;

    while ( menu->child ) {
        menu = menu->child;
    }

    render_set_bgcol(render,BLACK);
    render_clear(render);

    snprintf(buf,sizeof(buf)," %s\n",menu->title ? menu->title : "");
    render_set_bgcol(render,CYAN);
    render_set_fgcol(render,BLACK);
    render_printrow(render,0,buf);

    /* Calculate how many rows to print */
    last = (menu->top_offset + MENU_ROWS(menu) < menu->numoptions) 
        ? menu->top_offset + MENU_ROWS(menu) : menu->numoptions;
    for ( row = 2, i = menu->top_offset; i < last; i++, row++ ) {
        if ( menu->options[i].type == MENU_INT ) {
            extra = menu->options[i].options[*((int *)menu->options[i].value_ptr)];
        } else {
            extra = "";
        }
        if ( i < 9 && menu->flags & MENU_NUMBERS ) {
            snprintf(buf,sizeof(buf)," %d. %s %s\n",i+1,menu->options[i].name,extra);
        } else {
            snprintf(buf,sizeof(buf),"   %s %s\n",menu->options[i].name,extra);
        }      

        render_set_bgcol(render,BLACK);
        render_set_fgcol(render,WHITE);
        render_printrow(render,row,buf);
    }

    program_register_keys(menu->program,REGISTER_MENU,menu_keys,menu);
}
#endif

void menu_display(menu_t *menu)
{
    char          buf[256];
    const char   *extra;
    render_t     *render = menu->render;
    int           row;
    int           i;
    int           last;
    int           width;
    int           colwidth;

    while ( menu->child ) {
        menu = menu->child;
    }

    render_set_bgcol(render,BLACK);
    render_clear(render);

    snprintf(buf,sizeof(buf)," %s\n",menu->title ? menu->title : "");
    render_set_bgcol(render,CYAN);
    render_set_fgcol(render,BLACK);
    render_printrow(render,0,buf);


    /* Calculate how many rows to print */
    last = (menu->top_offset + MENU_ROWS(menu) < menu->numoptions) 
        ? menu->top_offset + MENU_ROWS(menu) : menu->numoptions;
    for ( row = 2, i = menu->top_offset; i < last; i++, row++ ) {
        if ( menu->options[i].type == MENU_INT ) {
            extra = menu->options[i].options[*((int *)menu->options[i].value_ptr)];
        } else {
            extra = "";
        }
        if ( i < 9 && menu->flags & MENU_NUMBERS ) {
            snprintf(buf,sizeof(buf)," %d. %s %s\n",i+1,menu->options[i].name,extra);
        } else {
            snprintf(buf,sizeof(buf),"   %s %s\n",menu->options[i].name,extra);
        }      

        if ( i == menu->current_selection ) {
            render_set_bgcol(render,CYAN);
            render_set_fgcol(render,BLACK);
        } else {
            render_set_bgcol(render,BLACK);
            render_set_fgcol(render,WHITE);
        }
        render_printrow(render,row,buf);
    }

    /* FIXME: Hardcoded hack... */
    colwidth = (render_get_width(render) - render_get_xoffset(render) * 2) / 4;
    for ( i = 0; i < 4; i++ ) {
        int    offset; 

        if ( menu->colours[i] ) {
            width = render_get_textspace(render,menu->colours[i]);

            if ( width > colwidth ) {
                offset = 0;
            } else {
                offset = (colwidth - width) / 2;
            }

            offset += colwidth * i;

            render_set_fgcol(render,BLACK);
            switch ( i ) {
            case 0:
                render_set_bgcol(render,RED);
                break;
            case 1:
                render_set_bgcol(render,GREEN);
                break;
            case 2:
                render_set_bgcol(render,YELLOW);
                break;
            case 3:
                render_set_fgcol(render,WHITE);
                render_set_bgcol(render,BLUE);
                break;
            }
            if ( menu->colours[i] ) {
                render_printrow_width(render,menu->maxrows-1,colwidth * i,colwidth,"\n");
                render_printrow_width(render,menu->maxrows-1,offset,colwidth,menu->colours[i]);
            }
        }
    }
        
    program_register_keys(menu->program,REGISTER_MENU,menu_keys,menu);
}


static int menu_keys(void *ptr, int code)
{
    menu_t    *menu = (menu_t *)ptr;
    int        pos;
    int      (*func)(menu_t *,void *param, int sel);
    int      (*cfunc)(menu_t *,void *param, int key,int sel);
    option_t  *option;
    unsigned int       *num;
    int      streamtype = 0;


    streamtype = dongle_get_type(menu->dongle);


    /* If we're in streaming mpeg mode, then we'd like to change channels
       without going back to the menu - this is handled at a different
       level by the plugin since it has to do some fancy work in working
       out channel+ / channel-
    */
#ifndef VDR_PLUGIN
    if ( streamtype == ( MEDIA_MPEG | MEDIA_LIVE ) ) {
        switch ( code ) {
        case keyChanUp:
            if ( menu->current_selection < menu->numoptions - 1 ) {
                menu->current_selection++;
                if ( menu->top_offset + MENU_ROWS(menu) <= menu->current_selection ) {
                    menu->top_offset++;
                }
            } else {
                menu->current_selection = 0;
                menu->top_offset = 0;
            }

            func = menu->options[menu->current_selection].callback_func;
            if ( func && func(menu,menu->param,menu->current_selection) == 0 ) {
                menu_t  *parent;                
                parent = menu->parent;
                delete_menu(menu);
                parent->child = NULL;        
            }  
            break;
        case keyChanDn:
            if ( menu->current_selection > 0 ) {
                menu->current_selection--;
                if ( menu->top_offset > menu->current_selection ) {
                    menu->top_offset = menu->current_selection;
                }
            } else {
                menu->current_selection = menu->numoptions - 1;          
                if ( menu->numoptions > MENU_ROWS(menu) ) {
                    menu->top_offset = menu->numoptions - MENU_ROWS(menu);
                }
            }
            func = menu->options[menu->current_selection].callback_func;
            if ( func && func(menu,menu->param,menu->current_selection) == 0 ) {
                menu_t  *parent;                
                parent = menu->parent;
                delete_menu(menu);
                parent->child = NULL;        
            }  
            break;
        }
        return 0;
    }
#endif



    switch ( code ) {
    case keyChanUp:
        menu_free_dialcode(menu);
        if ( streamtype != MEDIA_MPEG ) {
            if ( menu->current_selection > 0 ) {
                menu->current_selection--;
                if ( menu->top_offset > menu->current_selection ) {
                    menu->top_offset = menu->current_selection;
                }
            } else {
                menu->current_selection = menu->numoptions - 1;          
                if ( menu->numoptions > MENU_ROWS(menu) ) {
                    menu->top_offset = menu->numoptions - MENU_ROWS(menu);
                }
            }
            
            menu_display(menu);
        }
        break;
    case keyChanDn:
        menu_free_dialcode(menu);
        if ( streamtype != MEDIA_MPEG ) {
            if ( menu->current_selection < menu->numoptions - 1 ) {
                menu->current_selection++;
                if ( menu->top_offset + MENU_ROWS(menu) <= menu->current_selection ) {
                    menu->top_offset++;
                }
            } else {
                menu->current_selection = 0;
                menu->top_offset = 0;
            }

            menu_display(menu);
        }
        break;
    case keyVolUp:
        option = &menu->options[menu->current_selection];
        if ( option->type == MENU_INT ) {
            menu_t *parent = menu->parent;
            num = (unsigned int *)option->value_ptr;           
            *num = ( *num + 1 ) % option->num_options;
            func = menu->options[menu->current_selection].callback_func;
            if ( func && func(menu,menu->param,-1) == 0 ) {
                delete_menu(menu);
                parent->child = NULL;        
            }    
            menu_display(parent);
  
        }
        break;
    case keyVolDn:
        option = &menu->options[menu->current_selection];
        if ( option->type == MENU_INT ) {
            menu_t *parent = menu->parent;
            num = (unsigned int *)option->value_ptr;
            if ( *num == 0 ) {
                *num = option->num_options - 1;
            } else {
                *num = ( *num - 1 ) % option->num_options;
            }
            func = menu->options[menu->current_selection].callback_func;
            if ( func && func(menu,menu->param,-1) == 0 ) {
                delete_menu(menu);
                parent->child = NULL;        
            }      
            menu_display(parent);
        } else {
            int (*dialfunc)(menu_t *menu, void *param, int key, int sel);
            dialfunc = menu->dialcode_func;

            if(dialfunc) {    
                menu->current_selection = dialfunc(menu, menu->param, 0, menu->current_selection);
                menu->top_offset = menu->current_selection;
                menu_display(menu);
            }
        }
        break;
    case keyBack:
        if ( menu->parent ) {
            menu_t *parent = menu->parent;
            delete_menu(menu);
            parent->child = NULL;
            menu_display(parent);
        }
        break;
    case keySkip:        /* Go down a page */
        if ( menu->current_selection + MENU_ROWS(menu) > ( menu->numoptions - MENU_ROWS(menu) ) ) {
            menu->current_selection = menu->top_offset = menu->numoptions - MENU_ROWS(menu);
            if ( menu->current_selection < 0 ) {
                menu->current_selection = menu->top_offset = 0;
            }
        } else {
            menu->current_selection = menu->top_offset =  menu->current_selection + MENU_ROWS(menu);
        }
        menu_display(menu);
        break;
    case keyReplay:       /* Go up a page */
        if ( menu->current_selection - MENU_ROWS(menu) < 0 ) {
            menu->current_selection = menu->top_offset = 0;
        } else {
            menu->current_selection = menu->top_offset =  menu->current_selection - MENU_ROWS(menu);
        }
        menu_display(menu);
        break;
    case keyGo:
        while ( menu->parent ) {
            menu_t *parent = menu->parent;
            delete_menu(menu);
            menu = parent;
            menu->child = NULL;
        }
        menu_display(menu);
        break;
    case keyOk:
        if ( menu->options != NULL ) {
            menu_t  *parent;
            func = menu->options[menu->current_selection].callback_func;
            if ( func && func(menu,menu->param,menu->current_selection) == 0 ) {
                parent = menu->parent;
                delete_menu(menu);
                menu_display(parent);
            }        
        }
        break;
    case key0 ... key9:
        if ( (menu->flags & MENU_NUMBERS) && (code > key0) ) {
            pos = code - key1;
            if ( pos < menu->numoptions && menu->options ) {
                func = menu->options[pos].callback_func;
                if ( func && func(menu,menu->param,pos) == 0 ) {
                    delete_menu(menu);
                }
            }
        } else {
            int (*dialfunc)(menu_t *menu, void *param, int key, int sel);

            dialfunc = menu->dialcode_func;
            if ( dialfunc ) {        
                menu->current_selection = dialfunc(menu, menu->param, code, menu->current_selection);
                menu->top_offset = menu->current_selection;
                menu_display(menu);
            }
	    
        }

        break;
    case keyRed ... keyBlue:
        cfunc = menu->colour_func;        
        if ( cfunc && cfunc(menu,menu->param,code,menu->current_selection) == 0 ) {
            menu_t *parent;
            parent = menu->parent;
            delete_menu(menu);
            menu_display(parent);
        }     
        break;
    default:
        Dprintf(INFO,"Received IR code %d - not used here\n",code);
    }
    return 0;
}


/** \brief Set the clearup pointer for the param datatype 
 * 
 *  \param menu Menu handle
 *  \param clearup Function to call to clearup
 *
 *  Used for those situations when we recurse down through directory structures
 */
void menu_set_param_clearup(menu_t *menu, void (*clearup)(void *ptr))
{
    menu->param_clearup = clearup;
}


/** \brief Set what the colour keys do 
 *
 *  \param menu    Menu handle
 *  \param red     Red button text
 *  \param green   Green button text
 *  \param yellow  Yellow button text
 *  \param blue    Blue button text
 *  \param func    Callback function - called back with 0 - 3 for the colour button that has been
 *                 pressed
 */
void menu_set_colour_actions(menu_t *menu,
                             const char   *red,
                             const char   *green,
                             const char   *yellow,
                             const char   *blue,
                             int   (*func)(menu_t *menu,void *param, int key, int sel))
{
    int     i;

    for ( i = 0; i < 4; i++ ) {
        if ( menu->colours[i] ) {
            free(menu->colours[i]);
        }
        menu->colours[i] = NULL;
    }

    if ( red ) {
        menu->colours[0] = strdup(red);
    }
    if ( green ) {
        menu->colours[1] = strdup(green);
    }
    if ( yellow ) {
        menu->colours[2] = strdup(yellow);
    }
    if ( blue ) {
        menu->colours[3] = strdup(blue);
    }

    menu->colour_func = func;
}

menu_t  *menu_parent(menu_t *menu)
{
    return menu->parent;
}

menu_t  *menu_child(menu_t *menu)
{
    return menu->child;
}

void    *menu_return_param(menu_t *menu)
{
    return menu->param;
}

void menu_set_entry(menu_t * menu, int entry) 
{
    menu->current_selection=entry;
    if (entry < 0) 
        menu->current_selection = 0;
    if (entry >= menu->numoptions) 
        menu->current_selection=menu->numoptions-1;
    menu->top_offset = menu->current_selection;
}



int  menu_get_entry(menu_t * menu)
{
    return menu->current_selection;
}



 
/*
 * Local Variables:
 *  indent-tabs-mode:nil
 *  require-final-newline:t
 *  c-basic-offset: 4
 * End:
 */
