/*
 *                 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 <math.h>
#include <string.h>

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

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

/* (private) class r_of_t, inherits from function */
struct r_of_t {
    struct function f;
    char expr[EXPRLEN];
    value function, derivee;
};

/* (private) class, inherits from point */
struct point_r_of_t
{
    point p;
    double r, theta;
};

typedef struct point_r_of_t point_r_of_t;

/*-------------------------------------------------------------------------*/
/*                      r_of_t class implementation                        */
/*-------------------------------------------------------------------------*/

/* Is the function displayable ? */
static int r_of_t_ok(const struct r_of_t *this)
{
    return this->f.min != NOVAL && this->f.max != NOVAL &&
           this->f.min < this->f.max &&
           (this->f.steps == INOVAL || this->f.steps >= 3);
}

/* Free resources */
static void destroy_r_of_t(struct r_of_t *this)
{
    free_var_list(&this->f.used);
    if (this->f.calc) free_list(&this->f.pts, this->f.sizept);
    this->f.calc = FALSE;
    if (this->function) free_expr(this->function);
    if (this->derivee) free_expr(this->derivee);
    this->function = this->derivee = NULL;
}

/* Init dependant parts of function */
static int create_r_of_t(struct r_of_t *this)
{
    this->f.calc = FALSE;
    this->f.var.name = this->f.vname;
    this->function = compile(this->expr);
    if (eval_error != 0)
    {
        message(this->f.o.g, "Compilation error:", eval_messages[eval_error], (
char *)NULL);
        return FALSE;
    }
    this->derivee = differentiate(this->function, this->f.vname);
    if (eval_error != NOT_DIFFERENTIABLE && eval_error != 0)
    {
        message(this->f.o.g, "Differentiation error:", eval_messages[eval_error
], (char *)NULL);
        return FALSE;
    }
    if (!make_var_list(this->function, &this->f.used))
        init_var_list(&this->f.used);
    return TRUE;
}

/* Allow user to define function */
static int edit_r_of_t(struct r_of_t *this, struct Region **ref)
{
    struct Requester *req;
    struct Memory *m;
    struct Gadget *gl = NULL, *sd, *nd;
    char from[NBLEN], to[NBLEN], steps[INTLEN], expr[EXPRLEN], tname[VARLEN], c
olour[INTLEN];
    int ret = FALSE;

    *ref = NULL;

    /* Create requester */
    double2str(from, this->f.min);
    double2str(to, this->f.max);
    int2str(steps, this->f.steps);
    int2str(colour, this->f.colour);
    strcpy(expr, this->expr);
    strcpy(tname, this->f.vname);

    if ((m = NewMemory()) &&
        (req = InitReq(50, 20, 255, 145, m)) &&
        SetReqBorder(req, 1, m) &&
        AddIntuiText(&req->ReqText, "Function", 95, 6, m) &&
        AddText(&gl, 0, "r(", FALSE, tname, VARLEN, TRUE, 0, RELVERIFY, 25, 20,
 25, 10, TRUE, m) &&
        AddText(&gl, 0, ")=", FALSE, expr, EXPRLEN, TRUE, 0, RELVERIFY, 81, 20,
 160, 10, TRUE, m) &&
        AddText(&gl, 0, "from ", FALSE, from, NBLEN, TRUE, 0, RELVERIFY, 49, 40
, 80, 10, TRUE, m) &&
        AddText(&gl, 0, "to ", FALSE, to, NBLEN, TRUE, 0, RELVERIFY, 167, 40, 8
0, 10, TRUE, m) &&
        AddText(&gl, 0, "steps ", FALSE, steps, INTLEN, TRUE, 0, RELVERIFY, 57,
 60, 32, 10, TRUE, m) &&
        AddText(&gl, 0, "colour ", FALSE, colour, INTLEN, TRUE, 0, RELVERIFY, 1
56, 60, 32, 10, TRUE, m) &&
        (sd = AddOption(&gl, 0, "Show discontinuities", TRUE, this->f.showdisc
* SELECTED, 0, 9, 80, 10, 10, m)) &&
        (nd = AddOption(&gl, 0, "Allow flat discontinuities", TRUE, this->f.nic
edisc * SELECTED, 0, 9, 100, 10, 10, m)) &&
        AddBox(&gl, TRUE, "Ok", 0, RELVERIFY | ENDGADGET, 40, 120, 65, 15, FALS
E, m) &&
        AddBox(&gl, FALSE, "Cancel", 0, RELVERIFY | ENDGADGET, 145, 120, 65, 15
, FALSE, m))
    {
        SetReqGadgets(req, gl);
        if (ret = DoRequest(req, this->f.o.g, std_ghandler))
        {
            *ref = full_refresh(this->f.o.g);

            /* Extract info */
            this->f.min = str2double(from);
            this->f.max = str2double(to);
            this->f.steps = str2int(steps);
            if ((this->f.colour = str2int(colour)) == INOVAL) this->f.colour =
1;
            this->f.showdisc = (sd->Flags & SELECTED) != 0;
            this->f.nicedisc = (nd->Flags & SELECTED) != 0;
            strcpy(this->expr, expr);
            strcpy(this->f.vname, tname);

            /* Create function */
            destroy_r_of_t(this);
            if (this->f.o.ok = r_of_t_ok(this)) this->f.o.ok = create_r_of_t(th
is);
        }
    }
    Free(m);

    return ret;
}

