/*
   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
*/


// 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)

#ifdef LINUX
// This is currently only here to allow a quick syntax check on my linux web host.
// Otherwise just ignore.
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 Draw_Pat_VL(unsigned char *list) {
}
static void Rot_VL_Mode(int angle, void *original, void *rotated) {
}
static void Intensity_a(unsigned int bright) {
}
static void Intensity_7F(void) {
}
static void Intensity_3F(void) {
}
static inline void InternalSetScale(unsigned int AbsScale) {
}
#define IN_ROM
#else
#include <vectrex.h>
static inline void InternalSetScale(unsigned int AbsScale) {
  VIA_t1_cnt_lo = AbsScale;
}
// This doesn't seem to be working at the moment... trying to minimise RAM usage...
#define IN_ROM // __attribute__ ((section(".text")))
#endif

#ifndef NULL
#define NULL 0
#endif

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

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

typedef struct FixedVLp {
  int y,x;
  int *data;
} FixedVLp;

typedef struct SynchedListp {
  signed char *data;
} SynchedListp;


typedef struct VLp {
  unsigned int bright;
  int points;
  const int *data;
} VLp;

typedef struct CallBackp {
  // add optional parameters here?
  int (*callback) (void);
} CallBackp;

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

typedef struct Fixed_VLp_Object {
  PRIMITIVE type; // == FixedVL
  FixedVLp *data;
} Fixed_VLp_Object;

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

typedef struct Movable_VLp_Object {
  PRIMITIVE type; // == MovableVL
  int y, x;
  unsigned int AbsScale;
  VLp *data;
} Movable_VLp_Object;

typedef struct Rotatable_VLp_Object {
  PRIMITIVE type; // == RotatableVL
  int y, x;
  unsigned int angle;
  int deltaangle;
  unsigned int AbsScale;
  VLp *data;
} Rotatable_VLp_Object;

typedef struct Object {
  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 MovableVL: {
      Movable_VLp_Object *O = (Movable_VLp_Object *)sprite;
      O->y = y; O->x = x; O->AbsScale = scale;
      break;
    }
  case SynchedList: {
      Synched_Listp_Object *O = (Synched_Listp_Object *)sprite;
      O->y = y; O->x = x;
      break;
    }
  case RotatableVL: {
      Rotatable_VLp_Object *O = (Rotatable_VLp_Object *)sprite;
      O->y = y; O->x = x; O->AbsScale = 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;
  Movable_VLp_Object *O = (Movable_VLp_Object *)U.each[icon_num];
  O->x = x; O->y = y;
}

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) return;
  Rotatable_VLp_Object *P = (Rotatable_VLp_Object *)U.each[icon_num];
  P->angle = angle;
}

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
	)
{
#ifndef LINUX
	
	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);
#endif
}

void UpdateDisplay(void) {
  int i;
  Wait_Recal();
  //draw_objects(&world)
  for (i = 0; i < MAX_OBJECTS; i++) {
    Object *O = (Object *)U.each[i];
    if (O) {
      switch (O->type) {
      case FixedVL: {
        Fixed_VLp_Object *P = (Fixed_VLp_Object *)O;
        FixedVLp *VLp = P->data;
        Moveto_d(VLp->y, VLp->x);
        Draw_VLp(VLp->data);
        }
        break;
      case MovableVL: {
        Movable_VLp_Object *P = (Movable_VLp_Object *)O;
        Reset0Ref();
        InternalSetScale(127);
        Moveto_d(P->y, P->x);
        InternalSetScale(P->AbsScale);
        Intensity_a(P->data->bright);
        Draw_VLp((int *)P->data->data);
        }
        break;
      case SynchedList: {
        Synched_Listp_Object *P = (Synched_Listp_Object *)O;
        Reset0Ref();
        InternalSetScale(127);
        Moveto_d(P->y, P->x);
        Intensity_a(0x7f);
        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(127);
          Moveto_d(P->y, P->x);
          InternalSetScale(P->AbsScale);
          Intensity_a(P->data->bright);
          if (P->angle == 0) {
            Draw_VLp((int *)P->data->data);
          } else {
            char TEMP[P->data->points*3+1];
            // apply rotation here...
            Rotate_VLp((unsigned int)(P->angle>>2),
                     (unsigned int *)P->data->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 END_LIST:
        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;
static 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));
}