// TO DO: add:
// turn(A, angle) (remove autorotate?)
// 'point (A, * in direction of */ B)'
// cursor following,
// compare_distance(A, /*to*/ B, C, /*to*/ D)
// touching(A, B)
// auto_bounce(A, XL, YB, XR, YT)
// frame rate for automatic animation by cycling skins.
// animation: cycle, or forward then back?
// support both types of list (simple x,y as well as packet lists)
// need a 'print number (long int n, int x, int y)'

#ifdef LINUX
static void Wait_Recal(void) {
}
static void Reset0Ref(void) {
}
static void Moveto_d(int y, int x) {
}
static void Draw_VLp(int *lines) {
}
static void Rot_VL_Mode(int angle, void *original, void *rotated) {
}
static void Intensity_a(unsigned int bright) {
}
static inline void InternalSetScale(unsigned int AbsScale) {
}
#else
#include <vectrex.h>
static inline void InternalSetScale(unsigned int AbsScale) {
  VIA_t1_cnt_lo = AbsScale;
}
#endif

int Stop_on_Errors = TRUE;
#define ABEND(s) do { assert(s == NULL); } while (0)
#define IN_ROM // __attribute__ ((section(".text")))

/*
   I'm trying out a different programming paradigm that new programmers may find
   easier to use.  This is based on the model that was used in the 60's and 70's
   for vector graphic terminals such as you might find on a PDP computer.

   In this model of Vectrex graphics, there are no direct drawing commands - instead,
   you create objects off-screen and when the object is built you make it appear using
   a 'Post' command.  You move the object by altering it's x,y coordinates.

   On the Vectrex the data structure is designed so that maximum amounts of data are held in ROM,
   with only variable parameters such as x,y or intensity being in RAM.  By use of pointers
   to common data, you could even have multiple instantiations of an object in ROM, with
   each one having a different (y,x) position, and you could animate the image by switching through
   these multiple copies (Unpost-ing the object at one y,x and Posting a clone of the object
   which has a different Y,X embedded) so that the only RAM requirement is the 2 byte pointer
   for the Post-ed object.  Similarly changing pointers to the vector data would allow you
   to animate a sprite very simply.

   There are two kinds of objects, movable ones and fixed ones.
   They are essentially the same except that the initial x,y
   co-ordinates are in RAM for movable objects and ROM for fixed ones.
   A movable object will need at least 4 bytes in RAM - x, y, and the pointer to
   the object.

   Objects will eventually be able to instantiate other objects, at relative positions to
   the start of the object, and at specified scales (need to decide if fixed absolute, or
   relative to parent object)

   Objects can be Post()-ed or Unpost()-ed.  If movable, the Post()-ing call takes (y,x).

   Objects (icons) are primarily DrawVLp lists.  To draw arbitrary simple lines, (which you
   should do as seldom as possible) they have to be put into one of these lists in RAM.

   There can be other kinds of objects including (raster) String objects.

   Objects have intensities and can flash.

   The word Object is synonymous with the word Sprite.  Almost.

   This is a first draft, it will be improved later as we test on more examples and fine-tune
   the procedure call interface to make writing code in this style easy.

   Graham Toal, 12 May 2021
*/

#ifndef NULL
#define NULL 0
#endif

// Only FixedVL and MovableVL implemented so far... these other primitives are suggestions for
// the sort of features which might be added.

typedef enum PRIMITIVE { PointListVL, FixedVL, MovableVL, RotatableVL, SynchedList, CallBack, RasterString,
                         VectorString, RecursiveCall, END_LIST } PRIMITIVE;

// The next group of typedefs point to data that is likely constant at compile time, and is expected
// to be shared among multiple instances of a sprite type.

// these are const arrays of x,y pairs - NOT packet format that uses 3 bytes.  There are
// no moves within such a list, just contiguous lines. This is the most basic type of sprite
// and the least useful.

// Diffy:
// byte 0 - Vector count – 1 [optional]
// byte 1 - Scale factor [optional]
// bytes 2 / 3 - ‘Y:X’ for coordinate 1
// bytes n / n+1 - ‘Y:X’ for coordinate ‘n’

// Duffy:
// A ‘Duffy’ style list is identical to a ‘Diffy’ style list.
// The only difference appears to be in the way
// that it is processed. When processing one of these, the
// drawing functions will move to the first
// point in the list. It will then draw a line to the
// next relative coordinate, until no more points
// remain

typedef struct PointListVLp {
  int *data;
} PointListVLp;


