/*
 * 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: PGicn.c                                      */
/* Author: Paul Gardiner.                             */
/* Function: Provides a library of functions for      */
/*           defining the effect of mouse clicks      */
/*           over icons.                              */
/*                                                    */
/******************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "kernel.h"
#include "swis.h"
#include "PGerr.h"
#include "PGwin.h"
#include "PGwinH.h"
#include "PGspr.h"
#include "PGsprH.h"
#include "PGicn.h"
#include "PGicnH.h"
#include "BarSpr.h"


#define MAX(x, y) ((x) > (y) ? (x) : (y))

static _kernel_swi_regs regs;

static struct win_window_s bar_win;

/*
static struct icn_icon_s icn_bar = {-2, 0, NULL};

static struct icn_acts_s bar_data = {NULL,
                                     NULL,
                                     NULL,
                                     {
                                         0xfff,
                                         NULL,
                                         NULL
                                     },
                                     NULL,
                                     NULL};
*/

static struct icn_icon_s drag_icon;
static int sprite_being_dragged = 0;

static int my_ref = -1;

static int file_safe = 0;

static void *icn_data = NULL;

static char *leaf_name(char *path)
{
    char *ptr, *leaf;

    leaf = path;
    for(ptr = path; *ptr != '\0'; ++ptr)
    {
        if(*ptr == '.')
            leaf = ptr + 1;
    }
    return leaf;
}

static icn_acts find(int window, int icon)
{
    win_window win;

    win = window == -2 ? &bar_win : xyzWINFINDxyz(window);
    if(win == NULL || win->icon_data == NULL || icon >= win->n_icon)
        return NULL;
    return win->icon_data + icon;
}

void extend_icondata(int window, int icon)
{
    win_window win;
    int new_n_icon;
    icn_acts new_icon_data;

    win = window == -2 ? &bar_win : xyzWINFINDxyz(window);
    if(icon >= win->n_icon || win->icon_data == NULL)
    {
        new_n_icon = MAX(win->n_icon * 2, icon + 1);
        new_icon_data = malloc(new_n_icon * sizeof(struct icn_acts_s));
        if(new_icon_data == NULL)
            ErrorFatal("Insufficient memory space to initialise icons");
        memset(new_icon_data, 0, new_n_icon * sizeof(struct icn_acts_s));
        if(win->icon_data)
        {
            memcpy(new_icon_data, win->icon_data, win->n_icon * sizeof(struct icn_acts_s));
            free(win->icon_data);
        }
        win->icon_data = new_icon_data;
        win->n_icon = new_n_icon;
    }
    memset(win->icon_data + icon, 0, sizeof(struct icn_acts_s));
}

static void drag_box(int xmin, int ymin, int xmax, int ymax)
{
    int drag_buf[10];

    drag_buf[1] = 5;
    drag_buf[2] = xmin;
    drag_buf[3] = ymin;
    drag_buf[4] = xmax;
    drag_buf[5] = ymax;
    drag_buf[6] = 0;
    drag_buf[7] = 0;
    drag_buf[8] = 4000;
    drag_buf[9] = 4000;
    regs.r[1] = (int) drag_buf;
    _kernel_swi(Wimp_DragBox, &regs, &regs);
}

static void drag_sprite(int xmin, int ymin, int xmax, int ymax, int spr)
{
    int block[4];

    block[0] = xmin;
    block[1] = ymin;
    block[2] = xmax;
    block[3] = ymax;
    regs.r[0] = 0xC5;
    regs.r[1] = 1;
    regs.r[2] = spr;
    regs.r[3] = (int) block;
    _kernel_swi(DragASprite_Start, &regs, &regs);
    sprite_being_dragged = 1;
}

static void dodrag(icn_icon icn)
{
    int win_buf[9], icn_buf[10];
    int xmin, ymin, xmax, ymax, spr;
    int drag_sprite_on;

    regs.r[0] = 161;
    regs.r[1] = 0x1C;
    _kernel_swi(OS_Byte, &regs, &regs);
    drag_sprite_on = (regs.r[2] & 2);
    win_buf[0] = icn->window;
    regs.r[1] = (int) win_buf;
    _kernel_swi(Wimp_GetWindowState, &regs, &regs);
    icn_buf[0] = icn->window;
    icn_buf[1] = icn->icon;
    regs.r[1] = (int) icn_buf;
    _kernel_swi(Wimp_GetIconState, &regs, &regs);
    xmin = icn_buf[2] + win_buf[1] - win_buf[5];
    ymin = icn_buf[3] + win_buf[4] - win_buf[6];
    xmax = icn_buf[4] + win_buf[1] - win_buf[5];
    ymax = icn_buf[5] + win_buf[4] - win_buf[6];
    spr = (int)(icn_buf + 7);
    if((icn_buf[6] & 0x100) != 0)
        spr = *(int *)spr;
    if(drag_sprite_on && (icn_buf[6] & 0x3) == 2)
        drag_sprite(xmin, ymin, xmax, ymax, spr);
    else
        drag_box(xmin, ymin, xmax, ymax);
}

