#include <vectrex.h>
#define IN_ROM
#include "graphics.h"
#include "number.h"

/*
    The idea is to have 6 interrupts per frame instead of one.
            1) Right eye blue.
            2) Right eye green.
            3) Right eye red.
            4) Left eye blue.
            5) Left eye green.
            6) Left eye red.

The timings between each interrupt come from an array, so they can be changed
for different colour wheels.

The user code runs in the foreground, the drawing code runs on the interrupts.

The user code generates one frame's worth of data for display for every six interrupts,
i.e. the CWAI in the main loop is for the end of period 6/start of period 1.

(By the way, whenever you see / *dp_* /VIA in this code, it's probably OK to 
 replace it with the otiginal dp_VIA ... I was worried that maybe dp did not always
 have the correct value of 0xD0 but having checked, it does seem to be OK.)

The pulse code modulation to get the wheel spinning at the right rate is still
to be added - most likely in the foreground code...

For the first iteration of this framework, there will be no calls to draw individual
arbitrary lines.  Instead we will work from 'sync lists' to draw entire sprites.

The code is currently written with the intention of double-buffering the display list;
but it might be simpler/more space-efficient to use 6 FIFO queues implemented by cyclic 
buffers - but that is an idea for after we get the basics working - I have not yet
incorporated that in this sketch. It will need 'put' and 'get' indices for all 6 queues.

 */

#define int8_t  int
#define uint8_t unsigned int
#define int16_t long
#define uint16_t unsigned long
#define int32_t long long
#define uint32_t unsigned long long

#ifndef TRUE
#define TRUE (0==0)
#define FALSE (0!=0)
#endif

#define NULL 0

#define SCALE (127/14)
static /*const*/ int Meanie[] IN_ROM = {
  0,-10*SCALE,5*SCALE,
  // not very mean-looking yet ;-)
  -1,0*SCALE,-10*SCALE,
  -1,2*SCALE,3*SCALE,
  -1,10*SCALE,0*SCALE,
  -1,0*SCALE,-2*SCALE,
  -1,10*SCALE,0*SCALE,
  -1,-4*SCALE,2*SCALE,
  -1,4*SCALE,2*SCALE,
  -1,-4*SCALE,2*SCALE,
  -1,4*SCALE,2*SCALE,
  -1,-10*SCALE,0*SCALE,
  -1,0*SCALE,-2*SCALE,
  -1,-10*SCALE,0*SCALE,
  -1,-2*SCALE,3*SCALE,
   2 // Oops! Sync lists end in '2', not '1'!
};

static unsigned int dotmask = 0xC3;

#define DRAWING_SCALE 0x80
#define CROSSHAIR_SCALE 0x40

#define set_scale(s) do { VIA_t1_cnt_lo = s; } while (0)
#define BRIGHT TRUE
#define normal FALSE

/* I changed LEFT and RIGHT on reading this in Fred Taft's document:
 *
 * The order in which things are drawn are:
 *
 *              1) Right eye blue.
 *              2) Right eye green.
 *              3) Right eye red.
 *              4) Left eye blue.
 *              5) Left eye green.
 *              6) Left eye red.
 */
#define LEFT 3
#define RIGHT 0
#define RED 2
#define GREEN 1
#define BLUE 0

// these are volatile because they are changed by the interrupt routine
// but tested by the foreground code...
static volatile int eye_colour = RED; // first interrupt sets it to 0.
static volatile int hand = LEFT;
static volatile int complete_frame_done = FALSE;
#define LITTLE_ENDIAN(a) ( (((a)>>8L)&255L) | (((a)&255L)<<8L) )


#define FRAME 30000UL
#define MINIFRAME (FRAME/6UL)
const uint16_t segtimer[6] = {MINIFRAME, MINIFRAME, MINIFRAME, MINIFRAME, MINIFRAME, MINIFRAME };
uint16_t cycles[6] = {0UL, 0UL, 0UL, 0UL, 0UL, 0UL }; // record cycles used for debugging only.

// for double-buffering.  Might not keep this.
#define BUFFER_ZERO 0
#define BUFFER_ONE 6

