#define VERSION "V1.4"
/*
 * Ersetz
 *
 * Game Design by Der Luchs
 * Code by Graham Toal
 * Music by Brett Walach
 *
 * Changelog
 * ---------
 * v0.9 - Initial Release without sound
 * v1.0 - Added music and death sound
 * v1.1 - Fixes bug on game over screen, could not restart after waiting too long
 * v1.2 - Fixes music loop to be more on beat
 * v1.3 - Merged with GT original
 * v1.4 - bug fix for timer wraparound
 */

#include <vectrex.h>
#include <assert.h>
#include <ymPlayerOptimSpeed.h>

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

#include "controller.h"
#include "spritelib.h"

#include "music.h"

#define SC 4
const int quad[] = {
/*
           *  (0,8)
           ^
           4
           v
*<-- 4 --> + <-- 4 -->*  (8,0)
 (-8,0)    ^
           |
           |
          10
           |
           |
           |
           v
           *  (0,-20)
*/
  0,10*SC,0,
 -1,-10*SC,-8*SC,
 -1,-22*SC,8*SC,
 -1,22*SC,8*SC,
 -1,10*SC,-8*SC,
  1
};

const int sm_tri[] = {
/*
               * (0,2)
(-3,0)  *      +      *  (3,0)
 */
  0, 2*SC,0,
 -1,-2*SC,-4*SC,
  0,0,8*SC,
 -1,2*SC,-4*SC,
  1
};

const int lg_tri[] = {
/*
              * (0,8)


              


(-3,0) *      +      * (3,0)
 */
  0, 8*SC,0,
 -1, -8*SC,-3*SC,
  0, 0,6*SC,
 -1, 8*SC,-3*SC,
  1
};

const MovableVLp smLeftVLp = { .points = ((sizeof sm_tri)-1)/3,
                               .packet_data = sm_tri };
const MovableVLp lgLeftVLp = { .points = ((sizeof lg_tri)-1)/3,
                               .packet_data = lg_tri };

const MovableVLp smRightVLp = { .points = ((sizeof sm_tri)-1)/3,
                               .packet_data = sm_tri };
const MovableVLp lgRightVLp = { .points = ((sizeof lg_tri)-1)/3,
                               .packet_data = lg_tri };


const MovableVLp QuadVLp = { .points = ((sizeof quad)-1)/3,
                             .packet_data = quad };

static Rotatable_VLp_Object Quad = {
  RotatableVL,        // PRIMITIVE type;
  0, 0,               // int y, x;
  63+128,                  // unsigned int angle;
  0,                  //  int deltaangle;
  0x6f,               // unsigned int Intensity;
  127, 127/5,         // unsigned int move_scale, draw_scale;
  (MovableVLp *)&QuadVLp // object data
};

static const int wallVLp[] = {
   //127,          // scale
   0, -63,-51,  // moveto
  -1,127,0,     // lineto
   0,-127,0,
   0, 0,102,  // moveto
  -1,127,0,     // lineto
   1             // end
};

static FixedVLp walls = {
  (int *)wallVLp
};

static Fixed_VLp_Object Walls = {
  FixedVL,
  127,
  &walls
};

static Rotatable_VLp_Object Tri[4] = {
{
  RotatableVL,        // PRIMITIVE type;
  0, -100,            // int y, x;
  63,                 // unsigned int angle;
  0,                  //  int deltaangle;
  0x6f,               // unsigned int Intensity;
  127, 127/5,         // unsigned int move_scale, draw_scale;
  (MovableVLp *)&smLeftVLp // object data
},
{
  RotatableVL,        // PRIMITIVE type;
  127, -100,          // int y, x;
  64,                 // unsigned int angle;
  0,                  //  int deltaangle;
  0x6f,               // unsigned int Intensity;
  127, 127/5,         // unsigned int move_scale, draw_scale;
  (MovableVLp *)&smLeftVLp // object data
},
{
  RotatableVL,        // PRIMITIVE type;
  -60, 100,           // int y, x;
  64+128,             // unsigned int angle;
  0,                  //  int deltaangle;
  0x6f,               // unsigned int Intensity;
  127, 127/5,         // unsigned int move_scale, draw_scale;
  (MovableVLp *)&smRightVLp // object data
},
{
  RotatableVL,        // PRIMITIVE type;
  60, 100,            // int y, x;
  64+128,             // unsigned int angle;
  0,                  //  int deltaangle;
  0x6f,               // unsigned int Intensity;
  127, 127/5,         // unsigned int move_scale, draw_scale;
  (MovableVLp *)&smRightVLp // object data
}
};