// ‘Packet’ Description
// A ‘Packet’ style list is an uncounted list of (mode:Y:X) triplets.
// This type of packet is useful if you need to mix move and draw requests
// within the same list. The end of the list is indicated by the presence of
// a list terminator ($01).
// Depending upon the function processing the list, the first byte may be expected to contain the
// scale factor to be used when processing tlist, or this value may need to be stored into RAM.
// A sample ‘Packet’ list might look like the following:
//   byte 0 - Scale factor
//   bytes 1 / 2 / 3 - ‘mode:Y:X’ for coordinate 1
//   bytes n / n+1 / n+2 - ‘mode:Y:X’ for coordinate ‘n’
//   $01 - packet terminator

// where ‘mode’ can assume one of the following values:
// $00 - Move to the specified point
// $FF (-1) - Draw a line to the specified point

// These are simple objects. You can change their brightness but that's all. If you want something
// movable, use a MovableVLp.
typedef struct FixedVLp {
  int *packet_data;
} FixedVLp;

// this is a basic drawing object in 'packet' format.  The programmer really
// doesn't need to know that.  Rotatable objects also use this format, but take
// up a little more ram because they have to also store the rotation angle
typedef struct MovableVLp {
  int points; // count of points in the VLp list - required.
  const int *packet_data;
} MovableVLp;

// these are currently the same as MovableVLp objects, but they have support for resynchronising drift
// They currently cannot be rotated.
typedef struct SynchedListp {
  signed char *data;
} SynchedListp;

typedef struct CallBackp {
  // add non-optional parameters here?
  // optional parameters go in the writable parent block, Callback_Object.
  int (*callback) (void);
} CallBackp;

// A *_Object is a specific instance of a sprite.  The general form of the
// sprite, which can be shared among multiple instances, is the one currently
// ending in 'p' (pointer to)


typedef struct CallBack_Object {
  PRIMITIVE type; // == CallBack
  void *parameterBlock;
  CallBackp *data;
} CallBack_Object;

// These are really simple fixed objects.  They cannot be moved at all.
typedef struct Fixed_VLp_Object {
  PRIMITIVE type; // == FixedVL
  unsigned int Intensity;
  FixedVLp *data;
} Fixed_VLp_Object;

typedef struct Movable_VLp_Object {
  PRIMITIVE type; // == MovableVL
  int y,x;
  unsigned int Intensity;
  unsigned int move_scale, draw_scale;
  MovableVLp *data;
} Movable_VLp_Object;

typedef struct Synched_Listp_Object {
  PRIMITIVE type; // == SynchedList
  int y,x;
  unsigned int Intensity;
  unsigned int move_scale, draw_scale;
  SynchedListp *data;
} Synched_Listp_Object;


typedef struct Rotatable_VLp_Object {
  PRIMITIVE type; // == RotatableVL
  int y, x;
  unsigned int angle;
  int deltaangle;
  unsigned int Intensity;
  unsigned int move_scale, draw_scale;
  MovableVLp *data;
} Rotatable_VLp_Object;

typedef struct RasterString_Object {
  PRIMITIVE type; // == RasterString
  unsigned int Intensity;
  void *font;
  int y, x;
  int scale;
  char *data;
} RasterString_Object;

typedef struct VectorString_Object {
  PRIMITIVE type; // == VectorString
  unsigned int Intensity;
  int font;
  int y, x;
  int scale;
  char *data;
} VectorString_Object;

typedef struct Object {
  // A pointer to one of these is what is passed to procedures,
  // but it is really a generic pointer to one of many different
  // object types, which can be determined by looking at the common
  // 'type' variable which is present in all variants.
  PRIMITIVE type;
} Object;


#define MAX_OBJECTS 32
int OBJSTART = 0;
typedef struct Universe {
  Object *each[MAX_OBJECTS];
} Universe;

Universe U;

int Post(Object *sprite, int y, int x, unsigned int scale) {
  int i;
  for (i = 0; i < MAX_OBJECTS; i++, OBJSTART++) {
    if (OBJSTART == MAX_OBJECTS) OBJSTART = 0;
    if (U.each[OBJSTART] == NULL) break;
  }
  // fail if i == MAX_OBJECTS
  U.each[OBJSTART] = sprite;
  switch (sprite->type) {
  case FixedVL: {
      // Fixed_VLp_Object *O = (Fixed_VLp_Object *)sprite;
      // O->y = y; O->x = x;
      break;
    }
  case MovableVL: {
      Movable_VLp_Object *O = (Movable_VLp_Object *)sprite;
      O->y = y; O->x = x; O->move_scale = 127; O->draw_scale = scale;
      break;
    }
  case SynchedList: {
      Synched_Listp_Object *O = (Synched_Listp_Object *)sprite;
      O->y = y; O->x = x; O->move_scale = 127; O->draw_scale = scale;
      break;
    }
  case RotatableVL: {
      Rotatable_VLp_Object *O = (Rotatable_VLp_Object *)sprite;
      O->y = y; O->x = x;  O->move_scale = 127; O->draw_scale = scale;
      break;
    }
  case CallBack: {
      //CallBack_Object *O = (CallBack_Object *)sprite;
      break;
    }
  default:
    break;
  }

  return OBJSTART++;
}

