/*
 * $Id: engine.c,v 1.1 2005-09-18 22:04:19 dhmunro Exp $
 * Implement common properties of all GIST engines
 */
/* Copyright (c) 2005, The Regents of the University of California.
 * All rights reserved.
 * This file is part of yorick (http://yorick.sourceforge.net).
 * Read the accompanying LICENSE file for details.
 */

#include "gist.h"
#include "engine.h"
#include "draw.h"
#include "pstdlib.h"

Engine *gistEngines= 0;
Engine *gistActive= 0;
Engine *gistPreempt= 0;

#include <string.h>

static void DefaultClearArea(Engine *engine, GpBox *box);
static void MoreScratch(long np, long ns);

/* ------------------------------------------------------------------------ */

/* ARGSUSED */
static void DefaultClearArea(Engine *engine, GpBox *box)
{
  /* Default ClearArea triggers complete redraw */
  engine->Clear(engine, CONDITIONALLY);
  engine->lastDrawn= -1;
  engine->systemsSeen[0]= engine->systemsSeen[1]= 0;
  engine->damaged= engine->inhibit= 0;
}

Engine *GpNewEngine(long size, char *name, g_callbacks *on,
                    GpTransform *transform, int landscape,
  void (*Kill)(Engine*), int (*Clear)(Engine*,int), int (*Flush)(Engine*),
  void (*ChangeMap)(Engine*), int (*ChangePalette)(Engine*),
  int (*DrawLines)(Engine*,long,const GpReal*,const GpReal*,int,int),
  int (*DrawMarkers)(Engine*,long,const GpReal*,const GpReal *),
  int (*DrwText)(Engine*e,GpReal,GpReal,const char*),
  int (*DrawFill)(Engine*,long,const GpReal*,const GpReal*),
  int (*DrawCells)(Engine*,GpReal,GpReal,GpReal,GpReal,
                   long,long,long,const GpColor*),
  int (*DrawDisjoint)(Engine*,long,const GpReal*,const GpReal*,
                      const GpReal*,const GpReal*))
{
  long lname= name? strlen(name) : 0;
  Engine *engine;
  /* For Electric Fence package and maybe others, it is nice to ensure
     that size of block allocated for Engine is a multiple of the size
     of the most restrictively aligned object which can be in any
     Engine; assume this is a double.  */
  lname= (lname/sizeof(double) + 1)*sizeof(double);  /* >= lname+1 */
  engine= (Engine *)p_malloc(size+lname);
  if (!engine) return 0;

  /* Fill in Engine properties, link into gistEngines list */
  engine->next= gistEngines;
  gistEngines= engine;
  engine->nextActive= 0;
  engine->name= (char *)engine + size;
  strcpy(name? engine->name : "", name);
  engine->on = on;
  engine->active= 0;
  engine->marked= 0;

  engine->transform= *transform;
  engine->landscape= landscape? 1 : 0;
  GpDeviceMap(engine);
  /* (a proper map will be installed when the engine is activated) */
  engine->map.x.scale= engine->map.y.scale= 1.0;
  engine->map.x.offset= engine->map.y.offset= 0.0;

  /* No pseudocolor map initially */
  engine->colorChange= 0;
  engine->colorMode= 0;
  engine->nColors= 0;
  engine->palette= 0;

  /* No associated drawing initially */
  engine->drawing= 0;
  engine->lastDrawn= -1;
  engine->systemsSeen[0]= engine->systemsSeen[1]= 0;
  engine->inhibit= 0;
  engine->damaged= 0;  /* causes Clear if no ClearArea virtual function */
  engine->damage.xmin= engine->damage.xmax=
    engine->damage.ymin= engine->damage.ymax= 0.0;

  /* Fill in virtual function table */
  engine->Kill= Kill;
  engine->Clear= Clear;
  engine->Flush= Flush;
  engine->ChangeMap= ChangeMap;
  engine->ChangePalette= ChangePalette;
  engine->DrawLines= DrawLines;
  engine->DrawMarkers= DrawMarkers;
  engine->DrwText= DrwText;
  engine->DrawFill= DrawFill;
  engine->DrawCells= DrawCells;
  engine->DrawDisjoint= DrawDisjoint;
  engine->ClearArea= &DefaultClearArea;   /* damage causes complete redraw */

  return engine;
}