static int8_t WRITING_TO = BUFFER_ZERO;
static int8_t DRAWING_FROM = BUFFER_ONE;
static /*some struct?*/void *DisplayList[12] = {
  NULL, NULL, NULL, NULL, NULL, NULL,
  NULL, NULL, NULL, NULL, NULL, NULL
}; // eg LEFT+RED+WRITING_TO
/*some struct?*/void *Graphics; // data for this time segment only...

#define IRQ_6809_MASK 0x10
#define ENABLE_IRQ_6809 asm("andcc #0xef")
#define DISABLE_IRQ_6809 asm("orcc #0x10")

// does not return, since we use the
// actual waitRecal "shortcut" to jump to "set_refresh" and return from there
__attribute__((noinline)) void iWaitRecal() // if inlined, the JMP -> RTS goes astray!!!
{
	Vec_Loop_Count++;
	// I assume EVERYTHING is done with DP = D0
	// BSR     DP_to_D0        ;DP to I/O - should always be globally true in this code.
	asm("jmp 0xF1A2"); // Set_Refresh
}

unsigned int8_t mouse_x, mouse_y;
int8_t mouse_down, mouse_was_down;
volatile unsigned int8_t *rand = (volatile unsigned int *)0xc87b;
volatile unsigned int8_t *timer_lo = (volatile unsigned int *)0xD004;

__attribute__((interrupt)) static void  gameLoop(void)
{
  // for anything except the t2 timer we want to rti immediately.
  if ((/*dp_*/VIA_int_flags & 0x20) == 0) return;
  static uint16_t t2;

  t2  = (unsigned long)(*(volatile unsigned int *)0xD008 /*low*/) & 255UL;
  t2 |= (unsigned long)(*(volatile unsigned int *)0xD009 /*high*/) << 8UL;
  cycles[eye_colour+hand] = 65535UL-t2; // trying to check that we got around 5000 cycles on previous segment...

  // update subframe info immediately on entry:
  eye_colour = eye_colour+1; // 0 -> 1 -> 2   RED,GREEN,BLUE
  if (eye_colour > RED) eye_colour = BLUE; // 2 -> 0
  hand = hand ^ (LEFT|RIGHT); // 0 -> 3 -> 0  LEFT+RIGHT


  // Initialisation is such that first interrupt enters drawing code
  // with right eye, red filter.

  // The code that goes here is what would normally go in the main loop of
  // a regular vectrex program, after the WaitRecal() call.
  iWaitRecal();
  // I'm not sure if iWaitRecal has reset the timers to 30,000 cycles so I'll force it here.
  Vec_Rfrsh = segtimer[eye_colour+hand];
  *(volatile unsigned int volatile *)0xD009 /*high*/ = (unsigned int)(Vec_Rfrsh >> 8UL) & 255;
  *(volatile unsigned int volatile *)0xD008 /*low*/  = (unsigned int)(Vec_Rfrsh & 255UL);

  Intensity_a(0x60);
  set_scale(0x40);

  // DRAW ALL SPRITES THAT CORRESPOND TO THIS TIME SEGMENT
  // (We're using the same sprite for all 6 time slices for now, just changing
  //  position so we can tell them apart.  They should be positioned left/right
  //  corresponding to left/right eye, and red/green/blue from top to bottom.
  //  *If* I got it right, that is...)

  draw_synced_list_c(
	(const signed char *)DisplayList[eye_colour+DRAWING_FROM],
	(signed int) /*y*/ (eye_colour-1)*-40,
	(signed int) /*x*/ hand*20 - 30,
	(unsigned int) /*scaleMove*/ 0xff,
	(unsigned int) /*scaleList*/ 0x20
  );
  //SHOW_UNSIGNED_NUM(cycles[eye_colour+hand],  hand*40-75, (eye_colour-1)*-80-43, 0x40);
  position_and_scale(hand*40-25, (eye_colour-1)*-80-15, 0x80); show_digit(eye_colour+hand);

  if ((eye_colour == BLUE) && (hand == RIGHT)) {
    //SHOW_UNSIGNED_NUM(Vec_Loop_Count,  -40, 120, 0x30);
  } else if ((eye_colour == GREEN) && (hand == RIGHT)) {
    //SHOW_UNSIGNED_NUM((cycles[0]+cycles[1]+cycles[2]+cycles[3]+cycles[4]+cycles[5]),  20, 120, 0x30);
  } else if ((eye_colour == RED) && (hand == LEFT)) { // last one is done.
    // if we had a reliable timer, reset Vec_Loop_Count = 0; after 1 second...
    // Anything that needs to be done once per real frame (six segments) after frame execution.
    *(volatile int8_t *)0xC81A = 0; // maximum analog resolution
    Joy_Analog();
	
    mouse_x = (uint8_t)(((long)Vec_Joy_1_X+128L)); // -128:127 maps to 0:255
    mouse_y = (uint8_t)(((long)Vec_Joy_1_Y+128L)); // but scaled down to preserve margins

    Read_Btns();

    mouse_down = ((Vec_Btn_State & 8) != 0);

    complete_frame_done = TRUE;
  }
}

