#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "grap.h"
#include "y.tab.h"

#define MAXTICK 200
int     ntick   = 0;
double  tickval[MAXTICK];       /* tick values (one axis at a time */
char    *tickstr[MAXTICK];      /* and labels */

int     tside   = 0;
int     tlist   = 0;            /* 1 => explicit values given */
int     toffside = 0;           /* no ticks on these sides */
int     goffside = 0;           /* no ticks on grid on these sides */
int     tick_dir = OUT;
double  ticklen = TICKLEN;      /* default tick length */
int     autoticks = LEFT|BOT;
int     autodir = 0;            /* set LEFT, etc. if automatic ticks go in */

void savetick(double f, char *s)        /* remember tick location and label */
{
       if (ntick >= MAXTICK)
               ERROR "too many ticks (%d)", MAXTICK FATAL;
       tickval[ntick] = f;
       tickstr[ntick] = s;
       ntick++;
}

void dflt_tick(double f)
{
       if (f >= 0.0)
               savetick(f, tostring("%g"));
       else
               savetick(f, tostring("\\%g"));
}

void tickside(int n)    /* remember which side these ticks/gridlines go on */
{
       tside |= n;
}

void tickoff(int side)  /* remember explicit sides */
{
       toffside |= side;
}

void gridtickoff(void)  /* turn grid ticks off on the side previously specified (ugh) */
{
       goffside = tside;
}

void setlist(void)      /* remember that there was an explicit list */
{
       tlist = 1;
}

void tickdir(int dir, double val, int explicit) /* remember in/out [expr] */
{
       tick_dir = dir;
       if (explicit)
               ticklen = val;
}

void ticks(void)                /* set autoticks after ticks statement */
{
       /* was there an explicit "ticks [side] off"? */
       if (toffside)
               autoticks &= ~toffside;
       /* was there an explicit list? (eg "ticks at ..." or "ticks from ...") */
       if (tlist) {
               if (tside & (BOT|TOP))
                       autoticks &= ~(BOT|TOP);
               if (tside & (LEFT|RIGHT))
                       autoticks &= ~(LEFT|RIGHT);
       }
       /* was there a side without a list? (eg "ticks left in") */
       if (tside && !tlist) {
               if (tick_dir == IN)
                       autodir |= tside;
               if (tside & (BOT|TOP))
                       autoticks = (autoticks & ~(BOT|TOP)) | (tside & (BOT|TOP));
               if (tside & (LEFT|RIGHT))
                       autoticks = (autoticks & ~(LEFT|RIGHT)) | (tside & (LEFT|RIGHT));
       }
       tlist = tside = toffside = goffside = 0;
       tick_dir = OUT;
}

double modfloor(double f, double t)
{
       t = fabs(t);
       return floor(f/t) * t;
}

double modceil(double f, double t)
{
       t = fabs(t);
       return ceil(f/t) * t;
}

double  xtmin, xtmax;   /* range of ticks */
double  ytmin, ytmax;
double  xquant, xmult;  /* quantization & scale for auto x ticks */
double  yquant, ymult;
double  lograt = 5;

void do_autoticks(Obj *p)       /* make set of ticks for default coord only */
{
       double x, xl, xu, q;

       if (p == NULL)
               return;
       fprintf(tfd, "Autoticks:\t# x %g..%g, y %g..%g",
               p->pt.x, p->pt1.x, p->pt.y, p->pt1.y);
       fprintf(tfd, ";   xt %g,%g, yt %g,%g, xq,xm = %g,%g, yq,ym = %g,%g\n",
               xtmin, xtmax, ytmin, ytmax, xquant, xmult, yquant, ymult);
       if ((autoticks & (BOT|TOP)) && p->pt1.x >= p->pt.x) {   /* make x ticks */
               q = xquant;
               xl = p->pt.x;
               xu = p->pt1.x;
               if (xl >= xu)
                       dflt_tick(xl);
               else if ((p->log & XFLAG) && xu/xl >= lograt) {
                       for (x = q; x < xu; x *= 10) {
                               logtick(x, xl, xu);
                               if (xu/xl <= 100) {
                                       logtick(2*x, xl, xu);
                                       logtick(5*x, xl, xu);
                               }
                       }
               } else {
                       xl = modceil(xtmin - q/100, q);
                       xu = modfloor(xtmax + q/100, q) + q/2;
                       for (x = xl; x <= xu; x += q)
                               dflt_tick(x);
               }
               tside = autoticks & (BOT|TOP);
               ticklist(p, 0);
       }
       if ((autoticks & (LEFT|RIGHT)) && p->pt1.y >= p->pt.y) {        /* make y ticks */
               q = yquant;
               xl = p->pt.y;
               xu = p->pt1.y;
               if (xl >= xu)
                       dflt_tick(xl);
               else if ((p->log & YFLAG) && xu/xl >= lograt) {
                       for (x = q; x < xu; x *= 10) {
                               logtick(x, xl, xu);
                               if (xu/xl <= 100) {
                                       logtick(2*x, xl, xu);
                                       logtick(5*x, xl, xu);
                               }
                       }
               } else {
                       xl = modceil(ytmin - q/100, q);
                       xu = modfloor(ytmax + q/100, q) + q/2;
                       for (x = xl; x <= xu; x += q)
                               dflt_tick(x);
               }
               tside = autoticks & (LEFT|RIGHT);
               ticklist(p, 0);
       }
}