void GpDelEngine(Engine *engine)
{
  Engine *eng= gistEngines;
  if (!engine) return;

  /* Unlink from gistEngines list */
  if (engine->active) GpDeactivate(engine);
  if (eng==engine) gistEngines= engine->next;
  else {
    /* Because of recursive deletes necessary to deal with X window
       deletions (see xbasic.c:ShutDown, hlevel.c:ShutDownDev), if
       the engine has already been removed from the list, it means that
       this routine is being called for the second time for this engine,
       and p_free must NOT be called.  Fix this someday.  */
    while (eng && eng->next!=engine) eng= eng->next;
    if (!eng) return;
    eng->next= engine->next;
  }

  p_free(engine);
}

/* ------------------------------------------------------------------------ */

void GpKillEngine(Engine *engine)
{
  if (engine) engine->Kill(engine);
}

int GpActivate(Engine *engine)
{
  if (!engine) return 1;
  if (!engine->active) {
    engine->active= 1;
    engine->nextActive= gistActive;
    gistActive= engine;
    engine->ChangeMap(engine);
  }
  return 0;
}

int GpDeactivate(Engine *engine)
{
  if (!engine) return 1;
  if (engine->active) {
    Engine *active= gistActive;
    engine->active= 0;
    if (active==engine) gistActive= engine->nextActive;
    else {
      while (active->nextActive!=engine) active= active->nextActive;
      active->nextActive= engine->nextActive;
    }
  }
  return 0;
}

int GpPreempt(Engine *engine)
{
  gistPreempt= engine;
  if (engine && !engine->active) engine->ChangeMap(engine);
  return 0;
}

int GpActive(Engine *engine)
{
  if (!engine) return 0;
  return engine==gistPreempt? 1 : engine->active;
}

int GpClear(Engine *engine, int flag)
{
  int value= 0;
  if (!engine) {
    for (engine=GpNextActive(0) ; engine ; engine=GpNextActive(engine)) {
      engine->damaged= engine->inhibit= 0;
      engine->lastDrawn= -1;
      engine->systemsSeen[0]= engine->systemsSeen[1]= 0;
      value|= engine->Clear(engine, flag);
    }
  } else {
    engine->damaged= engine->inhibit= 0;
    engine->lastDrawn= -1;
    engine->systemsSeen[0]= engine->systemsSeen[1]= 0;
    value= engine->Clear(engine, flag);
  }
  return value;
}

int GpFlush(Engine *engine)
{
  if (!engine) {
    int value= 0;
    for (engine=GpNextActive(0) ; engine ; engine=GpNextActive(engine))
      value|= engine->Flush(engine);
    return value;
  }
  return engine->Flush(engine);
}

Engine *GpNextEngine(Engine *engine)
{
  return engine? engine->next : gistEngines;
}

Engine *GpNextActive(Engine *engine)
{
  if (gistPreempt) return engine? 0 : gistPreempt;
  else return engine? engine->nextActive : gistActive;
}

/* ------------------------------------------------------------------------ */

int GpSetTrans(const GpTransform *trans)
{
  Engine *engine;

  if (trans!=&gistT) gistT= *trans;

  for (engine=GpNextActive(0) ; engine ; engine=GpNextActive(engine))
    engine->ChangeMap(engine);

  return 0;
}

int GpLandscape(Engine *engine, int landscape)
{
  if (!engine) {
    for (engine=GpNextActive(0) ; engine ; engine=GpNextActive(engine))
      engine->landscape= landscape;
  } else {
    engine->landscape= landscape;
  }
  return 0;
}