#define IRQBASE 0xCBF8 // this must be IRQ not FIRQ!
uint16_t volatile * const IRQ = (uint16_t *) (IRQBASE+1);
uint8_t volatile * const IRQ_INST = (uint8_t *) IRQBASE;

#define WAIT_VALUE 15

void start_graphics_thread(void (*gameLoop)(void)) {

  //*(volatile unsigned int volatile *)0xD00C = 0xCD; // Force goggle index to trigger off positive edge.

// I probably need to replace all these RAM addresses with declared static variables...
// Still... progress ... this code does at least spin up the wheel, it just doesn't
// ever exit from the initialisation and ever draw anything... 

asm("       ldd   #0x00E0");
asm("       std   0xC83D");   //;   * Set refresh timer = 0.0382 sec
asm("       stb   0xCA8E");
asm("       ldd   #0x0008");
asm("       std   0xCA86");
asm("       stb   0xCA8A");   //;   * Init IRQ handler's loop counter
asm("       clr   0xCA8C");
asm("       clr   0xCA8D");
//asm("       ldd   #0x7E82");
//asm("       sta   0xCBF8");   //; * Set up IRQ interrupt vector: JMP
//asm("       stb   <0x0E");
//asm("       ldd   #IRQ_Handler");
//asm("       std   0xCBF9");   //; * Set IRQ interrupt function: Sync
asm("       lda   #0xCD");   //;    * Force goggle index to trigger off
asm("       sta   <0x0C");   //;    * positive edge.

asm("       clr   0xC891");   // * Set loop counter = 0

  // Wait for the goggle's disk to come up to speed:

asm("P0076: ldd   0xC83D");
asm("       std   <0x08"); //     * Set refresh timer                     $D008 (to #0x00E0)
asm("       ldb   0xC845"); //    * Get current I/O enable setting
asm("       andb  #0xBF");
asm("P0080: lda   #0x07");
asm("       jsr   0xF25B"); //    * Config Port A as an input
asm("       bsr   GetGoggleIndexState");
asm("       sta   0xCA7F");

asm("P008A: ldb   0xC845"); //    * Get current I/O enable setting
asm("       orb   #0x40");
asm("       lda   #0x07");
asm("       jsr   0xF25B"); //    * Config Port A as an output
asm("       ldd   #0x0E80"); //   * Write $80 to Port A
asm("       jsr   0xF256"); //    * byte_2_sound_chip
asm("       ldb   #0x60"); //     * Set timing loop value
asm("P009C: decb");
asm("       bne   P009C"); //     * Delay for awhile
asm("       ldd   #0x0EFF"); //   * Write $FF to Port A
asm("       jsr   0xF256"); //    * byte_2_sound_chip;
asm("       ldb   0xC845");
asm("       andb  #0xBF");
asm("       lda   #0x07");
asm("       jsr   0xF25B"); //    * Set Port A as an input
asm("       bsr   GetGoggleIndexState");
asm("       tst   0xCA7F"); //    * See if the goggles sync line
asm("       bne   P00B9"); //     * has gone from off to on.
  asm("       tsta");
  asm("       bne   P00BE"); //   * Sync line changed
asm("P00B9: sta   0xCA7F");
asm("       bra   P008A");

asm("P00BE: lda   <0x0D"); //     * If the refresh timer elapsed, then   D00D
asm("       bita  #0x20"); //     * the goggle disk is not yet up to
asm("       bne   P0076"); //     * speed; go thru another pass.

asm("       inc   0xC891"); //    * The disk is now upto speed; for
asm("       lda   0xC891"); //    * good measure, repeat, for a
asm("       cmpa  #0x03"); //     * total of 3 times.
asm("       beq   skip_over_proc");
asm("       bra   P0076");
//asm("*");
//asm("* GetGoggleIndexState()");
//asm("*");
//asm("* Check to see if the color wheel index has been seen.");
//asm("*");
//asm("* Exit: a = state of goggle index signal");
//asm("*           0 => index signal not seen");
//asm("*           !=0 => index signal seen");
//asm("*");
asm("GetGoggleIndexState:");
asm("       lda   #0x0E");
asm("       sta   <0x01"); //                                     D001
asm("       ldd   #0x1901");
asm("       sta   <0x00"); //                                     D000
asm("       nop");
asm("       stb   <0x00"); //                                     D000
asm("       clr   <0x03"); //     * Set Port A lines as inputs    D003
asm("       ldd   #0x0901");
asm("       sta   <0x00"); //                                     D000
asm("       nop;");
asm("       lda   <0x01"); //     * Read Port A lines             D001
asm("       nop");
asm("       stb   <0x00"); //                                     D000
asm("       ldb   #0xFF");
asm("       stb   <0x03"); //     * Set Port A lines as outputs   D003
asm("       anda  #0x80");
asm("       rts");
asm("skip_over_proc:");

  // I think we get stuck in one of the loops above and never get here?:

  *IRQ_INST = (unsigned int)0x7e; // JMP ... this is actually code! - first a JMP instruction,
  *IRQ = (unsigned long)gameLoop; // then the address to jump to.

  Vec_Rfrsh = segtimer[5];
  *(volatile unsigned int volatile *)0xD009 /*high*/ = (unsigned int)(Vec_Rfrsh >> 8UL) & 255;
  *(volatile unsigned int volatile *)0xD008 /*low*/  = (unsigned int)(Vec_Rfrsh & 255UL);

  // For the 3D imager, we'll be generating six times as many interrupts, i.e.
  // an average T2 of 5000 per segment.  However depending on the imager wheel
  // in use, the individual segments don't have to be 5000 each, as long as all
  // six add up to 30000.

  // enable T2 interrupt generation in VIA:
  /*dp_*/VIA_int_enable |= 0x20;
  ENABLE_IRQ_6809;

  // Note this is just a repeating 5000 cycle timer.  It is *not* using all
  // 6 saved timer values in segtimer[] which would be needed for any wheel
  // other than the equally-spaced one.  The timer should probably be reset
  // immediately on entry to the interrupt routine in order to support that.
}