static void mouse_click(int *poll)
{
    icn_acts acts;

    acts = find(poll[3], poll[4]);
    if(acts != NULL)
    {
	icn_data = acts->data;
        if((poll[2] & 0x70) && acts->drag.seed != NULL)
        {
            drag_icon.window = poll[3];
            drag_icon.icon   = poll[4];
            dodrag(&drag_icon);
        }
        else
        {
            if(poll[2] & 1 && acts->click_a != NULL)
                acts->click_a();
            if(poll[2] & 2 && acts->click_m != NULL)
                acts->click_m();
            if(poll[2] & 4 && acts->click_s != NULL)
                acts->click_s();
        }
    }
}

static void drag_over(void)
{
    int pbuf[5], mbuf[16];
    icn_drag drag;
    icn_acts ia;

    if(sprite_being_dragged)
    {
        _kernel_swi(DragASprite_Stop, &regs, &regs);
        sprite_being_dragged = 0;
    }
    ia = find(drag_icon.window, drag_icon.icon);
    drag = ia->drag;
    regs.r[1] = (int) pbuf;
    _kernel_swi(Wimp_GetPointerInfo, &regs, &regs);
    mbuf[0] = 64;
    mbuf[3] = 0;
    mbuf[4] = 1;
    mbuf[5] = pbuf[3];
    mbuf[6] = pbuf[4];
    mbuf[7] = pbuf[0];
    mbuf[8] = pbuf[1];
    mbuf[9] = 2000;
    mbuf[10] = drag.file_type;
    icn_data = ia->data;
    strncpy((char *)(mbuf + 11), leaf_name(drag.seed()), 20);
    ((char *)(mbuf + 11))[19] = '\0';
    regs.r[0] = 18;
    regs.r[1] = (int) mbuf;
    regs.r[2] = pbuf[3];
    regs.r[3] = pbuf[4];
    _kernel_swi(Wimp_SendMessage, &regs, &regs);
    my_ref = mbuf[2];
}

static void message(int *poll)
{
    switch(poll[4])
    {
        case 2:
            if(poll[3] == my_ref)
            {
                icn_drag drag;
                icn_acts ia;

		ia = find(drag_icon.window, drag_icon.icon);
		icn_data = ia->data;
                drag = ia->drag;
                file_safe = (poll[9] != -1);
                drag.save((char *)(poll + 11));
                poll[3] = poll[2];
                poll[4] = 3;
                regs.r[0] = 18;
                regs.r[1] = (int) poll;
                regs.r[2] = poll[1];
                _kernel_swi(Wimp_SendMessage, &regs, &regs);
                my_ref = poll[2];
            }
            break;
        case 4:
            if(poll[3] == my_ref)
            {
                my_ref = -1;
            }
            break;
    }
}

void xyzICNxyz(int reason, int *poll)
{
    switch(reason)
    {
        case  6: mouse_click(poll);
            break;
        case  7: drag_over();
            break;
        case 17:
        case 18: message(poll);
            break;
        case 19:
            if(poll[2] == my_ref)
            {
                my_ref = -1;
            }
            break;
    }
}

static icn_icon icn_bar_base(spr_sprite spr, int side)
{
    icn_icon res;
    int icon_buf[9];
    int x, y;

    res = malloc(sizeof(*res));
    if(res == NULL)
        ErrorFatal("Insufficient memory");
    if(spr == NULL)
        spr = SprIconBar();
    SprGetSize(spr, &x, &y);
    if(x < 68) x = 68;
    if(y < 68) y = 68;
    icon_buf[0] =        side;
    icon_buf[1] =           0;
    icon_buf[2] =           0;
    icon_buf[3] =           x;
    icon_buf[4] =           y;
    icon_buf[5] =       0x2102;
    icon_buf[6] = spr->sprite + spr->area;
    icon_buf[7] =   spr->area;
    icon_buf[8] =           0;
    regs.r[1] = (int) icon_buf;
    _kernel_swi(Wimp_CreateIcon, &regs, &regs);
    extend_icondata(-2, regs.r[0]);
    res->window = -2;
    res->icon = regs.r[0];
    res->spr_name = NULL;
    return res;
}

icn_icon IcnBar(spr_sprite spr)
{
    return icn_bar_base(spr, -1);
}

icn_icon IcnBarFS(spr_sprite spr)
{
    return icn_bar_base(spr, -2);
}