void GpSetMap(const GpBox *src, const GpBox *dst, GpXYMap *map)
{
  map->x.scale= (dst->xmax-dst->xmin)/(src->xmax-src->xmin);
  map->x.offset=  dst->xmin - map->x.scale*src->xmin;
  map->y.scale= (dst->ymax-dst->ymin)/(src->ymax-src->ymin);
  map->y.offset=  dst->ymin - map->y.scale*src->ymin;
}

void GpDeviceMap(Engine *engine)
{
  GpSetMap(&engine->transform.viewport, &engine->transform.window,
           &engine->devMap);
}

void GpComposeMap(Engine *engine)
{
  GpMap *devx= &engine->devMap.x;
  GpMap *devy= &engine->devMap.y;
  GpMap *mapx= &engine->map.x;
  GpMap *mapy= &engine->map.y;
  mapx->scale=
    devx->scale*(gistT.viewport.xmax-gistT.viewport.xmin)/
                (gistT.window.xmax-gistT.window.xmin);
  mapx->offset= devx->offset + devx->scale*gistT.viewport.xmin -
                mapx->scale*gistT.window.xmin;
  mapy->scale=
    devy->scale*(gistT.viewport.ymax-gistT.viewport.ymin)/
                (gistT.window.ymax-gistT.window.ymin);
  mapy->offset= devy->offset + devy->scale*gistT.viewport.ymin -
                mapy->scale*gistT.window.ymin;
}

/* ------------------------------------------------------------------------ */

/* Scratch space used by GpIntPoints and GpIntSegs */
static void *scratch= 0;
static long scratchPoints= 0, scratchSegs= 0;

static void MoreScratch(long np, long ns)
{
  if (scratch) p_free(scratch);
  if (np) {
    np+= 64;
    scratch= (void *)p_malloc(sizeof(GpPoint)*np);
    scratchPoints= np;
    scratchSegs= (sizeof(GpPoint)*np)/sizeof(GpSegment);
  } else {
    ns+= 32;
    scratch= (void *)p_malloc(sizeof(GpSegment)*ns);
    scratchSegs= ns;
    scratchPoints= (sizeof(GpSegment)*ns)/sizeof(GpPoint);
  }
}

long GpIntPoints(const GpXYMap *map, long maxPoints, long n,
                 const GpReal *x, const GpReal *y, GpPoint **result)
{
  GpReal scalx= map->x.scale, offx= map->x.offset;
  GpReal scaly= map->y.scale, offy= map->y.offset;
  long i, np= maxPoints<n? maxPoints : n;
  GpPoint *point;

  if (np+1>scratchPoints) MoreScratch(np+1, 0); /* allow for closure pt */
  *result= point= scratch;

  for (i=0 ; i<np ; i++) {
    point[i].x= (short)(scalx*x[i]+offx);
    point[i].y= (short)(scaly*y[i]+offy);
  }

  return np;
}

long GpIntSegs(const GpXYMap *map, long maxSegs, long n,
               const GpReal *x1, const GpReal *y1,
               const GpReal *x2, const GpReal *y2, GpSegment **result)
{
  GpReal scalx= map->x.scale, offx= map->x.offset;
  GpReal scaly= map->y.scale, offy= map->y.offset;
  long i, ns= maxSegs<n? maxSegs : n;
  GpSegment *seg;

  if (ns>scratchSegs) MoreScratch(0, ns);
  *result= seg= scratch;

  for (i=0 ; i<ns ; i++) {
    seg[i].x1= (short)(scalx*x1[i]+offx);
    seg[i].y1= (short)(scaly*y1[i]+offy);
    seg[i].x2= (short)(scalx*x2[i]+offx);
    seg[i].y2= (short)(scaly*y2[i]+offy);
  }

  return ns;
}

/* ------------------------------------------------------------------------ */

void GpPutGray(int nColors, GpColorCell *palette)
{
  /*
  while (nColors--) {
    palette->gray=
      ((int)palette->red+(int)palette->green+(int)palette->blue)/3;
    palette++;
  }
  */
}

