/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "Licence").
 * You may not use this file except in compliance with the Licence.
 *
 * You can obtain a copy of the licence at RISC OS path @.^.LICENCE
 * or  http://www.riscosdev.com/lanman98/LICENCE.CDDL
 * See the Licence for the specific language governing permissions
 * and limitations under the Licence.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the Licence file. If applicable, add the
 * following below this CDDL HEADER, with the fields enclosed by
 * brackets "[]" replaced with your own identifying information:
 * Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
 
/*
 *   Portions Copyright 1996 Warm Silence Software Ltd.
 */

/******************************************************/
/*                                                    */
/* Name: PGmnu.c                                      */
/* Author: Paul Gardiner.                             */
/* Function: Provides a library of function for       */
/*           producing menus.                         */
/*                                                    */
/******************************************************/

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "kernel.h"
#include "swis.h"
#include "PGmnu.h"
#include "PGmnuH.h"
#include "PGwin.h"
#include "PGwinH.h"
#include "PGerr.h" /* ??? */

enum mnu_item_t {item_t_action, item_t_submenu, item_t_colsel};

enum mnu_menu_t {menu_t_items, menu_t_window};

struct item_block_s
{
    int mflags, submenu, iflags;
    union
    {
        struct
        {
            char *textptr;
            char *valid;
            int len;
        } indirect;
        char direct[12];
    } text;
};

struct menu_block_s
{
    char *title;
    char *valid;
    int len;
    char tfgcol, tbgcol, wfgcol, wbgcol;
    int width, height, gap;
};

struct mnu_item_s
{
    enum mnu_item_t type;
    char *text;
    mnu_action         act;
    union
    {
        mnu_menu       submenu;
        mnu_set_colour  setcol;
    } data;
    int tick;
    int grey;
};

struct mnu_menu_s
{
    enum mnu_menu_t type;
    char *title;
    union
    {
        mnu_item *items;
        win_window win;
    } data;
};



static _kernel_swi_regs regs;

static mnu_menu current_menu = NULL;

static void *buff = NULL;
static int buffsize = 0;

static char *clicked_item_text;


static void fill_menu_block(struct menu_block_s *mb, char *title, int width)
{
    mb->title = title;
    mb->valid = NULL;
    mb->len   = strlen(title)+1;
    mb->tfgcol =       7;
    mb->tbgcol =       2;
    mb->wfgcol =       0;
    mb->wbgcol =       0;
    mb->width  = 16 * width;
    mb->height =      44;
    mb->gap    =       0;
}

static void fill_item_block(struct item_block_s *ib, mnu_item item, int subm)
{
    ib->mflags = 0x100;
    if(item->tick)
        ib->mflags |=  1;
    else
        ib->mflags &= ~1;
    ib->submenu = subm;
    ib->iflags = 0x7000101;
    if(item->grey)
        ib->iflags |= 0x400000;
    else
        ib->iflags &= ~0x400000;
    ib->text.indirect.textptr = item->text;
    ib->text.indirect.valid   = NULL;
    ib->text.indirect.len     = strlen(item->text)+1;
}

static void fill_item_block_col(struct item_block_s *ib, int i)
{
    ib->mflags = 0x100;
    ib->submenu = -1;
    ib->iflags = 0x7000001 | (i << 28);
    sprintf(ib->text.direct, "%d", i);
}

static void last_block(struct item_block_s *ib)
{
    ib->mflags |= 0x80;
}

static int num_menu_items(mnu_menu menu)
{
    int i;

    if(menu == NULL || menu->type != menu_t_items ||
                       menu->data.items == NULL)
        return 0;
    for(i = 0; menu->data.items[i] != NULL; ++i);
    return i;
}

static int max_width(mnu_menu menu)
{
    int i, w, l;
    if(menu == NULL || menu->type != menu_t_items ||
                       menu->data.items == NULL)
        return 0;
    w = strlen(menu->title);
    for(i = 0; menu->data.items[i] != NULL; ++i)
    {
        l = strlen(menu->data.items[i]->text)+1;
        if(l > w)
            w = l;
    }
    return w;
}

