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

/* Main program */

#include <exec/types.h>
#include <libraries/dos.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include "graph.h"
#include "file.h"
#include "object.h"
#include "uio.h"
#include "list.h"
#include "grph.h"
#include "user/eval.h"
#include "tracker.h"

#include <proto/exec.h>
#include <proto/dos.h>

/* Gadget ids for variables requester */
#define VARSLIST 2
#define VARSDEL 3

const char *eval_messages[] = {
    "No error",
    "Syntax error",
    "Out of memory",
    "Right bracket expected",
    "Obsolete",
    "Left bracket expected",
    "Function not differentiable",
    "Recursion detected",
    "Result not numeric"
};

/* The various libraries used */
struct LayersBase *LayersBase;
extern struct DiskfontBase *DiskfontBase;
extern struct GfxBase *GfxBase;
extern struct IntuitionBase *IntuitionBase;
struct ArpBase *ArpBase;

list graph_list;    /* List of graphs */
context vars;       /* List of variables */

static int vars_saved = TRUE;
/* Used by variable requester */
static char var[VARLEN], val[EXPRLEN];
static struct Gadget *valg, *delg;
static struct ListInfo *var_info;

/* Inform objects that variable var has changed value. They may need to
  recalculate */
static void check_vars(char *var)
{
    struct graph *scan;
    struct object *o;

    /* Variables are global ==> for all graphs, all objects */
    for (scan = first(&graph_list); succ(scan); scan = succ(scan))
        for (o = first(&scan->o_list); succ(o); o = succ(o))
            o->var_change(o, var);
}

/* Redraw all graphs (eg after variables have changed) */
static void draw_all(void)
{
    struct graph *g;

    for (g = first(&graph_list); succ(g); g = succ(g))
        draw_graph(g, TRUE);
}

/* Load a graph from a file into a new graph. See file format. */
static void load_file(struct graph *from)
{
    FILE *f;
    char file[FILELEN];

    if (getfile(file, "Load graph"))
        if (f = fopen(file, "r"))
        {
            short tag;

            if (READ(f, tag) && tag == GRAPH_TAG)
            {
                struct graph *g = load_graph(from, f);

                if (g) add_head(&graph_list, g);
            }
            else
                message(from, "File is not a graph", (char *)NULL);
            fclose(f);
        }
        else
            message(from, "Couldn't open file", file, (char *)NULL);
}

/* Save current graph to a file. See file format. */
/* TBD: Provide some default file name */
static void save_file(struct graph *g)
{
    FILE *f;
    char file[FILELEN];

    if (getfile(file, "Save graph"))
        if (f = fopen(file, "w"))
        {
            save_graph(g, f);
            fclose(f);
        }
        else
            message(g, "Couldn't open file", file, (char *)NULL);
}

/* Read variables from a file. See file format. This adds variables to the
   current context, it doesn't eliminate the old ones. */