void GpPutNTSC(int nColors, GpColorCell *palette)
{
  /*
  while (nColors--) {
    palette->gray=
      (30*(int)palette->red+59*(int)palette->green+11*(int)palette->blue)/100;
    palette++;
  }
  */
}

void GpPutRGB(int nColors, GpColorCell *palette)
{
  /*
  while (nColors--) {
    palette->red= palette->green= palette->blue= palette->gray;
    palette++;
  }
  */
}

int GpSetPalette(Engine *engine, GpColorCell *palette, int nColors)
{
  if (!engine) return 0;
  if (nColors<0) {
    palette= 0;
    nColors= 0;
  }
  engine->palette= palette;
  engine->nColors= nColors;
  engine->colorChange= 1;
  return engine->ChangePalette(engine);
}

int GpGetPalette(Engine *engine, GpColorCell **palette)
{
  *palette= engine? engine->palette : 0;
  return engine? engine->nColors : 0;
}

int GpDumpColors(Engine *engine, int colorMode)
{
  if (!engine) {
    for (engine=GpNextActive(0) ; engine ; engine=GpNextActive(engine))
      { engine->colorMode= colorMode;   engine->colorChange= 1; }
  } else {
    engine->colorMode= colorMode;   engine->colorChange= 1;
  }
  return 0;
}

/* ------------------------------------------------------------------------ */

long GpClipCells(GpMap *map, GpReal *px, GpReal *qx,
                 GpReal xmin, GpReal xmax, long ncells, long *off)
{
  long imin, imax;
  GpReal p, q, dx;
  GpReal scale= map->scale;
  GpReal offset= map->offset;

  xmin= xmin*scale+offset;
  xmax= xmax*scale+offset;
  if (xmin>xmax) {GpReal tmp=xmin; xmin=xmax; xmax=tmp;}
  p= (*px)*scale+offset;
  q= (*qx)*scale+offset;

  if (p<q && q>=xmin && p<=xmax) {
    dx= (q-p)/(GpReal)ncells;
    if (p<xmin) {
      imin= (long)((xmin-p)/dx);
      p+= dx*(GpReal)imin;
    } else {
      imin= 0;
    }
    if (q>xmax) {
      imax= (long)((q-xmax)/dx);
      q-= dx*(GpReal)imax;
      imax= ncells-imax;
    } else {
      imax= ncells;
    }
    if (imax-imin<2) {
      if (imax==imin) {
        if (p<xmin) p= xmin;
        if (q>xmax) q= xmax;
      } else {
        if (p<xmin && q>xmax) {
          if (q-xmax > xmin-p) { q-= xmin-p;  p= xmin; }
          else { p+= q-xmax;  q= xmax; }
        }
      }
    }
  } else if (p>q && p>=xmin && q<=xmax) {
    dx= (p-q)/(GpReal)ncells;
    if (q<xmin) {
      imax= (long)((xmin-q)/dx);
      q+= dx*(GpReal)imax;
      imax= ncells-imax;
    } else {
      imax= ncells;
    }
    if (p>xmax) {
      imin= (long)((p-xmax)/dx);
      p-= dx*(GpReal)imin;
    } else {
      imin= 0;
    }
    if (imax-imin<2) {
      if (imax==imin) {
        if (q<xmin) q= xmin;
        if (p>xmax) p= xmax;
      } else {
        if (q<xmin && p>xmax) {
          if (p-xmax > xmin-q) { p-= xmin-q;  q= xmin; }
          else { q+= p-xmax;  p= xmax; }
        }
      }
    }
  } else {
    imin= 0;
    imax= -1;
  }

  *px= p;
  *qx= q;
  *off= imin;

  return imax-imin;
}

/* ------------------------------------------------------------------------ */

int GpIntersect(const GpBox *box1, const GpBox *box2)
{
  /* Algorithm assumes min<max for x and y in both boxes */
  return box1->xmin<=box2->xmax && box1->xmax>=box2->xmin &&
         box1->ymin<=box2->ymax && box1->ymax>=box2->ymin;
}