int main(void) {

  // Do we need Jsr D0_to_DP to start? No - should be the default.

  // Initial code is just to create six time slices and draw one sprite per slice in
  // a different position for each slice.  Once that is working, we can add the code
  // to spin up the motor and try to synch it with the drawing slot timer.

  Vec_Joy_Mux_1_X = 1; // enable analog joystick mode
  Vec_Joy_Mux_1_Y = 3;

  mouse_was_down = FALSE; mouse_down = FALSE;

  DisplayList[RED+LEFT+BUFFER_ZERO] = Meanie;
  DisplayList[GREEN+LEFT+BUFFER_ZERO] = Meanie;
  DisplayList[BLUE+LEFT+BUFFER_ZERO] = Meanie;

  DisplayList[RED+RIGHT+BUFFER_ZERO] = Meanie;
  DisplayList[GREEN+RIGHT+BUFFER_ZERO] = Meanie;
  DisplayList[BLUE+RIGHT+BUFFER_ZERO] = Meanie;

  DisplayList[RED+LEFT+BUFFER_ONE] = Meanie;
  DisplayList[GREEN+LEFT+BUFFER_ONE] = Meanie;
  DisplayList[BLUE+LEFT+BUFFER_ONE] = Meanie;

  DisplayList[RED+RIGHT+BUFFER_ONE] = Meanie;
  DisplayList[GREEN+RIGHT+BUFFER_ONE] = Meanie;
  DisplayList[BLUE+RIGHT+BUFFER_ONE] = Meanie;

  // Spin up the imager and set up the interrupt routine.  In that order.
  start_graphics_thread(gameLoop); // last thing it does is enable interrupts.
  // MAIN GAME LOOP. Don't draw from here directly, ever.
  for (;;) {
    // For safety, make sure there are no bios calls in here which might interfere with the
    // graphics calls in the interrupt routines...

    // Below is the main loop code from Fred Taft's disassenbly of Narrow Escape 3D ...
    // I need to work out what it was doing to keep the wheel spinning at the the target
    // speed, and duplicate that for our situation.  Remembering that they had one
    // interrupt per frame and we have 6 ...

/*

MainGameLoop:
       jsr   set_refresh;

ReturnFromIRQ:
       ldd   $C83D;
       std   <$08;    * Set refresh timer
       ldb   $C845;
       andb  #$BF;
       lda   #$07;
       jsr   $F25B;   * Config Port A as an input
       jsr   read_switches2;
       ldb   $C845;
       orb   #$40;
       lda   #$07;
       jsr   $F25B;   * Config Port A as an output
       ldd   #$0E80;  * Write $80 to Port A
       jsr   byte_2_sound_chip;
       ldd   #$FF02;
       sta   $CA8B;
       stb   <$0D;    * Clear goggle index intrpt flag
       andcc #$EF;    * Enable goggle index intrpt (IRQ)
       ldd   $C825;
       addd  #$0001;  * For each pass, increment the
       std   $C825;   * 'refresh timer expired' counter.
       jsr   DoDrawing;
       clr   $C823;   * Disable joystick approximation
       jsr   read_jstick;
       jsr   P0353;
       jsr   $F35B;
       ldb   #$10;
P0139: decb;          * Timing loop
       bne   P0139;
       clr   $CA8B;
       cwai  #$EF;    * Enable IRQ & wait for goggle index interrupt.

*/

    if (mouse_down && !mouse_was_down) {
      // this was a click
    } else if (mouse_down) {
      // this is a continuing drag
    } else if (mouse_was_down) { // (but is no longer...)
      // end of drag - drop now.
    } else {
      // no change, just redraw
    }
    mouse_was_down = mouse_down;
    dotmask = (dotmask << 1) | (dotmask>>7); // for any dotted lines. Animate at a constant rate.

    // DO A FRAME'S WORTH OF GAMEPLAY COMPUTATION HERE.
    // and set up any graphics to be drawn in the double-buffered display list arrays.

    if ((eye_colour == RED) && (hand == LEFT)) { // now in start of 6th segment
      // Wait for final frame interrupt to happen
      // swap double buffers
      // Sit in a tight loop until a flag is written by the interrupt routine -
      // We only want to know when a full frame is done, not when 1/6th of a frame is done.
      WRITING_TO ^= 6; // this code writes, the interrupt code reads.
      do { } while (!complete_frame_done);  // wait here in a tight loop until
                                            // interrupt routine ticks over to slot 0.
      complete_frame_done = FALSE;
      DRAWING_FROM ^= 6;
    }
  }
  return 0;
  // Suppress annoying warnings:
  (void) &SHOW_N0;  (void) &SHOW_N1;  (void) &SHOW_N2;  (void) &SHOW_N3;
  (void) &SHOW_N4;  (void) &SHOW_N5;  (void) &SHOW_N6;  (void) &SHOW_N7;
  (void) &SHOW_N8;  (void) &SHOW_N9;  (void) &SHOW_MINUS;
  (void) &SHOW_SIGNED_NUM;  (void) &SHOW_UNSIGNED_NUM;
}