void Unpost(int icon_num) {
  if ((icon_num < 0) || (icon_num > MAX_OBJECTS)) return;
  U.each[icon_num] = NULL;
}

void Move(int icon_num, int y, int x) {
  if ((icon_num < 0) || (icon_num > MAX_OBJECTS)) return;
  if (U.each[icon_num] == NULL) return;
  switch (U.each[icon_num]->type) {
    //case FixedVL: {
    //    Fixed_VLp_Object *O = (Fixed_VLp_Object *)U.each[icon_num];
    //    O->x = x; O->y = y;
    //    break;
    //  }
    case MovableVL: {
        Movable_VLp_Object *O = (Movable_VLp_Object *)U.each[icon_num];
        O->x = x; O->y = y;
        break;
      }
    case RotatableVL: {
        Rotatable_VLp_Object *O = (Rotatable_VLp_Object *)U.each[icon_num];
        O->x = x; O->y = y;
        break;
      }
    case SynchedList: {
        Synched_Listp_Object *O = (Synched_Listp_Object *)U.each[icon_num];
        O->x = x; O->y = y;
        break;
      }
    default:
      if (!Stop_on_Errors) return;
      ABEND("Cannot move this object");
  }
}

void Direction(int icon_num, unsigned int angle) {
  // rotation angles count clockwise starting at vertical=0, in range 0:255
  if ((icon_num < 0) || (icon_num > MAX_OBJECTS)) return;
  if (U.each[icon_num] == NULL) return;
  if (U.each[icon_num]->type != RotatableVL) {
    if (!Stop_on_Errors) return;
    ABEND("RotatableVL only!");
  }
  Rotatable_VLp_Object *P = (Rotatable_VLp_Object *)U.each[icon_num];
  P->angle = angle;
}

void Brightness(int icon_num, unsigned int Intensity) {
  if ((icon_num < 0) || (icon_num > MAX_OBJECTS)) return;
  if (U.each[icon_num] == NULL) return;
  switch (U.each[icon_num]->type) {
  case FixedVL: {
      Fixed_VLp_Object *P = (Fixed_VLp_Object *)U.each[icon_num];
      P->Intensity = Intensity;
      break;
    }
  case MovableVL: {
      Movable_VLp_Object *P = (Movable_VLp_Object *)U.each[icon_num];
      P->Intensity = Intensity;
      break;
    }
  case RotatableVL: {
      Rotatable_VLp_Object *P = (Rotatable_VLp_Object *)U.each[icon_num];
      P->Intensity = Intensity;
      break;
    }
  case SynchedList: {
      Synched_Listp_Object *P = (Synched_Listp_Object *)U.each[icon_num];
      P->Intensity = Intensity;
      break;
    }
  default:
    if (!Stop_on_Errors) return;
    ABEND("Cannot set brightness of this object");
    break;
  }
}

void Size(int icon_num, unsigned int Scale) {
  if ((icon_num < 0) || (icon_num > MAX_OBJECTS)) return;
  if (U.each[icon_num] == NULL) return;
  switch (U.each[icon_num]->type) {
  //case FixedVL: {
  //    Fixed_VLp_Object *P = (Fixed_VLp_Object *)U.each[icon_num];
  //    P->draw_scale = Scale;
  //  }
  case MovableVL: {
      Movable_VLp_Object *P = (Movable_VLp_Object *)U.each[icon_num];
      P->draw_scale = Scale;
      break;
    }
  case RotatableVL: {
      Rotatable_VLp_Object *P = (Rotatable_VLp_Object *)U.each[icon_num];
      P->draw_scale = Scale;
      break;
    }
  case SynchedList: {
      Synched_Listp_Object *P = (Synched_Listp_Object *)U.each[icon_num];
      P->draw_scale = Scale;
      break;
    }
  default:
    if (!Stop_on_Errors) return;
    ABEND("Cannot set size of this object");
    break;
  }
}