int GpContains(const GpBox *box1, const GpBox *box2)
{
  /* Algorithm assumes min<max for x and y in both boxes */
  return box1->xmin<=box2->xmin && box1->xmax>=box2->xmax &&
         box1->ymin<=box2->ymin && box1->ymax>=box2->ymax;
}

void GpSwallow(GpBox *preditor, const GpBox *prey)
{
  /* Algorithm assumes min<max for x and y in both boxes */
  if (preditor->xmin>prey->xmin) preditor->xmin= prey->xmin;
  if (preditor->xmax<prey->xmax) preditor->xmax= prey->xmax;
  if (preditor->ymin>prey->ymin) preditor->ymin= prey->ymin;
  if (preditor->ymax<prey->ymax) preditor->ymax= prey->ymax;
}

/* ------------------------------------------------------------------------ */

/* These recondite routines are required to handle editing a drawing
   on one or more interactive engines.  The restriction to few
   routines builds in certain inefficiencies; if every drawing were always
   associated with one interactive engine some of the inefficiency could
   be reduced.  These are not intended for external use.  */

extern int gdNowRendering, gdMaxRendered;
int gdNowRendering= -1;
int gdMaxRendered= -1;

int GdBeginDr(Drauing *drawing, GpBox *damage, int landscape)
{
  int needToRedraw= 0;
  Engine *eng;

  if (damage) {
    /* If drawing has incurred damage, report damage to ALL engines
       interested in the drawing (not just active engines).  */
    for (eng=GpNextEngine(0) ; eng ; eng=GpNextEngine(eng))
      if (eng->drawing==drawing) GpDamage(eng, drawing, damage);
  }

  /* Loop on active engines to alert them that drawing is coming.  */
  for (eng=GpNextActive(0) ; eng ; eng=GpNextActive(eng)) {
    if (eng->drawing!=drawing) {
      /* This engine is not marked as interested in this drawing.
         Mark it, and reset damaged and lastDrawn flags so that no
         elements will be inhibited.  */
      eng->drawing= drawing;
      eng->lastDrawn= -1;
      eng->damaged= 0;
      if (landscape != eng->landscape) {
        eng->landscape= landscape;
        /* This change will be detected and acted upon by the first call
           to the ChangeMap method (GpSetTrans).  */
      }
      /* The semantics here are subtle --
         After a ClearDrawing, GdDetach zeroes eng->drawing in order to
         communicate that the drawing has been cleared.  Thus, the code
         gets here on a GdDraw after the drawing has been cleared, so
         the time has come to carry out the deferred clearing of this
         engine's plotting surface.  */
      GpClear(eng, CONDITIONALLY);
      needToRedraw= 1;

    } else if (eng->damaged) {
      /* This engine was interested in the drawing, which has been
         damaged.  Clear damaged area in preparation for repair work.
         (This is redundant if the damage was due to an X windows
          expose event, but the resulting inefficiency is very small.)  */
      eng->ClearArea(eng, &eng->damage);
      needToRedraw= 1;

    } else if (eng->lastDrawn<drawing->nElements-1) {
      needToRedraw= 1;
    }
  }

  gdNowRendering= gdMaxRendered= -1;
  return needToRedraw;
}