static void load_variables(struct graph *from)
{
    FILE *f;
    char file[FILELEN];

    if (getfile(file, "Load variables"))
        if (f = fopen(file, "r"))
        {
            short tag;
            int ok = FALSE;

            if (READ(f, tag) && tag == FILE_TAG) /* This is a variable file */
            {
                int done = FALSE;

                ok = TRUE;
                do /* Read all the file */
                {
                    short tag;

                    if (READ(f, tag))
                    {
                        switch (tag)
                        {
                            case FILE_END:
                                done = TRUE;
                                break;
                            case VAR_TAG: /* Another variable */
                                {
                                    char name[VARLEN], val[EXPRLEN];

                                    if (READ(f, name) &&
                                        READ(f, val) &&
                                        READ(f, tag) && tag == VAR_END)
                                    {
                                        value vv = compile(val);

                                        if (!vv || !set_var_name(name, vv))
                                            message(from, "Failed to set variab
le(no memory ?)", (char *)NULL);
                                        check_vars(name);
                                    }
                                    else
                                        ok = FALSE;
                                }
                                break;
                            default:
                                ok = FALSE;
                                break;
                        }
                    }
                    else
                        ok = FALSE;
                } while (!done && ok);
            }
            fclose(f);
            if (!ok)
                message(from, "File does not contain variables", (char *)NULL);
     
            else draw_all();
        }
        else
            message(from, "Couldn't open file", file, (char *)NULL);
}

/* Save variables. See file format. */
static void save_variables(struct graph *g)
{
    FILE *f;
    char file[FILELEN];

    if (getfile(file, "Save variables"))
        if (f = fopen(file, "w"))
        {
            short tag = FILE_TAG;
            int ok = FALSE;

            if (WRITE(f, tag))
            {
                tnode *var;

                ok = TRUE;
                /* For all variables */
                for (var = first(&vars); ok && succ(var); var = succ(var))
                {
                    short end = VAR_END;
                    char name[VARLEN], expr[EXPRLEN];
                    value vv = get_var_name(var->ln_Name);

                    name[VARLEN - 1] = '\0';
                    strncpy(name, var->ln_Name, VARLEN - 1);
                    if (vv) decompile(vv, expr, EXPRLEN);
                    else expr[0] = '\0';

                    tag = VAR_TAG;

                    ok = WRITE(f, tag) &&
                         WRITE(f, name) &&
                         WRITE(f, expr) &&
                         WRITE(f, end);
                }
                tag = FILE_END;
                if (ok) ok = WRITE(f, tag);
            }
            fclose(f);
            if (!ok) message(g, "Error writing file", (char *)NULL);
            else vars_saved = TRUE;
        }
        else
            message(g, "Couldn't open file", file, (char *)NULL);
}

/* Handle variable requester. Could it be cleaned up ??? */
int vars_handler(struct Gadget *gg, ULONG class, struct Requester *req, struct
graph *g)
{
    int id = gg->GadgetID;
    int change = FALSE;     /* Has the list changed ? */
    int actval = FALSE;     /* Activate variable value gadget */
    int actvar = FALSE;     /*    "        "     name    "    */
    int refresh = FALSE;    /* value & name gadgets have changed */
    UWORD valpos, varpos;
    struct Gadget *varg = ListStr(var_info); /* The lists string gadget */

    if (gg == valg) /* Value gadget message, class is always GADGETUP */
    {
        valpos = RemoveGList(req->RWindow, valg, 1);
        varpos = RemoveGList(req->RWindow, varg, 1);
        refresh = TRUE;
        strip(var); /* remove blanks */
        strlwr(var);
        if (*var)
        {
            value vv = compile(val), old;

            if (eval_error != 0) /* Invalid value, allow correction */
            {
                DisplayBeep(req->RWindow->WScreen);
                actval = TRUE;
            }
            else /* Try & set variable */
            {
                if (old = get_var_name(var)) free_expr(old);
                else /* list changes, there will be an extra var */
                    change = TRUE;

                check_vars(var);
                if (!set_var_name(var, vv))
                {
                    alert(g->io.win, "Couldn't create variable", NULL);
                    change = FALSE;
                }
                val[0] = var[0] = '\0';
                actvar = TRUE;
            }
        }
        else /* blank var name -- erase val */
        {
            DisplayBeep(req->RWindow->WScreen);
            actvar = TRUE;
        }
    }
    else if (id == VARSDEL) /* Delete selected */
    {
        value vv;

        valpos = RemoveGList(req->RWindow, valg, 1);
        varpos = RemoveGList(req->RWindow, varg, 1);
        refresh = TRUE;
        strip(var);
        strlwr(var);
        if (*var && (vv = get_var_name(var)))
        {
            free_expr(vv);
            free_var_name(var);
            change = TRUE;
            check_vars(var);
        }
        var[0] = val[0] = '\0';
        actvar = TRUE;
    }
    else if (id == VARSLIST) /* Something in list selected */
    {
        if (ModifyList(gg, req, req->RWindow, class == GADGETUP) != 0 && *var)
        {   /* New value selected from list or typed, ie new name. Switch to va
lue gadget */
            value vv;

            valpos = RemoveGList(req->RWindow, valg, 1);
            varpos = RemoveGList(req->RWindow, varg, 1);
            refresh = TRUE;
            strip(var);

            if (!(vv = get_var_name(var)) || !decompile(vv, val, EXPRLEN)) val[
0] = '\0';
            actval = TRUE;
        }
    }
    else
        return std_ghandler(gg, class, req, g);

    /* Handle requester services */
    if (refresh)
    {
        AddGList(req->RWindow, varg, varpos, 1, req);
        AddGList(req->RWindow, valg, valpos, 1, req);
        RefreshGList(valg, req->RWindow, req, 1);
        RefreshGList(varg, req->RWindow, req, 1);
    }

    if (change) /* Variable list changed */
        if (!ChangeList(var_info, &vars, req, req->RWindow))
        {
            EndRequest(req, req->RWindow);
            /* Not enough memory ... */
            actval = actvar = FALSE;
       }

    if (actval) ActivateGadget(valg, req->RWindow, req);
    if (actvar) ActivateGadget(varg, req->RWindow, req);
    vars_saved = FALSE;

    return FALSE;
}

/* Setup variables requester */
void enter_vars(struct graph *g)
{
    struct Requester *req;
    struct Memory *m;
    struct Gadget *gl = NULL;

    if ((m = NewMemory()) &&
        (req = InitReq(50, 20, 285, 126, m)) &&
        SetReqBorder(req, 1, m) &&
        AddIntuiText(&req->ReqText, "Edit variables", 86, 6, m) &&
        (var_info = AddList(&gl, VARSLIST, "Variables", &vars, var, VARLEN, 0,
RELVERIFY, 20, 20, 160, 80, TRUE, m)) &&
        (valg = AddText(&gl, VARSLIST, "Value", FALSE, val, EXPRLEN, TRUE, 0, R
ELVERIFY, 64, 110, 116, 10, TRUE, m)) &&
        AddBox(&gl, TRUE, "Ok", 0, RELVERIFY | ENDGADGET, 200, 23, 65, 15, FALS
E, m) &&
        AddBox(&gl, VARSDEL, "Delete", 0, RELVERIFY, 200, 86, 65, 15, FALSE, m)
)
    {
        var[0] = val[0] = '\0';
        SetReqGadgets(req, gl);
        DoRequest(req, g, vars_handler);
        /* vars_handler called for every gadget event */
    }
    Free(m);
}

/* Create a new graph */
static struct graph *add_graph(struct graph *from)
{
    struct graph *g = new_graph(from);

    if (g) add_head(&graph_list, g);

    return g;
}

/* Delete a graph, checking for save */
static void remove_graph(struct graph *g)
{
    if (!g->saved) save_file(g);
    remove(g);
    delete_graph(g);
}

/* Global initialisation */
static int init(void)
{
    if (GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 0))
        if (DiskfontBase = (struct Library *)OpenLibrary("diskfont.library", 0)
)
            if (IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.
library", 33))
                if (LayersBase = (struct LayersBase *)OpenLibrary("layers.libra
ry", 33))
                    if (ArpBase = (struct ArpBase *)OpenLibrary("arp.library",
0))
                    {
                        new_list(&graph_list);
                        if (init_user() && init_grph() && add_graph(NULL))
                            if (init_expr())
                            {
                                init_context(&vars);
                                set_context(&vars);
                                return TRUE;
                            }
                            else alert(NULL, "Expression init failed", "No memo
ry ?");
                    }
                    else alert(NULL, "arp.library required", NULL);
                else alert(NULL, "layers.library V1.2 required", NULL);
            else alert(NULL, "intuition.library V1.2 required", NULL);
        else alert(NULL, "diskfont.library required", NULL);
    else alert(NULL, "graphics.library required", NULL);

    return FALSE;
}

/* Free ALL resources ! */
static void clean_up(void)
{
    struct graph *g, *next;

    cleanup_expr();
    cleanup_grph();
    cleanup_user();

    for (g = first(&graph_list); next = succ(g); g = next) remove_graph(g);

    if (ArpBase) CloseLibrary((struct Library *)ArpBase);
    if (LayersBase) CloseLibrary((struct Library *)LayersBase);
    if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase);
    if (DiskfontBase) CloseLibrary((struct Library *)DiskfontBase);
    if (GfxBase) CloseLibrary((struct Library *)GfxBase);

#ifdef DEBUG
    TrackerExitReport();
#endif
}

void main(int argc, char **argv)
{
    struct cmd cmd;
    int done = FALSE;

    if (init())
        do
            switch ((cmd = next_command()).command)
            {
                case close:
                    done = succ(succ(first(&graph_list))) == NULL; /* one graph
 left ? */
                    if (done && !vars_saved) save_variables(cmd.g);
                    remove_graph(cmd.g);
                    break;
                case _new_graph:
                    add_graph(cmd.g);
                    break;
                case _load_graph:
                    load_file(cmd.g);
                    break;
                case _save_graph:
                    save_file(cmd.g);
                    break;
                case print_graph:
                    prt_graph(cmd.g);
                    break;
                case iff_graph:
                    iff_todisk(cmd.g);
                    break;
                case save_vars:
                    save_variables(cmd.g);
                    break;
                case load_vars:
                    load_variables(cmd.g);
                    break;
                case quit:
                    if (!vars_saved) save_variables(cmd.g);
                    done = TRUE;
                    break;
                case add_function:
                    select_object(cmd.g, add_object(cmd.g, new_function(cmd.g))
);
                    break;
                case add_label:
                    add_object(cmd.g, (struct object *)new_label(cmd.g, cmd.dat
a.pt.x, cmd.data.pt.y));
                    break;
                case del_object:
                    remove_object(cmd.g, cmd.data.o);
                    break;
                case limits:
                    enter_limits(cmd.g);
                    break;
                case axes:
                    enter_axes(cmd.g);
                    break;
                case zoom:
                    zoom_in(cmd.g, cmd.data.zoom_in.x0, cmd.data.zoom_in.y0, cm
d.data.zoom_in.x1, cmd.data.zoom_in.y1);
                    break;
                case zoom_out:
                    zoom_factor(cmd.g, cmd.data.zoom_out);
                    break;
                case center:
                    center_graph(cmd.g, cmd.data.pt.x, cmd.data.pt.y);
                    break;
                case edit:
                    {
                        struct Region *ref;

                        if (cmd.data.o->edit(cmd.data.o, &ref))
                        {
                            cmd.g->saved = FALSE;
                            refresh_graph(cmd.g, TRUE, ref);
                        }
                    }
                    break;
                case improve:
                    refresh_graph(cmd.g, TRUE, cmd.data.o->improve(cmd.data.o))
;
                    break;
                case edit_vars:
                    enter_vars(cmd.g);
                    draw_all();
                    break;
            }
        while (!done);

    clean_up();
}