icn_icon IcnBarDisc(char *sprite, char *text)
{
    icn_icon res;
    int icon_buf[9];
    static char *tbuf;
    static char *sbuf;

    res = malloc(sizeof(*res));
    tbuf = malloc(strlen(text)+1);
    sbuf = malloc(strlen(sprite)+2);
    if(res == NULL || tbuf == NULL || sbuf == NULL)
        ErrorFatal("Insufficient memory");
    strcpy(tbuf, text);
    sprintf(sbuf, "S%s", sprite);
    icon_buf[0] =          -5;
    icon_buf[1] =           0;
    icon_buf[2] =         -16;
    icon_buf[3] =          MAX(48,strlen(text)*16);
    icon_buf[4] =          88;
    icon_buf[5] =      0x1700210B;
    icon_buf[6] =        (int) tbuf;
    icon_buf[7] =       (int) sbuf;
    icon_buf[8] =       strlen(text)+1;
    regs.r[1] = (int) icon_buf;
    regs.r[0] = 0x5F000000;
    _kernel_swi(Wimp_CreateIcon, &regs, &regs);
    extend_icondata(-2, regs.r[0]);
    res->window = -2;
    res->icon = regs.r[0];
    res->spr_name = NULL;
    return res;
}

void IcnBarDelete(icn_icon icn)
{
    icn_acts acts;
    int block[2];

    if(icn->window != -2)
        Error("Only bar icons can be deleted");
    acts = find(icn->window, icn->icon);
    if(acts) memset(acts, 0, sizeof(*acts));
    block[0] = -2;
    block[1] = icn->icon;
    regs.r[1] = (int) block;
    _kernel_swi(Wimp_DeleteIcon, &regs, &regs);
    free(icn->spr_name);
    free(icn);
}

icn_icon *IcnButtons(win_window win)
{
    int i;
    icn_icon *icon_arr, icon_recs;

    if(win == NULL)
        return NULL;
    if(win->icon_data == NULL && win->n_icon != 0)
    {
        win->icon_data = (icn_acts) malloc(win->n_icon * sizeof(struct icn_acts_s));
        if(win->icon_data == NULL)
        {
            ErrorFatal("Insufficient memory space to initialise icons");
            return NULL;
        }
        for(i = 0; i < win->n_icon; ++i)
        {
            win->icon_data[i].click_s = NULL;
            win->icon_data[i].click_m = NULL;
            win->icon_data[i].click_a = NULL;
            win->icon_data[i].drag.seed = NULL;
            win->icon_data[i].load = NULL;
        }
    }
    if(win->n_icon != 0)
    {
        icon_recs = (icn_icon) malloc(win->n_icon * sizeof(struct icn_icon_s));
        if(icon_recs == NULL)
        {
            ErrorFatal("Insufficient memory space to initialise icons");
            return NULL;
        }
    }
    icon_arr = (icn_icon *) malloc((win->n_icon + 1) * sizeof(icn_icon));
    if(icon_arr == NULL)
    {
        ErrorFatal("Insufficient memory space to initialise icons");
        return NULL;
    }
    for(i = 0; i < win->n_icon; ++i)
    {
        icon_recs[i].window = win->handle;
        icon_recs[i].icon = i;
        icon_recs[i].spr_name = NULL;
        icon_arr[i] = icon_recs + i;
    }
    icon_arr[i] = NULL;
    return icon_arr;
}

void IcnSetText(icn_icon icn, char *text)
{
    int buf[10];

    buf[0] = icn->window;
    buf[1] = icn->icon;
    regs.r[1] = (int) buf;
    _kernel_swi(Wimp_GetIconState, &regs, &regs);
    if(buf[6] & 0x101) /* Not sufficiently discriminating ??? */
    {
        strncpy((char *) buf[7], text, buf[9]);
        ((char *) buf[7])[buf[9] - 1] = '\0';
        buf[2] = 0;
        buf[3] = 0;
        _kernel_swi(Wimp_SetIconState, &regs, &regs); /* Caret ??? */
    }
}

char *IcnText(icn_icon icn)
{
    int buf[10];
    char *text;
    int i;

    buf[0] = icn->window;
    buf[1] = icn->icon;
    regs.r[1] = (int) buf;
    _kernel_swi(Wimp_GetIconState, &regs, &regs);
    if(buf[6] & 0x101)
    {
        text = (char *) buf[7];
        for(i = 0; i < buf[9]; ++i)
            if(text[i] == '\r')
                text[i] = '\0';
    }
    else
    {
        text = NULL;
    }
    return text;
}

void IcnSetSel(icn_icon icn, int sel)
{
    int buf[4];

    buf[0] = icn->window;
    buf[1] = icn->icon;
    if(sel)
        buf[2] = 0x200000;
    else
        buf[2] = 0;
    buf[3] = 0x200000;
    regs.r[1] = (int) buf;
    _kernel_swi(Wimp_SetIconState, &regs, &regs);
}

