/*
 *                 GRAPH, Version 1.00 - 4 August 1989
 *
 *            Copyright 1989, David Gay. All Rights Reserved.
 *            This software is freely redistrubatable.
 */

#include <exec/types.h>
#include <intuition/intuition.h>
#include <graphics/text.h>
#include <string.h>
#include <limits.h>

#include "object.h"
#include "object/default.h"
#include "file.h"
#include "graph.h"
#include "uio.h"
#include "coords.h"
#include "grph.h"
#include "user/gadgets.h"
#include "graphics.h"
#include "tracker.h"

#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>

#define LABELLEN 256
#define LABELHEIGHT 10
#define LABELFONT "topaz.font"
#define FONTLIST 2

/* The various justification possibilities */
enum justify { top, centre, bottom };

/* (private) class label, inherits from object */
struct label
{
    struct object o;
    char text[LABELLEN]; /* What to display, \ for newlines */
    BYTE colour;
    char fname[FONTLEN]; /* font name */
    int fheight;         /* font height */
    double x, y;         /* text position */
    double dx, dy;       /* offset from position, in inches */
    enum justify jx, jy; /* justification (x & y axes) */

    char selected;
    /* Temp, etc vars */
    char broken[LABELLEN];   /* Text, separated into lines (separated by NULLs)
 */
    int nblines;             /* number of lines */
    struct Rectangle extent; /* Size of rectangle taken by text */
    int bw, bh;              /* Size of bitmap (for moving) */
    struct Rectangle lims;   /* Rectangle taken up on screen */
    int x0, y0;              /* x & y in screen coords (wthout ddx, ddy) */
    struct TextFont *font, *newfont; /* the font (newfont for inform/confirm) *
/
    double oldx, oldy;       /* position before move */
    int ddx, ddy;            /* dx, dy in pixels */
    struct Region *ref;
    struct RastPort bkgd;    /* Rastport & bitmap for moves */
    struct BitMap bkgd_bm;
};

/*-------------------------------------------------------------------------*/
/*                       label class implementation                        */
/*-------------------------------------------------------------------------*/

/* Allocates bitmap for saving background while moving */
/* Assumes 2 bit planes */
static int alloc_bkgd_label(struct label *this)
{
    BYTE *data;

    if (data = AllocMem(2 * RASSIZE(this->bw, this->bh), MEMF_CHIP))
    {
        InitBitMap(&this->bkgd_bm, 2, this->bw, this->bh);
        this->bkgd_bm.Planes[0] = (PLANEPTR)data;
        this->bkgd_bm.Planes[1] = (PLANEPTR)(data + RASSIZE(this->bw, this->bh)
);
        InitRastPort(&this->bkgd);
        this->bkgd.BitMap = &this->bkgd_bm;
    }
    return data != 0;
}

/* Frees bitmap alloced above */
static void free_bkgd_label(struct label *this)
{
    FreeMem(this->bkgd_bm.Planes[0], 2 * RASSIZE(this->bw, this->bh));
}

/* Save scrren background into temp space */
static void save_bkgd_label(struct label *this)
{
    ClipBlit(this->o.g->io.rw->rp, this->lims.MinX, this->lims.MinY, &this->bkg
d, 0, 0, this->bw, this->bh, 0xc0);
}

/* Copies saved background back to the screen */
static void restore_bkgd_label(struct label *this)
{
    ClipBlit(&this->bkgd, 0, 0, this->o.g->io.rw->rp, this->lims.MinX, this->li
ms.MinY, this->bw, this->bh, 0xc0);
}

/* Do all the precalculations */
static void setup_label(struct label *this)
{
    struct TextFont *font = this->font;
    char *srch, *nl;

    this->ddx = xinch2dots(this->o.g, this->dx);
    this->ddy = yinch2dots(this->o.g, this->dy);

    /* Break up label & calculate rectangle used */
    srch = strcpy(this->broken, this->text);
    this->nblines = 0;
    this->extent.MaxX = 0;
    this->extent.MinX = INT_MAX;
    do
    {
        struct TextExtent te;

        nl = strchr(srch, '\\');

        if (nl && *(nl + 1) == '\\') /* add single slash */
            movmem(nl + 1, nl, LABELLEN - (nl - this->broken) - 1);
        else
        {
            this->nblines++;
            if (nl) *nl = '\0';
            /* Check space for this line */
            TextExtent(srch, font, &te);
            if (te.te_Extent.MaxX > this->extent.MaxX) this->extent.MaxX = te.t
e_Extent.MaxX;
            if (te.te_Extent.MinX < this->extent.MinX) this->extent.MinX = te.t
e_Extent.MinX;
        }
        srch = nl + 1;
    } while (nl);

    this->extent.MinY = 0;
    this->extent.MaxY = this->nblines * font->tf_YSize;
    this->bw = this->extent.MaxX - this->extent.MinX + 1;
    this->bh = this->extent.MaxY - this->extent.MinY + 1;
}