/* letter grid:

  *   *   *  4

  *   *   *  3

  *   *   *  2

  *   *   *  1

  *   *   *  0
  0   1   2
*/
#define LX 30
#define LY 30

static const int E_VLp[] = {
  0, 1*LY,2*LX,
 -1, -1*LY,-2*LX,
 -1, 4*LY,0*LX,
 -1, -1*LY,2*LX,
  0, -1*LY,-2*LX,
 -1, 0*LY,2*LX,
  1
};

static const int R_VLp[] = {
  -1, 4*LY,0*LX,
  -1, -1*LY,2*LX,
  -1, -1*LY,-2*LX,
  -1, -2*LY,2*LX,
  1
};

static const int S_VLp[] = {
  -1, 2*LY,2*LX,
  -1, 0*LY,-2*LX,
  -1, 2*LY,2*LX,
  1
};

static const int T_VLp[] = {
  0, 0*LY,1*LX,
 -1, 4*LY,0*LX,
 -1, -1*LY,1*LX,
  0, 0*LY,-2*LX,
 -1, 1*LY,1*LX,
  1
};

static const int Z_VLp[] = {
  0, 0*LY,2*LX,
 -1, 0*LY,-2*LX,
 -1, 4*LY, 2*LX,
 -1, 0*LY, -2*LX,
  1
};

const int *ERSETZT[] = {
  E_VLp, R_VLp, S_VLp, E_VLp, T_VLp, Z_VLp, T_VLp
};

/*
  PRIMITIVE type; // == RotatableVL
  int y, x;
  unsigned int angle;
  int deltaangle;
  unsigned int Intensity;
  unsigned int move_scale, draw_scale;
  MovableVLp *data;
      int points; // count of points in the VLp list - required.
      const int *packet_data;
 */

void bump_score(char *n) { // Add a fixed 11 points
  n[3] += 1;
  if (n[3] > '9') { n[3] -= 10; n[2] += 1; }
  n[2] += 1; if (n[2] > '9') { n[2] -= 10; n[1] += 1;}
  if (n[1] > '9') { n[1] -= 10; n[0] += 1;}
  if (n[0] > '9') {
    n[3] = n[2] = n[1] = n[0] = '0';
  }
}

int Pointing;
#define LEFT 0
#define RIGHT 1
int HIT(void) {
  int i, low, high;
  if (Quad.deltaangle == 0) { // horizontal
    if (Pointing == LEFT) {
      low = 0; high = 1;
    } else {
      low = 2; high = 3;
    }
    for (i = low; i <= high; i++) {
      if ((abs(Tri[i].y) < 10) && (Tri[i].Intensity)) {
        return TRUE;
      }
    }
  } else if (Quad.deltaangle == 4) {
    if (Pointing == RIGHT) { // currently moving from right to left
      low = 0; high = 1;
    } else {
      low = 2; high = 3;
    }
    for (i = low; i <= high; i++) {
      if ((Tri[i].Intensity) && 
          (Tri[i].y == 0 || Tri[i].y == -1 || Tri[i].y == -2) && 
          ((Quad.angle == 63-4) || (Quad.angle == 63+128+4))) { // one tick below horizontal
        return TRUE;
      }
    }
  }
  return FALSE;
}

static void FakeWaitRecal(void) {
    DP_to_C8();
    Explosion_Snd(current_explosion);
    Init_Music_chk(current_music);

    UpdateDisplay();

    if (pause_ym_sound == 0) {
        ym_sound();
    } else {
        // unpause our main tune if expl & music are not playing
        if (Vec_Music_Flag == 0 && Vec_Expl_Timer == 0) {
            // pause_ym_sound = 0;
            return;// 1;
        }
    }
    if (ym_cdata == 0) // song ended
    {
        ym_init((void*)song.data); /* 'C' callable init, sound data */
    }
    Do_Sound();
}

void offer_credits(void) {
    if (buttons_pressed()&1) {
      unsigned int text_timer = 255;
      int old_text_height = Vec_Text_Height;
      int old_text_width = Vec_Text_Width;
      do {
        Wait_Recal(); // raw
        Reset0Ref();
        InternalSetScale(127);
        Intensity_a(0x70);
        Vec_Text_Height = -8;
        Print_Str_d(55, -67, "ERSETZT " VERSION "\x80");
        Vec_Text_Width = 80;
        Vec_Text_Height = -3;
        Print_Str_d(30, -55, "GAME BY\x80");
        Print_Str_d(20, -65, "DER LUCHS\x80");
        Print_Str_d(0, -55, "CODE BY\x80");
        Print_Str_d(-10, -65, "GRAHAM TOAL\x80");
        Print_Str_d(-30,  -55, "MUSIC BY\x80");
        Print_Str_d(-40, -65, "BRETT WALACH\x80");
      } while (--text_timer != 0);
      Vec_Text_Height = old_text_height;
      Vec_Text_Width = old_text_width;
    }
}