static int mspace(mnu_menu);

static int ispace(mnu_item item)
{
    int space;

    switch(item->type)
    {
        case item_t_submenu:
            space = mspace(item->data.submenu);
            break;
        case item_t_action:
            space = 0;
            break;
        case item_t_colsel:
            space = sizeof(struct menu_block_s) + 16 * sizeof(struct item_block_s);
            break;
    }
    return space;
}

static int mspace(mnu_menu menu)
{
    int i, n, space;

    switch(menu->type)
    {
        case menu_t_window:
            space = 0;
            break;
        case menu_t_items:
            n = num_menu_items(menu);
            space = sizeof(struct menu_block_s);
            space += n * sizeof(struct item_block_s);
            for(i = 0; i < n; ++i)
                space += ispace(menu->data.items[i]);
            break;
    }
    return space;
}

static void *mmake(mnu_menu, char *);

static int imake(mnu_item item, char *buffer)
{
    int subm;
    int i;
    struct item_block_s *ib;

    switch(item->type)
    {
        case item_t_action:
            subm = -1;
            break;
        case item_t_submenu:
            subm = (int) mmake(item->data.submenu, buffer);
            break;
        case item_t_colsel:
            subm = (int)buffer;
            fill_menu_block((struct menu_block_s *)buffer, "Colour", 6);
            buffer += sizeof(struct menu_block_s);
            ib = (struct item_block_s *) buffer;
            for(i = 0; i < 16 ; ++i)
                fill_item_block_col(ib+i, i);
            last_block(ib+15);
            break;
    }
    return subm;
}

static void *mmake(mnu_menu menu, char *buffer)
{
    char *handle;
    char *spare;
    int n, i, subm, w;
    struct item_block_s *ib;

    switch(menu->type)
    {
        case menu_t_window:
            handle = (char *) menu->data.win->handle;
            break;
        case menu_t_items:
            handle = buffer;
            n = num_menu_items(menu);
            w = max_width(menu);
            fill_menu_block((struct menu_block_s *)buffer, menu->title, w);
            buffer += sizeof(struct menu_block_s);
            ib = (struct item_block_s *)buffer;
            spare = buffer + n * sizeof(struct item_block_s);
            for(i = 0; i < n; ++i)
            {
                subm = imake(menu->data.items[i], spare);
                spare += ispace(menu->data.items[i]);
                fill_item_block(ib+i, menu->data.items[i], subm);
            }
            last_block(ib+n-1);
            break;
    }
    if(spare - (char *)buff > buffsize)
        Error("Buffer overflow in menu module (bug)");
    return handle;
}

void xyzMNUxyz(int *poll)
{
    int blk[5];
    mnu_item item;
    int i;


    if(poll[0] == -1)
    {
        printf("Null menu selection ????\n");
        return;
    }
    item = current_menu->data.items[poll[0]];
    for(i = 1; poll[i] != -1 && item->type == item_t_submenu; ++i)
        item = item->data.submenu->data.items[poll[i]];
    regs.r[1] = (int) blk;
    _kernel_swi(Wimp_GetPointerInfo, &regs, &regs);
    clicked_item_text = item->text;
    if(item->act != NULL)
        item->act();
    if(item->type == item_t_colsel && poll[i] != -1)
        item->data.setcol(poll[i]);
    if(blk[2] &= 1)
        MnuOpen(current_menu);
    else
        current_menu = NULL;
}



mnu_item MnuAction(char *text, mnu_action act)
{
    mnu_item item;

    item = (mnu_item) malloc(sizeof(struct mnu_item_s));
    if(item == NULL)
    {
        ErrorFatal("Insufficient memory to create menus");
        return NULL;
    }
    item->type = item_t_action;
    item->text = malloc(strlen(text)+1);
    if(item->text == NULL)
    {
        ErrorFatal("Insufficient memory to create menus");
        return NULL;
    }
    strcpy(item->text, text);
    item->act = act;
    item->tick = 0;
    item->grey = 0;
    return item;
}