/* Work out rectangle used by text */
static void calc_rect(struct label *this)
{
    struct graph *g = this->o.g;
    struct RWindow *rw = g->io.rw;
    int delta, x0, y0;

    /* Find origin */
    /* Default */
    x0 = (this->x0 = ftol(rw->sx(rw, this->x))) + this->ddx;
    y0 = (this->y0 = ftol(rw->sy(rw, this->y))) + this->ddy;

    /* Adjust for justification */
    switch (this->jx)
    {
        case centre:
            x0 -= this->bw / 2;
            break;
        case bottom: /* right */
            x0 -= this->bw;
            break;
    }
    switch (this->jy)
    {
        case centre:
            y0 -= this->bh / 2;
            break;
        case bottom: /* right */
            y0 -= this->bh;
            break;
    }
    /* Extent defines actual rectangle */
    this->lims.MinX = x0 + this->extent.MinX;
    this->lims.MaxX = x0 + this->extent.MaxX;
    this->lims.MinY = y0 + this->extent.MinY;
    this->lims.MaxY = y0 + this->extent.MaxY;

    /* Make sure that it stays visible */
    /* Could one reduce the number of flops ? */
    /* Idea: Generalise inform/confirm */
    if ((delta = ftol(rw->sx(rw, g->a.x.min)) - this->lims.MinX) > 0 ||
        (delta = ftol(rw->sx(rw, g->a.x.max)) - this->lims.MaxX) < 0)
    {
        this->x0 += delta;
        this->lims.MinX += delta;
        this->lims.MaxX += delta;
    }
    if ((delta = ftol(rw->sy(rw, g->a.y.max)) - this->lims.MinY) > 0 ||
        (delta = ftol(rw->sy(rw, g->a.y.min)) - this->lims.MaxY) < 0)
    {
        this->y0 += delta;
        this->lims.MinY += delta;
        this->lims.MaxY += delta;
    }
}

/* Add the current position to the region */
static void add_region(struct label *this)
{
    if (!this->ref) this->ref = NewRegion();
    if (this->ref && !OrRectRegion(this->ref, &this->lims))
    {
        DisposeRegion(this->ref);
        this->ref = NULL;
    }
    if (!this->ref) nomem(this->o.g->io.win);
}

/* Were we clicked on ? */
static int down_label(struct label *this)
{
    struct graph *g = this->o.g;
    WORD sx = ftol(g->io.rw->sx(g->io.rw, g->s.x));
    WORD sy = ftol(g->io.rw->sy(g->io.rw, g->s.y));
    int inside;

    calc_rect(this);
    inside = sx >= this->lims.MinX && sx <= this->lims.MaxX &&
             sy >= this->lims.MinY && sy <= this->lims.MaxY;
    if (inside)
    {
        /* Setup internal info for move */
        this->o.mx = sx - this->x0;
        this->o.my = sy - this->y0;
        if (alloc_bkgd_label(this))
        {
            save_bkgd_label(this);
            if (this->selected)
            {
                SetDrMd(&this->bkgd, COMPLEMENT);
                RectFill(&this->bkgd, 0, 0, this->bw - 1, this->bh - 1);
            }
            this->ref = NULL;
            add_region(this); /* Original pos. will probably need refreshing */
     
            this->oldx = this->x; this->oldy = this->y;
        }
        else /* failed, no mem */
        {
            nomem(this->o.g->io.win);
            inside = FALSE;
        }
    }
    return inside;
}

/* Reverse label's rectangle (eg for selection) */
static void reverse_label(struct label *this)
{
    struct RastPort *rp = this->o.g->io.rw->rp;

    SetDrMd(rp, COMPLEMENT);
    RectFill(rp, this->lims.MinX, this->lims.MinY, this->lims.MaxX, this->lims.
MaxY);
}