void logtick(double v, double lb, double ub)
{
       float slop = 1.0;       /* was 1.001 */

       if (slop * lb <= v && ub >= slop * v)
               dflt_tick(v);
}

Obj *setauto(void)      /* compute new min,max, and quant & mult */
{
       Obj *p, *q;

       if ((q = lookup("lograt",0)) != NULL)
               lograt = q->fval;
       for (p = objlist; p; p = p->next)
               if (p->type == NAME && strcmp(p->name,dflt_coord) == 0)
                       break;
       if (p) {
               if ((p->log & XFLAG) && p->pt1.x/p->pt.x >= lograt)
                       autolog(p, 'x');
               else
                       autoside(p, 'x');
               if ((p->log & YFLAG) && p->pt1.y/p->pt.y >= lograt)
                       autolog(p, 'y');
               else
                       autoside(p, 'y');
       }
       return p;
}

void autoside(Obj *p, int side)
{
       double r, s, d, ub, lb;

       if (side == 'x') {
               xtmin = lb = p->pt.x;
               xtmax = ub = p->pt1.x;
       } else {
               ytmin = lb = p->pt.y;
               ytmax = ub = p->pt1.y;
       }
       if (ub <= lb)
               return; /* cop out on little ranges */
       d = ub - lb;
       r = s = 1;
       while (d * s < 10)
               s *= 10;
       d *= s;
       while (10 * r < d)
               r *= 10;
       if (r > d/3)
               r /= 2;
       else if (r <= d/6)
               r *= 2;
       if (side == 'x') {
               xquant = r / s;
       } else {
               yquant = r / s;
       }
}

void autolog(Obj *p, int side)
{
       double r, s, t, ub, lb;
       int flg;

       if (side == 'x') {
               xtmin = lb = p->pt.x;
               xtmax = ub = p->pt1.x;
               flg = p->coord & XFLAG;
       } else {
               ytmin = lb = p->pt.y;
               ytmax = ub = p->pt1.y;
               flg = p->coord & YFLAG;
       }
       for (s = 1; lb * s < 1; s *= 10)
               ;
       lb *= s;
       ub *= s;
       for (r = 1; 10 * r < lb; r *= 10)
               ;
       for (t = 1; t < ub; t *= 10)
               ;
       if (side == 'x')
               xquant = r / s;
       else
               yquant = r / s;
       if (flg)
               return;
       if (ub / lb < 100) {
               if (lb >= 5 * r)
                       r *= 5;
               else if (lb >= 2 * r)
                       r *= 2;
               if (ub * 5 <= t)
                       t /= 5;
               else if (ub * 2 <= t)
                       t /= 2;
               if (side == 'x') {
                       xtmin = r / s;
                       xtmax = t / s;
               } else {
                       ytmin = r / s;
                       ytmax = t / s;
               }
       }
}

void iterator(double from, double to, int op, double by, char *fmt)     /* create an iterator */
{
       double x;

       /* should validate limits, etc. */
       /* punt for now */

       dprintf("iterate from %g to %g by %g, op = %c, fmt=%s\n",
               from, to, by, op, fmt ? fmt : "");
       switch (op) {
       case '+':
       case ' ':
               for (x = from; x <= to + (SLOP-1) * by; x += by)
                       if (fmt)
                               savetick(x, tostring(fmt));
                       else
                               dflt_tick(x);
               break;
       case '-':
               for (x = from; x >= to; x -= by)
                       if (fmt)
                               savetick(x, tostring(fmt));
                       else
                               dflt_tick(x);
               break;
       case '*':
               for (x = from; x <= SLOP * to; x *= by)
                       if (fmt)
                               savetick(x, tostring(fmt));
                       else
                               dflt_tick(x);
               break;
       case '/':
               for (x = from; x >= to; x /= by)
                       if (fmt)
                               savetick(x, tostring(fmt));
                       else
                               dflt_tick(x);
               break;
       }
       if (fmt)
               free(fmt);
}

void ticklist(Obj *p, int explicit)     /* fire out the accumulated ticks */
                                       /* 1 => list, 0 => auto */
{
       if (p == NULL)
               return;
       fprintf(tfd, "Ticks_%s:\n\tticklen = %g\n", p->name, ticklen);
       print_ticks(TICKS, explicit, p, "ticklen", "");
}