int GdBeginSy(GpBox *tickOut, GpBox *tickIn, GpBox *viewport,
              int number, int sysIndex)
{
  Engine *eng;
  int value= 0;
  long sysMask;

  /* Note that this is harmless if sysIndex>2*sizeof(long)--
     just slightly inefficient in that ticks and elements will ALWAYS
     be drawn...  This shouldn't be a practical problem.  */
  if (sysIndex>sizeof(long)) {
    sysMask= 1 << (sysIndex-sizeof(long));
    sysIndex= 1;
  } else {
    sysMask= 1 << sysIndex;
    sysIndex= 0;
  }

  /* Loop on active engines to determine whether any require ticks or
     elements to be drawn.  Set inhibit switches for ticks.  */
  for (eng=GpNextActive(0) ; eng ; eng=GpNextActive(eng)) {
    if ( ! (eng->systemsSeen[sysIndex] & sysMask) ) {
      /* this engine has never seen this system */
      value|= 3;
      eng->inhibit= 0;
      eng->systemsSeen[sysIndex]|= sysMask;

    } else if (eng->damaged && GpIntersect(tickOut, &eng->damage)) {
      /* engine damage touches this coordinate system--
         redraw ticks if region between tickIn and tickOut damaged,
         redraw elements if viewport damaged */
      if (!tickIn || !GpContains(tickIn, &eng->damage)) {
        value|= 2;
        eng->inhibit= 0;
      } else eng->inhibit= 1;
      if (number>eng->lastDrawn || GpIntersect(viewport, &eng->damage))
        value|= 1;

    } else {
      /* engine undamaged or damage doesn't touch this system--
         redraw elements if any new ones, don't redraw ticks */
      eng->inhibit= 1;
      if (number>eng->lastDrawn) value|= 1;
    }
  }

  return value;
}

int GdBeginEl(GpBox *box, int number)
{
  Engine *eng;
  int value= 0;

  /* Loop on active engines to determine whether any require this
     element to be drawn, and to set inhibit switches so that some
     may draw it and others not.  */
  for (eng=GpNextActive(0) ; eng ; eng=GpNextActive(eng)) {
    if (number>eng->lastDrawn) {
      /* this engine hasn't seen this element before */
      eng->inhibit= 0;
      value= 1;
      if (eng->damaged && gdMaxRendered<=eng->lastDrawn) {
        /* If this is the first new element, the damage flag
           must be reset, and ChangeMap must be called to set the
           clip rectangle back to its undamaged boundary.  */
        eng->damaged= 0;
        eng->ChangeMap(eng);
      }

    } else if (box && eng->damaged && GpIntersect(box, &eng->damage)) {
      /* engine damage touches this element */
      eng->inhibit= 0;
      value= 1;

    } else {
      /* this element has been seen before and hasn't been damaged */
      eng->inhibit= 1;
    }

    /* set number of element currently being drawn for GdEndDr */
    gdNowRendering= number;
    if (gdMaxRendered<gdNowRendering) gdMaxRendered= gdNowRendering;
  }

  return value;
}

void GdEndDr(void)
{
  Engine *eng;
  /* Done with this drawing- reset inhibit, damaged, and lastDrawn flags */
  for (eng=GpNextActive(0) ; eng ; eng=GpNextActive(eng)) {
    if (eng->lastDrawn<gdMaxRendered) eng->lastDrawn= gdMaxRendered;
    eng->inhibit= eng->damaged= 0;
  }
}

void GpDamage(Engine *eng, Drauing *drawing, GpBox *box)
{
  if (eng->drawing!=drawing || !eng->marked) return;
  if (eng->ClearArea==&DefaultClearArea) {
    /* This engine doesn't need to record the damage box */
    eng->damaged= 1;
  } else if (eng->damaged) {
    /* drawing is already damaged on this engine */
    if (eng->damage.xmin>box->xmin) eng->damage.xmin= box->xmin;
    if (eng->damage.xmax<box->xmax) eng->damage.xmax= box->xmax;
    if (eng->damage.ymin>box->ymin) eng->damage.ymin= box->ymin;
    if (eng->damage.ymax<box->ymax) eng->damage.ymax= box->ymax;
  } else {
    /* drawing is currently undamaged on this engine */
    eng->damaged= 1;
    eng->damage= *box;
  }
}

void GdDetach(Drauing *drawing, Engine *engine)
{
  Engine *eng;
  for (eng=GpNextEngine(0) ; eng ; eng=GpNextEngine(eng)) {
    if (!drawing || eng->drawing==drawing) {
      eng->drawing= 0;
      eng->inhibit= eng->damaged= 0;
      eng->lastDrawn= -1;
    }
  }
}

/* ------------------------------------------------------------------------ */