/* Draw label */
static void draw_label(struct label *this, int allow_mes)
{
    struct graph *g = this->o.g;
    struct RastPort *rp = g->io.rw->rp;
    char *str;
    int i;

    calc_rect(this);
    SetAPen(rp, this->colour);
    SetDrMd(rp, JAM1);
    SetFont(rp, this->font);

    /* Draw all the lines of text */
    Move(rp, this->lims.MinX - this->extent.MinX, this->lims.MinY - this->exten
t.MinY + this->font->tf_Baseline);
    for (str = this->broken, i = this->nblines; i; i--)
    {
        int l = strlen(str);

        Text(rp, str, l);
        Move(rp, this->lims.MinX - this->extent.MinX, rp->cp_y + this->font->tf
_YSize);

        str += l + 1; /* Onto next string */
    }

    if (this->selected) reverse_label(this);
}

/* We're now selected */
static void select_label(struct label *this)
{
    this->selected = TRUE;
    if (this->o.g->ok && this->o.g->io.rw)
    {
        calc_rect(this);
        reverse_label(this);
    }
}

/* A quick walk around town ... */
static void move_label(struct label *this)
{
    restore_bkgd_label(this); /* erase label */
    this->x = this->o.g->s.x;
    this->y = this->o.g->s.y;
    calc_rect(this);
    save_bkgd_label(this);    /* Save background */
    draw_label(this, TRUE);   /* & draw at new pos */
}

/* Mouse buttonb released, refresh needed ? */
static struct Region *up_label(struct label *this)
{
    restore_bkgd_label(this); /* Restore at last pos */
    this->x = this->o.g->s.x;
    this->y = this->o.g->s.y;
    calc_rect(this);
    free_bkgd_label(this);
    if (this->x != this->oldx || this->y != this->oldy) /* we moved */
    {
        add_region(this); /* Refresh at new pos */
        return this->ref;
    }
    else
    {
        draw_label(this, TRUE); /* Just redraw, no refresh needed */
        DisposeRegion(this->ref);
        return NULL;
    }
}

/* End of selection, unhighlight */
static struct Region *deselect_label(struct label *this)
{
    this->selected = FALSE;
    if (this->o.g->ok && this->o.g->io.rw)
    {
        calc_rect(this);
        reverse_label(this);
    }
    return NULL;
}

/* Handle edit requester */
int edit_handler(struct Gadget *gg, ULONG class, struct Requester *req, struct
graph *g)
{
    if (gg->GadgetID == FONTLIST) /* User played with font list */
    {
        if (ModifyList(gg, req, req->RWindow, class == GADGETUP) == 2)
        {
            /* Double click -> exit */
            EndRequest(req, req->RWindow);
            return TRUE;
        }
        return FALSE;
    }
    else return std_ghandler(gg, class, req, g);
}