/* Calculate points of function */
static int calc_r_of_t(struct r_of_t *this, int allow_mes)
{
    double theta;
    int i;
    struct graph *const g = this->f.o.g;
    int const steps = (this->f.steps == INOVAL ? DEFSTEPS : this->f.steps) - 1;
     
    double const step = (this->f.max - this->f.min) / steps;
    char func[FNAMELEN + 30];

    new_list(&this->f.pts);

    strcpy(func, "Can't calculate points for ");
    strcat(func, this->f.o.name);
    strcat(func, ":");

    if (!create_quick(&this->f.var))
    {
        if (allow_mes) message(g, func, "Couldn't create variable", (char *)NUL
L);
        else alert(g->io.win, func, "Couldn't create variable");
        return FALSE;
    }

    /* Calculate steps points, spread evenly from min to max */
    for (i = 0, theta = this->f.min; i <= steps; i++, theta += step)
    {
        point_r_of_t *pt = alloc_node(this->f.sizept);

        if (!pt)
        { /* No mem */
            free_list(&this->f.pts, this->f.sizept);
            free_quick(&this->f.var);
            if (allow_mes) message(g, func, "No memory", (char *)NULL);
            else alert(g->io.win, func, "No memory");
            return FALSE;
        }
        add_tail(&this->f.pts, pt);

        pt->theta = theta;
        set_quick(&this->f.var, theta);
        pt->r = quick_eval(this->function);
        pt->p.state = (eval_error == 0) ? EXISTS : 0;
        /* Polar -> Rect conversion */
        pt->p.x = fabs(pt->r) * cos(theta); pt->p.y = fabs(pt->r) * sin(theta);
     
    }
    free_quick(&this->f.var);
    return TRUE;
}

/* Try to improve look of function by adding points. If fails, decides that
   there is a discontinuity */
/* see f_of_x.c for details */
static struct Region *improve_r_of_t(struct r_of_t *this)
{
    struct graph *const g = this->f.o.g;
    point_r_of_t *pt, *next;
    int ok = FALSE, iter, abort = FALSE;
    double flatx = FLAT * (g->a.y.max - g->a.y.min) / g->io.win->Height;
    double flaty = FLAT * (g->a.x.max - g->a.x.min) / g->io.win->Width;
    char msg[FNAMELEN + 30];
    char pass[20];
    struct Requester *req;
    struct Region *full = NULL;

    /* Flat has no meaning when graph incorrect */
    if (!this->f.o.g->ok) flatx = flaty = 0.0;

    if (!this->f.calc)
    {
        strcpy(msg, this->f.o.name);
        strcpy(msg, "not calculated!");
        message(g, msg, (char *)NULL);
        return NULL;
    }
    if (!this->derivee)
    {
        strcpy(msg, this->f.o.name);
        strcat(msg, " wasn't differentiable");
        message(g, msg, (char *)NULL);
        return NULL;
    }
    if (!create_quick(&this->f.var))
    {
        message(g, "Couldn't create variable", (char *)NULL);
        return NULL;
    }

