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

/* Set up a coordinate system in a Rastport */
/* Copyright 1989, David Gay */
#include <exec/types.h>
#include <graphics/regions.h>
#include <intuition/intuition.h>
#include <proto/graphics.h>
#include <proto/layers.h>

#include <limits.h>
#include <math.h>

#include "coords.h"
#include "tracker.h"

extern int _FPERR;

/* Actual structure used */
struct RWin {
    struct RWindow rw;
    double xmin, xscale, ymin, yscale;
    double xoffset, yoffset;
    struct Region *clip, *oldRegion;
    int lostpos;
};

/*-------------------------------------------------------------------------*/
/*                            RWindow definition                           */
/*-------------------------------------------------------------------------*/

/* convert double to integer, round down. cf floor */
long ftol(double x)
{
    if (x >= 0) return (long)x;
    else return (long)floor(x);
}

/* Do Move/Draw style operation func, checking for overflow, etc */
static void rdo_RWin(struct RWin *this, void (*func)(struct RastPort *rp, long
sx, long sy), double x, double y)
{
    double sx, sy;

    if (this->lostpos) func = Move;

    _FPERR = 0;
    sx = this->rw.sx((struct RWindow *)this, x);
    sy = this->rw.sy((struct RWIndow *)this, y);
    /* Move, Draw only accept shorts */
    this->lostpos = (_FPERR != 0 || fabs(sx) > SHRT_MAX || fabs(sy) > SHRT_MAX)
;

    if (!this->lostpos) func(this->rw.rp, ftol(sx), ftol(sy));
}

/* The various conversion routines, to/from ints, for lin orr log scales, x or
y ax */
static double sx_lin(struct RWin *this, double x)
{
    return (x - this->xmin) * this->xscale + this->xoffset;
}

static double sx_log(struct RWin *this, double x)
{
    return (log10(x) - this->xmin) * this->xscale + this->xoffset;
}

static double sy_lin(struct RWin *this, double y)
{
    return (y - this->ymin) * this->yscale + this->yoffset;
}

static double sy_log(struct RWin *this, double y)
{
    return (log10(y) - this->ymin) * this->yscale + this->yoffset;
}

static double x_lin(struct RWin *this, long sx)
{
    return (sx - this->xoffset) / this->xscale + this->xmin;
}

static double x_log(struct RWin *this, long sx)
{
    return pow(10.0, (sx - this->xoffset) / this->xscale + this->xmin);
}

static double y_lin(struct RWin *this, long sy)
{
    return (sy - this->yoffset) / this->yscale + this->ymin;
}

static double y_log(struct RWin *this, long sy)
{
    return pow(10.0, (sy - this->yoffset) / this->yscale + this->ymin);
}

/* Delete a member of this class */
static void delete_RWin(struct RWin *this)
{
    if (this->clip)
    {
        InstallClipRegion(this->rw.rp->Layer, this->oldRegion);
        DisposeRegion(this->clip);
    }
    FreeMem(this, sizeof(struct RWin));
}

/* Create a coordinate system in Rastport rp (w by h pixels),
   {...}offset : offset in rp at which coords starts (normally > 0)
   {x,y}{min,max} : limits for coords
   logx, logy : logarithmic scale ?
   clip : setup clipping to {...}offset boundaries ?
*/
struct RWindow *new_RWindow(struct RastPort *rp, long w, long h,
                            long leftoffset, long bottomoffset, long rightoffse
t, long topoffset,
                            double xmin, double ymin, double xmax, double ymax,
     
                            long logx, long logy, long clip)
{
    long width, height;
    struct Rectangle rect;
    struct Region *r;
    struct RWin *this = AllocMem(sizeof(struct RWin), 0L);

    if (this)
    {
        /* Setup class methods, and private data */
        this->rw.delete = (void *)delete_RWin;
        this->rw.rdo = (void *)rdo_RWin;
        /* Setup scaling */
        this->rw.rp = rp;
        this->xoffset = leftoffset;
        this->yoffset = h - bottomoffset - 1;
        width = w - leftoffset - rightoffset - 1;
        height = h - bottomoffset - topoffset - 1;
        if (logx)
        {
            this->xmin = log10(xmin);
            this->xscale = width / (log10(xmax) - this->xmin);
            this->rw.sx = (void *)sx_log;
            this->rw.x = (void *)x_log;
        }
        else
        {
            this->xmin = xmin;
            this->xscale = width / (xmax - this->xmin);
            this->rw.sx = (void *)sx_lin;
            this->rw.x = (void *)x_lin;
        }
        if (logy)
        {
            this->ymin = log10(ymin);
            this->yscale = height / (this->ymin - log10(ymax));
            this->rw.sy = (void *)sy_log;
            this->rw.y = (void *)y_log;
        }
        else
        {
            this->ymin = ymin;
            this->yscale = height / (this->ymin - ymax);
            this->rw.sy = (void *)sy_lin;
            this->rw.y = (void *)y_lin;
        }

        if (clip)
        {
            /* Setup clipping */
            if (r = NewRegion())
            {
                rect.MinX = leftoffset;
                rect.MaxX = w - rightoffset - 1;
                rect.MinY = topoffset;
                rect.MaxY = h - bottomoffset - 1;
                if (OrRectRegion(r, &rect))
                {
                    this->clip = r;

/* Remark: Due to a bug(?) in InstallClipRegion, make sure that the currently
  installed region when EndRefresh is called is not NULL (trashed windows
  otherwise...). */
                    this->oldRegion = InstallClipRegion(rp->Layer, r);
                }
                else
                {
                    DisposeRegion(r);
                    r = NULL;
                }
            }
            if (!r)
            {
                FreeMem(this, sizeof(struct RWin));
                this = NULL;
            }
        }
        else
            this->clip = NULL;
    }
    /* Return the newly allocated instance */
    return (struct RWindow *)this;
}