/* Allow user to edit us */
static int edit_label(struct label *this, struct Region **ref)
{
    struct Requester *req;
    struct Memory *m;
    struct Gadget *gl = NULL;
    int ret = FALSE;
    char text[LABELLEN], dx[NBLEN], dy[NBLEN], x[NBLEN], y[NBLEN], fname[FONTLE
N], fheight[INTLEN], colour[INTLEN];
    struct Gadget *leftg, *ctrxg, *rightg, *topg, *ctryg, *bottomg;

    this->ref = NULL;

    /* Create requester */
    strcpy(text, this->text);
    double2str(dx, this->dx);
    double2str(dy, this->dy);
    double2str(x, this->x);
    double2str(y, this->y);
    strcpy(fname, this->fname);
    remfont(fname);
    int2str(fheight, this->fheight);
    int2str(colour, this->colour);

    if ((m = NewMemory()) &&
        (req = InitReq(50, 15, 395, 166, m)) &&
        SetReqBorder(req, 1, m) &&
        AddIntuiText(&req->ReqText, "Edit Label", 158, 6, m) &&
        AddText(&gl, 0, "Text ", FALSE, text, LABELLEN, TRUE, 0, RELVERIFY, 49,
 20, 335, 10, TRUE, m) &&
        AddText(&gl, 0, "X = ", FALSE, x, NBLEN, TRUE, 0, RELVERIFY, 43, 40, 56
, 10, TRUE, m) &&
        AddText(&gl, 0, "dX = ", FALSE, dx, NBLEN, TRUE, 0, RELVERIFY, 147, 40,
 56, 10, TRUE, m) &&
        (leftg = AddRadio(&gl, 0, "left", TRUE, SELECTED * (this->jx == top), R
ELVERIFY, 16 + 32, 210, 40, 10, 10, m)) &&
        (ctrxg = AddRadio(&gl, 0, "centre", TRUE, SELECTED * (this->jx == centr
e), RELVERIFY, 8 + 32, 260, 40, 10, 10, m)) &&
        (rightg = AddRadio(&gl, 0, "right", TRUE, SELECTED * (this->jx == botto
m), RELVERIFY, 16 + 8, 326, 40, 10, 10, m)) &&
        AddText(&gl, 0, "Y = ", FALSE, y, NBLEN, TRUE, 0, RELVERIFY, 43, 60, 56
, 10, TRUE, m) &&
        AddText(&gl, 0, "dY = ", FALSE, dy, NBLEN, TRUE, 0, RELVERIFY, 147, 60,
 56, 10, TRUE, m) &&
        (topg = AddRadio(&gl, 0, "top", TRUE, SELECTED * (this->jy == top), REL
VERIFY, 512 + 1024, 210, 60, 10, 10, m)) &&
        (ctryg = AddRadio(&gl, 0, "centre", TRUE, SELECTED * (this->jy == centr
e), RELVERIFY, 256 + 1024, 260, 60, 10, 10, m)) &&
        (bottomg = AddRadio(&gl, 0, "bottom", TRUE, SELECTED * (this->jy == bot
tom), RELVERIFY, 256 + 512, 326, 60, 10, 10, m)) &&
        AddText(&gl, 0, "Colour ", FALSE, colour, INTLEN, TRUE, 0, RELVERIFY, 2
36, 80, 32, 10, TRUE, m) &&
        AddText(&gl, 0, "Size ", FALSE, fheight, INTLEN, TRUE, 0, RELVERIFY, 22
0, 150, 32, 10, TRUE, m) &&
        AddList(&gl, FONTLIST, "Font", &flist, fname, FONTLEN, 0, RELVERIFY, 11
, 80, 160, 80, TRUE, m) &&
        AddBox(&gl, TRUE, "Ok", 0, RELVERIFY | ENDGADGET, 255, 100, 65, 15, FAL
SE, m) &&
        AddBox(&gl, FALSE, "Cancel", 0, RELVERIFY | ENDGADGET, 255, 130, 65, 15
, FALSE, m))
    {
        SetReqGadgets(req, gl);
        if (ret = DoRequest(req, this->o.g, edit_handler) && *stpblk(text) != '
\0')
        {
            /* Update label */
            double new;
            int fh;
            struct TextFont *newfont;

            add_region(this); /* Refresh needed at old pos */

            /* New justification, etc */
            if ((this->colour = str2int(colour)) == INOVAL) this->colour = 1;
            strcpy(this->text, text);
            if ((new = str2double(x)) != NOVAL) this->x = new;
            if ((new = str2double(y)) != NOVAL) this->y = new;
            if ((this->dx = str2double(dx)) == NOVAL) this->dx = 0.0;
            if ((this->dy = str2double(dy)) == NOVAL) this->dy = 0.0;

            if (leftg->Flags & SELECTED) this->jx = top;
            else if (ctrxg->Flags & SELECTED) this->jx = centre;
            else this->jx = bottom;

            if (topg->Flags & SELECTED) this->jy = top;
            else if (ctryg->Flags & SELECTED) this->jy = centre;
            else this->jy = bottom;

            /* Check font */
            addfont(fname);
            if ((fh = str2int(fheight)) == INOVAL || fh <= 0) fh = LABELHEIGHT;
     
            if (newfont = open_font(this->o.g, fname, fh, 0, 0))
            {
                strcpy(this->fname, fname);
                this->fheight = fh;
                CloseFont(this->font);
                this->font = newfont;
            }
            else message(this->o.g, "Invalid font specified", (char *)NULL);

            /* Do precalculation */
            setup_label(this);
            calc_rect(this);
            add_region(this); /* New position needs refreshing */
        }
    }
    Free(m);
    *ref = this->ref;
    return ret;
}

/* No vsriables used in labels */
int var_change_label(struct label *this, char *name)
{
    return FALSE;
}

/* Confirm changes made */
static void confirm_label(struct label *this, int ok)
{
    if (ok)
    {
        CloseFont(this->font);
        this->font = this->newfont;
        setup_label(this);
    }
    else CloseFont(this->newfont); /* failed */
}

/* Resolution has changed */
static int inform_label(struct label *this, int ok)
{
    if (this->newfont = open_font(this->o.g, this->fname, this->fheight, 0, 0))
     
        return TRUE;

    message(this->o.g, "Couldn't open font", (char *)NULL);
    return FALSE;

}

/* Write label to file */
static int save_label(struct label *this, FILE *f)
{
    short tag = LABEL_TAG;
    short end = LABEL_END;

    return WRITE(f, tag) &&
           WRITE(f, this->text) &&
           WRITE(f, this->colour) &&
           WRITE(f, this->fname) &&
           WRITE(f, this->fheight) &&
           WRITE(f, this->x) &&
           WRITE(f, this->y) &&
           WRITE(f, this->dx) &&
           WRITE(f, this->dy) &&
           WRITE(f, end);
}

/* free label */
static struct Region *delete_label(struct label *this)
{
    struct Region *ref;

    /* Label's position will need refreshing */
    this->ref = NULL;
    if (this->o.g->ok && this->o.g->io.rw) add_region(this);
    ref = this->ref;

    if (this->font) CloseFont(this->font);
    FreeMem(this, sizeof(struct label));

    return ref;
}

/* Initialise label structure */
static struct label *make_label(struct graph *g)
{
    struct label *this = AllocMem(sizeof(struct label), 0L);
    const static struct label def_l = {
        {
            { NULL },
            NULL, "", TRUE, 0, 0,
            (void *)delete_label, (void *)select_label, (void *)deselect_label,
     
            (void *)down_label, (void *)move_label, (void *)up_label,
            (void *)edit_label, (void *)draw_label, (void *)notdone,
            (void *)notdone, (void *)var_change_label, (void *)save_label,
            (void *)inform_label, (void *)confirm_label
        },
        "", 1, LABELFONT, LABELHEIGHT, NOVAL, NOVAL, 0.0, 0.0, FALSE
    };

    if (this)
    {
        *this = def_l;
        this->o.g = g;
        /* Default font *must* be there */
        if (this->font = open_font(g, LABELFONT, LABELHEIGHT, 0, 0))
        {
            return this;
        }
        else
            message(g, "Couldn't open font", (char *)NULL);
        FreeMem(this, sizeof(struct label));
    }
    else
        message(g, "No memory !", (char *)NULL);
    return NULL;
}

/* Load label from file */
struct label *load_label(struct graph *g, FILE *f)
{
    struct label *this = make_label(g);

    if (this)
    {
        short tag;

        if (READ(f, this->text) &&
            READ(f, this->colour) &&
            READ(f, this->fname) &&
            READ(f, this->fheight) &&
            READ(f, this->x) &&
            READ(f, this->y) &&
            READ(f, this->dx) &&
            READ(f, this->dy) &&
            READ(f, tag) &&
            tag == LABEL_END)
        {
            struct TextFont *newfont;

            if (newfont = open_font(this->o.g, this->fname, this->fheight, 0, 0
))
            {
                CloseFont(this->font);
                this->font = newfont;
            }
            else /* Lack of font isn't drastic */
            {
                message(g, "No such font available", this->fname, (char *)NULL)
;
                strcpy(this->fname, LABELFONT);
            }
            setup_label(this);
            return this;
        }
        delete_label(this);
    }
    return NULL;
}

/* Create a new label, at pos. (x,y). Asks user for text */
struct label *new_label(struct graph *g, double x, double y)
{
    struct label *this = make_label(g);

    if (this)
    {
        /* Create requester */
        struct Requester *req;
        struct Memory *m;
        struct Gadget *gl = NULL;
        int ret = FALSE;

        this->x = x;
        this->y = y;

        if ((m = NewMemory()) &&
            (req = InitReq(50, 20, 160, 65, m)) &&
            SetReqBorder(req, 1, m) &&
            AddIntuiText(&req->ReqText, "Add Label", 44, 6, m) &&
            AddText(&gl, TRUE, "Text ", FALSE, this->text, LABELLEN, TRUE, 0, R
ELVERIFY | ENDGADGET, 49, 20, 100, 10, TRUE, m) &&
            AddBox(&gl, TRUE, "Ok", 0, RELVERIFY | ENDGADGET, 8, 40, 65, 15, FA
LSE, m) &&
            AddBox(&gl, FALSE, "Cancel", 0, RELVERIFY | ENDGADGET, 88, 40, 65,
15, FALSE, m))
        {
            SetReqGadgets(req, gl);
            if (ret = DoRequest(req, g, std_ghandler) && *stpblk(this->text) !=
 '\0')
            {
                /* Create it */
                setup_label(this);
                if (g->ok && g->io.rw) draw_label(this, TRUE);
                Free(m);
                return this;
            }
        }
        Free(m);
        delete_label(this);
    }
    else
        message(g, "No memory !", (char *)NULL);
    return NULL;
}