mnu_item MnuColSel(char *text, mnu_set_colour func)
{
    mnu_item item;

    item = (mnu_item) malloc(sizeof(struct mnu_item_s));
    if(item == NULL)
    {
        ErrorFatal("Insufficient memory to create menus");
        return NULL;
    }
    item->type = item_t_colsel;
    item->text = malloc(strlen(text)+1);
    if(item->text == NULL)
    {
        ErrorFatal("Insufficient memory to create menus");
        return NULL;
    }
    strcpy(item->text, text);
    item->act = NULL;
    item->data.setcol = func;
    item->tick = 0;
    item->grey = 0;
    return item;
}

mnu_item MnuSubmenu(char *text, mnu_menu menu)
{
    mnu_item item;

    item = (mnu_item) malloc(sizeof(struct mnu_item_s));
    if(item == NULL)
    {
        ErrorFatal("Insufficient memory to create menus");
        return NULL;
    }
    item->type = item_t_submenu;
    item->text = malloc(strlen(text)+1);
    if(item->text == NULL)
    {
        ErrorFatal("Insufficient memory to create menus");
        return NULL;
    }
    strcpy(item->text, text);
    item->act = NULL;
    item->data.submenu = menu;
    item->tick = 0;
    item->grey = 0;
    return item;
}

mnu_item MnuSetAct(mnu_item item, mnu_action act)
{
    item->act = act;
    return item;
}

void MnuTick(mnu_item item, int tick)
{
    item->tick = tick;
}

void MnuGrey(mnu_item item, int grey)
{
    item->grey = grey;
}

mnu_menu MnuMenu(char *title, mnu_item *items)
{
    mnu_menu menu;
    int n_item;
    int ms;

    menu = (mnu_menu) malloc(sizeof(struct mnu_menu_s));
    if(menu == NULL)
    {
        ErrorFatal("Insufficient memory to create menus");
        return NULL;
    }
    menu->type = menu_t_items;
    menu->title = malloc(strlen(title)+1);
    if(menu->title == NULL)
    {
        ErrorFatal("Insufficient memory to create menus");
        return NULL;
    }
    strcpy(menu->title, title);
    for(n_item = 0; items[n_item] != NULL; ++n_item);
    menu->data.items = malloc((n_item + 1) * sizeof(mnu_item));
    if(menu->data.items == NULL)
    {
        ErrorFatal("Insufficient memory to create menus");
        return NULL;
    }
    memcpy(menu->data.items, items, (n_item + 1) * sizeof(mnu_item));
    ms = mspace(menu);
    if(ms > buffsize)
    {
        buffsize = ms;
        buff = realloc(buff, buffsize);
        if(buff == NULL)
        {
            ErrorFatal("Insufficient memory to create menus");
            return NULL;
        }
    }

    return menu;
}

mnu_menu MnuWindow(win_window win)
{
    mnu_menu menu;

    menu = (mnu_menu) malloc(sizeof(struct mnu_menu_s));
    if(menu == NULL)
    {
        ErrorFatal("Insufficient memory to create menus");
        return NULL;
    }
    menu->type = menu_t_window;
    menu->title = NULL;
    menu->data.win = win;
    return menu;
}

void MnuOpen(mnu_menu menu)
{
    int blk[5];

    current_menu = menu;
    regs.r[1] = (int) blk;
    _kernel_swi(Wimp_GetPointerInfo, &regs, &regs);
    if(menu == NULL)
        regs.r[1] = -1;
    else
        regs.r[1] =(int) mmake(menu, buff);
    regs.r[2] = blk[0] - 64;
    if(blk[3] == -2)
        regs.r[3] = 96 + 44 * num_menu_items(menu);
    else
        regs.r[3] = blk[1];
    _kernel_swi(Wimp_CreateMenu, &regs, &regs);
}

void MnuRedraw(void)
{
    if(current_menu)
        MnuOpen(current_menu);
}

char *MnuClickedItemText(void)
{
    return clicked_item_text;
}