void InitUniverse(void) {
  int i;
  for (i = 0; i < MAX_OBJECTS; i++) {
    U.each[i] = NULL;
  }
}

static inline void Rotate_VLp(unsigned int angle, const void *original, void *rotated)
{
  // rotation angles count clockwise starting at vertical=0, in range 0:255
  Rot_VL_Mode(255U-angle, (void *)original, rotated);
}


// this is quite a "C" optimized version of print_sync
// (-O3 and no_frame_pointer)
#define ZERO_DELAY 5

void draw_synced_list_c(
	const signed char *u,
	signed int y,
	signed int x,
	unsigned int scaleMove,
	unsigned int scaleList
	)
{
	
	do
	{
		// resync / startsync
		dp_VIA_shift_reg = 0;		// all output is BLANK
		
		// move to zero
		dp_VIA_cntl = (unsigned int)0xcc;	// zero the integrators
		dp_VIA_port_a = 0;			// reset integrator offset
		dp_VIA_port_b = (int)0b10000010;
		
		dp_VIA_t1_cnt_lo = scaleMove;
		// delay, till beam is at zero
		// volatile - otherwise delay loop does not work with -O
		for (volatile signed int b=ZERO_DELAY; b>0; b--);
		dp_VIA_port_b= (int)0b10000011;
		
		// move to "location"
		dp_VIA_port_a = y;			// y pos to dac
		dp_VIA_cntl = (unsigned int)0xce;	// disable zero, disable all blank
		dp_VIA_port_b = 0;			// mux enable, dac to -> integrator y (and x)
		dp_VIA_port_b = 1;			// mux disable, dac only to x
		dp_VIA_port_a = x;			// dac -> x
		dp_VIA_t1_cnt_hi=0;		// start timer
		
		// this can be done before the wait loop
		// since it only fills the latch, not the actual timer!
		dp_VIA_t1_cnt_lo = scaleList;
		u+=3;
		
		// moveing test for yx== 0 into the move delay
		if ((*(u-2)!=0) || (*(u-1)!=0))
		{
			while ((dp_VIA_int_flags & 0x40) == 0); // wait till timer finishes
			
			// internal moveTo
			dp_VIA_port_a = *(u-2);	// y pos to dac
			dp_VIA_cntl = (unsigned int)0xce;	// disable zero, disable all blank
			dp_VIA_port_b = 0;			// mux enable, dac to -> integrator y (and x)
			dp_VIA_port_b = 1;			// mux disable, dac only to x
			dp_VIA_port_a = *(u-1);	// dac -> x
			dp_VIA_t1_cnt_hi=0;		// start timer
			while ((dp_VIA_int_flags & 0x40) == 0); // wait till timer finishes
		}
		else
		{
			while ((dp_VIA_int_flags & 0x40) == 0); // wait till timer finishes
		}
		
		while (1)
		{
			if (*u<0) // draw line
			{
				// draw a vector
				dp_VIA_port_a = *(1+u);		// y pos to dac
				dp_VIA_port_b = 0;				// mux enable, dac to -> integrator y (and x)
				dp_VIA_port_b=1;				// mux disable, dac only to x
				dp_VIA_port_a = *(2+u);		// dac -> x
				dp_VIA_t1_cnt_hi=0;			// start timer
				dp_VIA_shift_reg = (unsigned int)0xff;	// draw complete line

				if (scaleList>10)
				{
					while ((dp_VIA_int_flags & 0x40) == 0); // wait till timer finishes
				}
				dp_VIA_shift_reg = 0;			// all output is BLANK
			}
			else if (*u == 0) // moveTo
			{
				if ((*(u+1)!=0) || (*(u+2)!=0))
				{
					// internal moveTo
					dp_VIA_port_a = *(1+u);	// y pos to dac
					dp_VIA_cntl = (unsigned int)0xce;	// disable zero, disable all blank
					dp_VIA_port_b = 0;			// mux enable, dac to -> integrator y (and x)
					dp_VIA_port_b =1;			// mux disable, dac only to x
					dp_VIA_port_a = *(2+u);	// dac -> x
					dp_VIA_t1_cnt_hi=0;		// start timer
					while ((dp_VIA_int_flags & 0x40) == 0); // wait till timer finishes
				}
			}
			else
			{
				break;
			}
			u+=3;
		}
	} while (*u != 2);
}