    if (!(req = abort_request(g, "Improve: Pass 1")))
        message(g, "No Memory !", (char *)NULL);
    else
    {
        full = full_refresh(this->f.o.g);

        for (iter = 1; iter <= MAXITER && !ok && !abort; iter++)
        {
            sprintf(pass, "Improve: Pass %d", iter);
            set_abort_msg(req, pass);
            ok = TRUE;

            for (pt = first(&this->f.pts); succ(next = succ(pt)); pt = next)
            {
                if (aborted(req)) { abort = TRUE; break; }

                if ((pt->p.state & (EXISTS | OK)) == EXISTS) /* Only exists */
                {
                    double dx, dy, dr, dtheta;

                    pt->p.state |= OK;
                    pt->p.state &= ~DISC;

                    dtheta = next->theta - pt->theta;
                    set_quick(&this->f.var, pt->theta);
                    dr = quick_eval(this->derivee);
                    if (eval_error == 0)
                    {
                        double c = cos(pt->theta);
                        double s = sin(pt->theta);

                        /* A little elementary calculus ... */
                        dx = dr * c - pt->r * s;
                        dy = dr * s + pt->r * c;

                        if (eval_error == 0)
                        {
                            double ecartx = next->p.x - pt->p.x;
                            double errorx = fabs(ecartx - dtheta * dx);
                            double ecarty = next->p.y - pt->p.y;
                            double errory = fabs(ecarty - dtheta * dy);

                            /* Check both axes */
                            if ((errorx > fabs(ecartx) * MAXERROR && (!this->f.
nicedisc || errorx > flatx)) ||
                                (errory > fabs(ecarty) * MAXERROR && (!this->f.
nicedisc || errory > flaty)))
                            {
                                pt->p.state &= ~OK;
                                ok = FALSE;

                                if (iter == MAXITER) pt->p.state |= DISC;
                                else /* cut interval in 2 */
                                {
                                    point_r_of_t *newpt = alloc_node(this->f.si
zept);

                                    if (!newpt)
                                    {
                                        nomem(g->io.win);
                                        abort = TRUE;
                                        break;
                                    }

                                    newpt->theta = (pt->theta + next->theta) /
2;
                                    set_quick(&this->f.var, newpt->theta);
                                    newpt->r = quick_eval(this->function);
                                    newpt->p.state = (eval_error == 0) ? EXISTS
 : 0;
                                    newpt->p.x = fabs(newpt->r) * cos(newpt->th
eta);
                                    newpt->p.y = fabs(newpt->r) * sin(newpt->th
eta);
                                    insert(&this->f.pts, newpt, pt);
                                }
                            }
                        }
                    }
                }
            }
        }
        end_abort_request(req);
    }
    free_quick(&this->f.var);
    return full;
}

/* String representation of function */
static char *f2str_r_of_t(struct r_of_t *this, char *buf, int maxlen)
{
    buf[maxlen - 1] = '\0';
    strncpy(buf, "polar ", maxlen - 1);
    strncat(buf, this->f.o.name, maxlen - strlen(buf) - 1);
    strncat(buf, "(", maxlen - strlen(buf) - 1);
    strncat(buf, this->f.vname, maxlen - strlen(buf) - 1);
    strncat(buf, ")=", maxlen - strlen(buf) - 1);
    strncat(buf, this->expr, maxlen - strlen(buf) - 1);

    return buf;
}

static int save_r_of_t(struct r_of_t *this, FILE *f)
{
    short tag = R_OF_T_TAG;
    short end = R_OF_T_END;

    return WRITE(f, tag) &&
           WRITE(f, this->expr) &&
           WRITE(f, end);
}

/* free function */
static struct Region *delete_r_of_t(struct r_of_t *this)
{
    struct Region *full = full_refresh(this->f.o.g);

    destroy_r_of_t(this);
    FreeMem(this, sizeof(struct r_of_t));

    return full;
}

/* Create a new function */
struct r_of_t *new_r_of_t(struct graph *g, char *name)
{
    struct r_of_t *this = AllocMem(sizeof(struct r_of_t), MEMF_CLEAR);

    if (this)
    {
        /* Standard init */
        init_function(&this->f, g, name);
        /* Local methods */
        this->f.o.delete = (void *)delete_r_of_t;
        this->f.o.edit = (void *)edit_r_of_t;
        this->f.o.improve = (void *)improve_r_of_t;
        this->f.o.f2str = (void *)f2str_r_of_t;
        this->f.calcf = (void *)calc_r_of_t;
        this->f.save = (void *)save_r_of_t;
        this->f.sizept = sizeof(point_r_of_t);
        init_var_list(&this->f.used);
        return this;
    }
    message(g, "Couldn't create function:", "No memory", (char *)NULL);
    return NULL;
}

/* Save local data to file */
/* load from file */
struct r_of_t *load_r_of_t(struct graph *g, FILE *f)
{
    struct r_of_t *this = new_r_of_t(g, "");

    if (this)
    {
        short end;

        /* Read local data */
        if (READ(f, this->expr) &&
            READ(f, end) &&
            end == R_OF_T_END)
        {
            load_rest(&this->f, f); /* Read standard data */
            if (this->f.o.ok = r_of_t_ok(this)) this->f.o.ok = create_r_of_t(th
is);

            return this;
        }
        delete_r_of_t(this);
    }
    return NULL;
}