int IcnSel(icn_icon icn)
{
    int buf[10];

    buf[0] = icn->window;
    buf[1] = icn->icon;
    regs.r[1] = (int) buf;
    _kernel_swi(Wimp_GetIconState, &regs, &regs);
    return (buf[6] & 0x200000) != 0;
}

void IcnSetGrey(icn_icon icn, int grey)
{
    int buf[4];

    buf[0] = icn->window;
    buf[1] = icn->icon;
    if(grey)
        buf[2] = 0x400000;
    else
        buf[2] = 0;
    buf[3] = 0x400000;
    regs.r[1] = (int) buf;
    _kernel_swi(Wimp_SetIconState, &regs, &regs);
}

void IcnGiveFocus(icn_icon icn)
{
    int len;

    len = strlen(IcnText(icn));
    regs.r[0] = icn->window;
    regs.r[1] = icn->icon;
    regs.r[4] = -1;
    regs.r[5] = len;
    _kernel_swi(Wimp_SetCaretPosition, &regs, &regs);
}

char *IcnSprName(icn_icon icn)
{
    int buf[10];

    if(icn->spr_name)
        return icn->spr_name;
    buf[0] = icn->window;
    buf[1] = icn->icon;
    regs.r[1] = (int) buf;
    _kernel_swi(Wimp_GetIconState, &regs, &regs);
    if((buf[6] & 0x102) == 2)
    {
        icn->spr_name = (char *) malloc(13);
        if(icn->spr_name == NULL)
        {
            Error("Out of memory");
            return NULL;
        }
        strncpy(icn->spr_name, (char *)(buf + 7), 12);
        icn->spr_name[12] = '\0';
        return icn->spr_name;
    }
    else
    {
        return NULL;
    }
}

void IcnClickS(icn_icon icn, icn_action act)
{
    icn_acts acts;

    acts = find(icn->window, icn->icon);
    if(acts != NULL)
        acts->click_s = act;
}

void IcnClickM(icn_icon icn, icn_action act)
{
    icn_acts acts;

    acts = find(icn->window, icn->icon);
    if(acts != NULL)
        acts->click_m = act;
}

void IcnClickA(icn_icon icn, icn_action act)
{
    icn_acts acts;

    acts = find(icn->window, icn->icon);
    if(acts != NULL)
        acts->click_a = act;
}

void IcnDrag(icn_icon icn, icn_drag drag)
{
    icn_acts acts;

    acts = find(icn->window, icn->icon);
    if(acts != NULL)
        acts->drag = drag;
}

int IcnSafe(void)
{
    return file_safe;
}

void *IcnData(void) {

    return icn_data;
}

void IcnSetData(icn_icon icn, void *data) {

    icn_acts ia;

    ia = find(icn->window, icn->icon);
    ia->data = data;
}

void *IcnGetData(icn_icon icn) {

    icn_acts ia;

    ia = find(icn->window, icn->icon);
    return ia->data;
}

icn_icon IcnCopy(icn_icon master, int x, int y)
{
    int buf[10], *tmp;
    int len;
    icn_icon res;

    buf[0] = master->window;
    buf[1] = master->icon;
    regs.r[1] = (int) buf;
    _kernel_swi(Wimp_GetIconState, &regs, &regs);
    if(buf[6] & (1<<8))
    {
        switch(buf[6] & 3)
        {
            case 0:
                break;
            case 1:
            case 3:
                tmp = malloc(buf[9]);
                if(!buf) ErrorFatal("Insufficient memory");
                memcpy(tmp, (void *) buf[7], buf[9]);
                buf[7] = (int) tmp;
                if(buf[8] != -1)
                {
                    len = strlen((void *) buf[8])+1;
                    tmp = malloc(len);
                    memcpy(tmp, (void *) buf[8], len);
                    buf[8] = (int) tmp;
                }
                break;
            case 2:
                if(buf[9])
                {
                    tmp = malloc(buf[9]);
                    if(!buf) ErrorFatal("Insufficient memory");
                    memcpy(tmp, (void *) buf[7], buf[9]);
                    buf[7] = (int) tmp;
                }
                break;
        }
    }
    buf[2] += x;
    buf[3] += y;
    buf[4] += x;
    buf[5] += y;
    buf[1] = buf[0];
    regs.r[1] = (int) &buf[1];
    _kernel_swi(Wimp_CreateIcon, &regs, &regs);
    res = malloc(sizeof(*res));
    if(!res) ErrorFatal("Insufficient memory");
    res->window = master->window;
    res->icon = regs.r[0];
    extend_icondata(res->window, res->icon);
    return res;
}