void UpdateDisplay(void) {
  int i;
  Wait_Recal();
  check_buttons();
  //draw_objects(&world)
  for (i = 0; i < MAX_OBJECTS; i++) {
    Object *O = (Object *)U.each[i];
    if (O) {
      switch (O->type) {
      case FixedVL: {
        // TO DO: a fixed position example.
        Fixed_VLp_Object *P = (Fixed_VLp_Object *)O;
        FixedVLp *VLp = P->data;
        Reset0Ref();
        Intensity_a(P->Intensity);
        InternalSetScale(255);
        Moveto_d(0,0);
        Draw_VLp(VLp->packet_data);
        }
        break;
      case MovableVL: {
        Movable_VLp_Object *P = (Movable_VLp_Object *)O;
        Reset0Ref();
        InternalSetScale(P->move_scale);
        Moveto_d(P->y, P->x);
        InternalSetScale(P->draw_scale);
        Intensity_a(P->Intensity);
        Draw_VLp((int *)P->data->packet_data);
        }
        break;
      case SynchedList: {
        Synched_Listp_Object *P = (Synched_Listp_Object *)O;
        Reset0Ref();
        InternalSetScale(127);
        Moveto_d(P->y, P->x);
        Intensity_a(P->Intensity);
        draw_synced_list_c(
            P->data->data,
            P->y, P->x,
            P->move_scale,
            P->draw_scale
          );
        }
        break;
      case RotatableVL: {
          Rotatable_VLp_Object *P = (Rotatable_VLp_Object *)O;
          Reset0Ref();
          InternalSetScale(P->move_scale);
          Moveto_d(P->y, P->x);
          InternalSetScale(P->draw_scale);
          Intensity_a(P->Intensity);
          if (P->angle == 0) {
            Draw_VLp((int *)P->data->packet_data);
          } else {
            char TEMP[P->data->points*3+1];
            // apply rotation here...
            Rotate_VLp((unsigned int)(P->angle>>2),
                     (unsigned int *)P->data->packet_data, (unsigned int *)TEMP);
            Draw_VLp((int *)TEMP); /* a rotate&draw without extra storage would be nice... */
          }
          // rotation angles count clockwise starting at vertical=0, in range 0:255
          // If deltaange is non-0, object autorotates. full circle = 0:255
          P->angle += (unsigned int)(P->deltaangle*4);
        }
        break;
      case CallBack: {
          CallBack_Object *P = (CallBack_Object *)O;
          P->data->callback();
          break;
        }
      case RasterString:
        break;
      case VectorString:
        break;
      case RecursiveCall:
        break;
      case END_LIST:
        break;
      default:
        break;
      }
    }
  }
}

static inline int abs(int x) {
  if (x >= 0) return x;
  return -x;
}

static inline int sgn(int x) {
  if (x < 0) return -1;
  if (x > 0) return  1;
  return x;
}


static unsigned int dotmask = 0xC3;

static unsigned char patList[2];
static void drawline_patterned(int y, int x, unsigned char pat)
{
  patList[0]=(unsigned char)y;
  patList[1]=(unsigned char)x;
  *(volatile unsigned char *)0xC829 = pat;
  *(volatile unsigned char *)0xC823 =0;
  Draw_Pat_VL(patList);
}

#define DRAWING_SCALE 0x80
//#define CROSSHAIR_SCALE 0x40

#define BRIGHT TRUE
#define normal FALSE
static int last_intensity = -128;
void drawline(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, int bright, int dashed) {
  long ax1, ay1, dx, dy;
  if (bright) {
    if (last_intensity != BRIGHT) Intensity_7F();
  } else {
    if (last_intensity != normal) Intensity_3F();
  }
  last_intensity = bright;
  Reset0Ref(); InternalSetScale(DRAWING_SCALE);
  ax1 = ((long)x1-128L);     ay1 = ((long)y1-128L);
  dx  = ((long)x2-(long)x1); dy  = ((long)y2-(long)y1);

  Moveto_d((int)ay1,(int)ax1);

  // id dx is 255, it splits into 127 and 128 - which causes a problem because +128 is not possible
  if (dy == 255 || dx == 255) {
    // 255-unit long lines are split in three, all others are split in two.
    drawline_patterned((int)(dy/3L), (int)(dx/3L), (dashed ? dotmask : 0xFF));
    dy -= dy/3L; dx -= dx/3L;
  }
  if (dy < -128L || dy > 127L || dx < -128L || dx > 127L) {
    drawline_patterned((int)(dy>>1L), (int)(dx>>1L), (dashed ? dotmask : 0xFF));
    // don't care which way it rounds (>>1 or /2) because this picks up the odd bit:
    dy -= dy>>1L; dx -= dx>>1L;
  }
  drawline_patterned((int)dy, (int)dx, (dashed ? dotmask : 0xFF));
}