int main(void) {
  static int init = 0;
/*
Still to do:  Music player option

            TITLETRACK          then  TRACK01 .. TRACK 10  GAME OVER
          |<           >|

*/
  char Score[6] = { '0', '0', '0', '0', 0x80, 0 };
  long timer, trigger[2] = {-1, -1}; // RIGHT, LEFT
  int side, i;
  int TRI[6], WALLS=0, QUAD=0;

RESTART:

  InitUniverse();
  // Positioning scale for objects is currently hard-coded as 127 - to be improved?
  // Other scales are per-object

  // Create and enable your sprites:
  WALLS = Post((Object *)&Walls, 0, 0, 127);
  QUAD = Post((Object *)&Quad, 0, 0, 127);

  for (i = 0; i < 4; i++) {
    TRI[i] = Post((Object *)&Tri[i], Tri[i].y, Tri[i].x, 127);
  }
  trigger[1] = trigger[0] = -1;
  //left = 1; right = 3; // index of next triangle to drop off...
  Score[3] = Score[2] = Score[1] = Score[0] = '0';
  Brightness(TRI[0], 0);Brightness(TRI[1], 0); // easy start with nothing on LHS
  Brightness(TRI[2], 0);Brightness(TRI[3], 0); // easy start with nothing on RHS

  // Kick off the sound!
  song.data = &(mad_max_enchanted_lands7_data);
  ym_init((void*)song.data); // only init music once
  sound_init();
  pause_ym_sound = 0;
  explosion_done = 0;

  // Big title screen. Choose game or music. Only game implemented so far.
  for (;;) {
    static int choice = 1;
    int x = -127;

    Wait_Recal(); // real one, so no sprite support here, no sound.
    check_buttons();
    Intensity_a(0x70);
    for (i = 0; i < 7; i++) {
      Reset0Ref();
      InternalSetScale(0x7f);
      Moveto_d(8, x);
      InternalSetScale(0x42);
      Draw_VLp((int *)ERSETZT[i]);
      x += 39;
    }
    InternalSetScale(0x7f);

    offer_credits(); // B1 during "ERSETZT" message gives credits
    if (buttons_pressed()&(2|4)) {
      choice = -choice;
    }
    Reset0Ref(); Intensity_a((choice>0?0x7fU:0x50U)); Print_Str_d(-8, -26, "START\x80");
    Reset0Ref(); Intensity_a((choice>0?0x50U:0x7fU)); Print_Str_d(-30, -60, "MUSIC PLAYER\x80");
    if (buttons_pressed()&8) {
      if (choice > 0) break;
      unsigned int text_timer = 255;
      int old_text_height = Vec_Text_Height;
      int old_text_width = Vec_Text_Width;
      do {
        Wait_Recal(); // raw
        Reset0Ref();
        InternalSetScale(127);
        Intensity_a(0x70);
        Vec_Text_Height = -8;
        Print_Str_d(55, -65, "MUSIC PLAYER?\x80");
        Vec_Text_Width = 80;
        Vec_Text_Height = -3;
        Print_Str_d(30, -100, "WHAT MUSIC PLAYER?!\x80");
        Print_Str_d(-10, -100, "WE HAVEN'T WRITTEN \x80");
        Print_Str_d(-22, -70, "THAT BIT YET\x80");
      } while (--text_timer != 0);
      Vec_Text_Height = old_text_height;
      Vec_Text_Width = old_text_width;
    }
  }
  // end of "ERSETZ" startup.  Play game.

  Pointing = LEFT;
  timer = 0L;

  for (;;) {
    FakeWaitRecal();
    Reset0Ref(); Intensity_a(0x50); Print_Str_d(127, -30, Score); // RasterString not yet in library

    if ((timer > 25L) && HIT()) {

      pause_ym_sound = 1;
      play_explosion(&bang);

      for (i = 0; i < 4; i++) Unpost(TRI[i]);
      Unpost(WALLS);
      Unpost(QUAD);
      // Would unpost score if strings were supported
      timer = 0;
      for (;;) {
        int old_text_height = Vec_Text_Height;
        int old_text_width = Vec_Text_Width;
        if (timer < 100) timer += 1L; // stops it going negative after 65536 ticks
        FakeWaitRecal();
        Reset0Ref();
        Intensity_a(0x50);
        Print_Str_d(127, -30, Score); // PrintRaster string not yet in library
        Intensity_a(0x70);
        Print_Str_d(8, -60, "GAME OVER\x80");

        Vec_Text_Width = 60; Vec_Text_Height = -3;
        Print_Str_d(-30, -122, "(PRESS ANY BUTTON TO RESTART)\x80");
        Vec_Text_Height = old_text_height; Vec_Text_Width = old_text_width;

        init = 0;
        // just a short lockout to ensure they see the "GAME OVER" message.
        if ((timer > 30L) && (buttons_pressed()&15)) goto RESTART;
      }
    }

    if ((Quad.angle == 63) && (Pointing == RIGHT)) {
      Quad.deltaangle = 0;
      Pointing = LEFT;
      bump_score(Score);
    } else if ((Quad.angle == 63+128) && (Pointing == LEFT)) {
      Quad.deltaangle = 0;
      Pointing = RIGHT;
      bump_score(Score);
    }

    if ((buttons_pressed()&15) && (Quad.deltaangle == 0)) {
      if (Pointing == LEFT) Quad.deltaangle = -4;
      if (Pointing == RIGHT) Quad.deltaangle = 4;
    }
    for (i = 0; i < 4; i++) {
      Tri[i].y = (Tri[i].y - 3);
      if (Tri[i].y > 32) Tri[i].data = (MovableVLp *)(i < 2 ? &smLeftVLp : &smRightVLp);
      else if (Tri[i].y < -125) {
        Brightness(TRI[i], 0x00); // I'm using brightness as a flag for whether the triangle
                                  // is active or not, it's cheap to draw with 0 intensity
                                  // and less complicated than Unpost-ing/Post-ing. I guess
                                  // this is telling me I need to add a 'hide/unhide' option...
      } else if (Tri[i].y < -12) Tri[i].data = (MovableVLp *)(i < 2 ? &smLeftVLp : &smRightVLp);
      else if (Tri[i].y < 32) Tri[i].data = (MovableVLp *)(i < 2 ? &lgLeftVLp : &lgRightVLp);
    }
    // last action: replenish the missing triangles
    // Both triangles missing:
    for (side = 0; side < 3; side += 2) {
      if (!Tri[side].Intensity && !Tri[side+1].Intensity) {
        // kick one off right away
        trigger[side>>1] = timer + (Random()&31);
        // DANGER WILL ROBINSON! If playing a long time, the timer can go negative.
        // at 50Hz frame rate that would be 32767/50 = 650 seconds or 10 minutes.
        // Is anyone likely to want to play a single round that long?
        // (timer is reset on each round)
        // Maybe the game should be modified slightly to put a time limit on a round
        // and see how high you can score within that time.
        // Also to do: speed the movement up after a certain time.
        // Brett has a faster tune we can slip in when that happens! (in his ersetz1.3 zip)
      }

      int this_side, that_side, other;
      if (!Tri[side].Intensity) this_side = side; else this_side = side+1; // right is the one to add.
      other = this_side^1;
      that_side = (this_side&2)^2;
      if ((trigger[side>>1] == -1) && (!Tri[side].Intensity || !Tri[side+1].Intensity)) {
        if (
             (Tri[other].y < 50)
             && (!Tri[that_side].Intensity || Tri[that_side].y < 50)
             && (!Tri[that_side^1].Intensity || Tri[that_side^1].y < 50)
            ) { // first determine minimum spacing
            trigger[side>>1] = timer + (Random()&31);
        }
      }

      if ((timer >= trigger[side>>1]) &&
          (!Tri[other].Intensity || Tri[other].y < 50) &&
          (!Tri[that_side].Intensity || Tri[that_side].y < 50) &&
          (!Tri[that_side^1].Intensity || Tri[that_side^1].y < 50)
          ) {
        if (!Tri[side+1].Intensity) {
          Tri[side+1].y = 120; Brightness(TRI[side+1], 0x7F);
        } else if (!Tri[side].Intensity) {
          Tri[side].y = 120; Brightness(TRI[side], 0x7F);
        }
        trigger[side>>1] = -1;
      }
    }
    timer += 1L;
  }
  return 0;
}