void print_ticks(int type, int explicit, Obj *p, char *lenstr, char *descstr)
{
       int i, logflag, inside;
       char buf[100];
       double tv;

       for (i = 0; i < ntick; i++)     /* any ticks given explicitly? */
               if (tickstr[i] != NULL)
                       break;
       if (i >= ntick && type == TICKS)        /* no, so use values */
               for (i = 0; i < ntick; i++) {
                       if (tickval[i] >= 0.0)
                               sprintf(buf, "%g", tickval[i]);
                       else
                               sprintf(buf, "\\-%g", -tickval[i]);
                       tickstr[i] = tostring(buf);
               }
       else
               for (i = 0; i < ntick; i++) {
                       if (tickstr[i] != NULL) {
                               sprintf(buf, tickstr[i], tickval[i]);
                               free(tickstr[i]);
                               tickstr[i] = tostring(buf);
                       }
               }
       logflag = sidelog(p->log, tside);
       for (i = 0; i < ntick; i++) {
               tv = tickval[i];
               halfrange(p, tside, tv);
               if (logflag) {
                       if (tv <= 0.0)
                               ERROR "can't take log of tick value %g", tv FATAL;
                       logit(tv);
               }
               if (type == GRID)
                       inside = LEFT|RIGHT|TOP|BOT;
               else if (explicit)
                       inside = (tick_dir == IN) ? tside : 0;
               else
                       inside = autodir;
               if (tside & BOT)
                       maketick(type, p->name, BOT, inside, tv, tickstr[i], lenstr, descstr);
               if (tside & TOP)
                       maketick(type, p->name, TOP, inside, tv, tickstr[i], lenstr, descstr);
               if (tside & LEFT)
                       maketick(type, p->name, LEFT, inside, tv, tickstr[i], lenstr, descstr);
               if (tside & RIGHT)
                       maketick(type, p->name, RIGHT, inside, tv, tickstr[i], lenstr, descstr);
               if (tickstr[i]) {
                       free(tickstr[i]);
                       tickstr[i] = NULL;
               }
       }
       ntick = 0;
}

void maketick(int type, char *name, int side, int inflag, double val, char *lab, char *lenstr, char *descstr)
{
       char *sidestr, *td;

       fprintf(tfd, "\tline %s ", descstr);
       inflag &= side;
       switch (side) {
       case BOT:
       case 0:
               td = inflag ? "up" : "down";
               fprintf(tfd, "%s %s from (x_%s(%g),0)", td, lenstr, name, val);
               break;
       case TOP:
               td = inflag ? "down" : "up";
               fprintf(tfd, "%s %s from (x_%s(%g),frameht)", td, lenstr, name, val);
               break;
       case LEFT:
               td = inflag ? "right" : "left";
               fprintf(tfd, "%s %s from (0,y_%s(%g))", td, lenstr, name, val);
               break;
       case RIGHT:
               td = inflag ? "left" : "right";
               fprintf(tfd, "%s %s from (framewid,y_%s(%g))", td, lenstr, name, val);
               break;
       }
       fprintf(tfd, "\n");
       if (type == GRID && (side & goffside))  /* wanted no ticks on grid */
               return;
       sidestr = tick_dir == IN ? "start" : "end";
       if (lab != NULL) {
               /* BUG: should fix size of lab here */
               double wid = strlen(lab)/7.5 + (tick_dir == IN ? 0 : 0.1);      /* estimate width at 15 chars/inch */
               switch (side) {
               case BOT: case 0:
                       /* can drop "box invis" with new pic */
                       fprintf(tfd, "\tbox invis \"%s\" ht .25 wid 0 with .n at last line.%s",
                               lab, sidestr);
                       break;
               case TOP:
                       fprintf(tfd, "\tbox invis \"%s\" ht .2 wid 0 with .s at last line.%s",
                               lab, sidestr);
                       break;
               case LEFT:
                       fprintf(tfd, "\t\"%s \" wid %.2f rjust at last line.%s",
                               lab, wid, sidestr);
                       break;
               case RIGHT:
                       fprintf(tfd, "\t\" %s\" wid %.2f ljust at last line.%s",
                               lab, wid, sidestr);
                       break;
               }
               /* BUG: works only if "down x" comes before "at wherever" */
               lab_adjust();
               fprintf(tfd, "\n");
       }
}

Attr    *grid_desc      = 0;

void griddesc(Attr *a)
{
       grid_desc = a;
}

void gridlist(Obj *p)
{
       char *framestr;

       if ((tside & (BOT|TOP)) || tside == 0)
               framestr = "frameht";
       else
               framestr = "framewid";
       fprintf(tfd, "Grid_%s:\n", p->name);
       tick_dir = IN;
       print_ticks(GRID, 0, p, framestr, desc_str(grid_desc));
       if (grid_desc) {
               freeattr(grid_desc);
               grid_desc = 0;
       }
}

char *desc_str(Attr *a) /* convert DOT to "dotted", etc. */
{
       static char buf[50], *p;

       if (a == NULL)
               return p = "";
       switch (a->type) {
       case DOT:       p = "dotted"; break;
       case DASH:      p = "dashed"; break;
       case INVIS:     p = "invis"; break;
       default:        p = "";
       }
       if (a->fval != 0.0) {
               sprintf(buf, "%s %g", p, a->fval);
               return buf;
       } else
               return p;
}

sidelog(int logflag, int side)  /* figure out whether to scale a side */
{
       if ((logflag & XFLAG) && ((side & (BOT|TOP)) || side == 0))
               return 1;
       else if ((logflag & YFLAG) && (side & (LEFT|RIGHT)))
               return 1;
       else
               return 0;
}