/*
  VERSION INFO:
    000 first public release Jan 12 2004
    001
        fix: drops only counted as +1 level, regardless of the number of lines killed
*/

/** mac compile: gcc -framework OpenGL -framework GLUT -framework Foundation -o FallingUp fallingup.c */
/* OS X */
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <GLUT/glut.h>
/**/

/* WINDOWS 
#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
*/


/** drawBlock "borrowed" from "gluttris" by manny najera [ mnajera@banarnar.com ] http://www.banarnar.com/gluttris */

/**
  customizable 'dedication' or 'about' screen?
  customizable colors
  customizable shapes and number of shapes?
  splash screen?
  top 10? (resettable, save where/how?)


  tetris sounds: block tick, block drop, line clear (2,3,4?), board "lock"
  music???

  two modes of play? how to switch/choose?  menu option, changes at 'new game'

  status bar? standard glut thing, or not?  maybe I do need to go SDL?

  stages (levels?):
    1 (0 lines) -- normal normal
  2 (1 line)  -- swings upside-down
  3 (8 lines) -- swings left-right
  4 (15 lines)-- slow spin left-right
  5 (23 lines)-- slow spin top-bottom
  6 (31 lines)-- slow random spin [[ how to fade?? ]]
  7 (39 lines)-- slow random spin based on key presses [[ how to fade?? ]]
  8 (47 lines)-- medium *

*/
#include <stdlib.h>
#include <time.h>
#include <stdio.h>

#define true 1
#define false 0

#define INFO 0
#define RUNNING 1
#define PAUSED 2
#define OVER 3
#define HIGHSCORE 4

#define PIECES 7

#define TOPN 10

#define XOFF -6.0
#define YOFF -9.0
#define ZOFF -16.0

#define MAXFLAME 120.0f

#define ROT_NONE 0
#define ROT_BOUNCE 1
#define ROT_CONTINUE 2
#define ROT_STOP 3
#define ROT_ONCE 4

/* GL/GLUT stuff... */
int mainWindowId;
int font = (int)GLUT_BITMAP_9_BY_15;
GLuint texture;
int replacescore=-1, replacechar=0;

const short CHANGEY=0, CHANGEX=1, CHANGEROT=2;
const int BASE_SPEED=1200;
const short PC_EL=0, PC_ELB=1, PC_S=2, PC_Z=3, PC_BLOCK=4, PC_BAR=5, PC_TEE=6; //PC:Piece#
const short data[][4][16]= {
  { { 0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1    } ,{ 0,0,0,0,0,0,1,0,0,0,1,0,0,1,1,0    } ,{ 0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,0    } ,{ 0,0,0,0,0,0,1,1,0,0,1,0,0,0,1,0    } } , //PC_EL
  { { 0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,0    } ,{ 0,0,0,0,0,0,1,0,0,0,1,0,0,0,1,1    } ,{ 0,0,0,0,0,0,0,1,0,1,1,1,0,0,0,0    } ,{ 0,0,0,0,0,1,1,0,0,0,1,0,0,0,1,0    } } , //PC_ELB
  { { 0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,0    } ,{ 0,0,0,0,0,1,0,0,0,1,1,0,0,0,1,0    } ,{ 0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,0    } ,{ 0,0,0,0,0,1,0,0,0,1,1,0,0,0,1,0    } } , //PC_S
  { { 0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1    } ,{ 0,0,0,0,0,0,1,0,0,1,1,0,0,1,0,0    } ,{ 0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1    } ,{ 0,0,0,0,0,0,1,0,0,1,1,0,0,1,0,0    } } , //PC_Z
  { { 0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0    } ,{ 0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0    } ,{ 0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0    } ,{ 0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0    } } , //PC_BLOCK
  { { 0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0    } ,{ 0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0    } ,{ 0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0    } ,{ 0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0    } } , //PC_BAR
  { { 0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,0    } ,{ 0,0,0,0,0,0,1,0,0,0,1,1,0,0,1,0    } ,{ 0,0,0,0,0,0,1,0,0,1,1,1,0,0,0,0    } ,{ 0,0,0,0,0,0,1,0,0,1,1,0,0,0,1,0    } }  //PC_TEE

};

float colors[][3] = {
  { 0.2,0.2,0.6  } , { 0.6,0.2,0.2  } , { 0.4,0.2,0.4  } , { 0.2,0.6,0.2  } , { 0.8,0.2,0.1  } , { 0.8,0.8,0.8  } , { 0.2,0.2,0.2  }
};

const int BLOCKTIMER=0;
const int SCALE=128,SCALEWIDTH=256,SCALEHEIGHT=256;
int HEIGHT=600, WIDTH=425, BLOCKW=200, BLOCKH=400;
char PROGRAM_NAME[80] = "Falling Up";
char title[80], scoreString[80], lineString[80], levelString[80];

int MAXLEVEL_NAMES=8;
char *levels[8] = {
  "Deceptive beginnings...", //0
  "Flipped!", //1
  "Flip^2", //2
  "A hint of things to come", //3
  "Double gasp", //4
  "You think you know terror?", //5
  "Lightly whirled peas", //6
  "eight" //7
};

char *topnames[TOPN];
int  topscores[TOPN];
int  toplevels[TOPN];

int linelevels[8] = { 1, 2, 3, 4, 5, 6, 7, 10000000 };

char *levelstring;

//info for keeping track of the time and frames per second
int timeOfRender=0;
int timeAtLastFrame=0;
int timeAtLastSecond=0;
int timeAtLastRender=0;
int timeOfBlockDrop=0;
int timeAtLastTexture=0;
int timeAtLastFlameCount= 0;
int timeAtLastRotCount = 0;

int frame=0;
int fps=0;
int displayFPS = false, displayNext = false;


int paused, gameOn;
int flameCount=1, flameDirection=1, rotCount=0, rotDirection=1, rotted=0;
int mode;

int lines, score, speed, level, nextlevel;

int rotMsec, rotMax, rotSpeed, rotStyle, rotDegrees;

short current, nextpiece;
short orientation;
short posx, posy; //position based on *center* of 3x3 grid. bar goes outside to the left/top
short grid[10][24]; //4 added to the top in the case of the bar being flipped up

char* makeString(int i) {
   char* mystring = (char*)malloc(sizeof(char)*(i+1));
   for (;i>=0;i--) mystring[i]=0;
   return mystring;
}

void writeScores(void) {
  FILE *out;
  int i;
  if ((out = fopen("fallingup.sco","w")) == NULL) {
    puts("Unable to open high score file for writing :(\n");
    exit(0);
  } else {
    for (i=0;i<TOPN;i++) {
      fprintf(out,"%s,%d,%d;",topnames[i],topscores[i],toplevels[i]+1);
    }
    fclose(out);
  }
}

void readScores(void) {
  FILE *in;
  int i,j;
  int c;
  if ((in = fopen("fallingup.sco","r")) == NULL) {
    writeScores();
  } else {
    for (i=0;i<TOPN;i++) {
      //free(topnames[i]); //TODO: can't free on first pass?
      topnames[i]=makeString(20);
      j=0;
      while ((c = fgetc(in)) != ',') {
        topnames[i][j]=c; j++;
      }
      fscanf(in,"%d,%d;",&topscores[i],&toplevels[i]);
    }
    fclose(in);
  }
}

void endGame(void) {
  int i,j;
  for (i=0, replacescore = -1;i<TOPN && replacescore == -1;i++) {
    if (score > topscores[i]) {
      replacescore = i;
      replacechar=0;
      mode = HIGHSCORE;
      free (topnames[TOPN-1]);
      for (j=TOPN-1;j>=i;j--) {
        topscores[j+1]=topscores[j];
        topnames[j+1]=topnames[j];
        toplevels[j+1]=toplevels[j];
      }
      topnames[replacescore]=makeString(20);
      topnames[replacescore][replacechar]='_';
      topscores[replacescore]=score;
      toplevels[replacescore]=level;
    }
  }
  if (mode != HIGHSCORE) mode=OVER;
  paused=false;
  //EnableMenuItem(menu,IDM_START,MF_ENABLED);
  //EnableMenuItem(menu,IDM_STOP,MF_GRAYED);
  //EnableMenuItem(menu,IDM_PAUSE,MF_GRAYED);
  gameOn=false;
}

int isHit(int myposx, int myposy, const short* tempblock) {
  int x,y;
  for (x=0;x<4;x++) for (y=0;y<4;y++) {
    if (tempblock[x+y*4] != 0) {
      //if this space exists in the grid, or is outside the left/right/bottom bounds... bip
      if (myposx+x < 0 || myposx+x > 9 || myposy-y < 0) return 1;
      if (grid[myposx+x][myposy-y] != -1) return 1;
    }
  }
  return 0;
}

void setBlock(int myposx, int myposy, int myorientation,int mycurrent) {
  int x,y,clear,numclear,suby;
  //TODO: reset timer?
  for (x=0;x<4;x++) for (y=0;y<4;y++) {
    if(data[mycurrent][myorientation][x+y*4] != 0) {
      grid[myposx+x][myposy-y] = current;
    }
  }
  current=nextpiece;
  nextpiece=rand()%PIECES;
  posx=3;
  posy=22;
  orientation=0;
  if (isHit(posx,posy,data[current][orientation])) {
    endGame();
    return;
  }
  /*test to see if we can clear some lines :)*/
  numclear=0;
  for (y=0;y<21;y++) {
    clear=1;
    for (x=0;x<10;x++) if (grid[x][y] == -1) clear=0;
    if (clear) {
      numclear++;
      lines+=1;
      //go up a level, depending
      //TODO: make this a formula?
      while (lines >= linelevels[nextlevel]) nextlevel++;
      for (suby=y+1;suby<21;suby++) for (x=0;x<10;x++) grid[x][suby-1]=grid[x][suby];
      y--;
    }
    //TODO: better speed alg?
    //speed is msecs to wait for a block drop
    speed=BASE_SPEED - lines * 35;
    if (speed < 125) speed=125;

    //TODO: better scoring system?
    score+=100*(numclear*numclear);
  }
  clear=1;
  for (x=0;x<10;x++) if (grid[x][21] != -1) clear=0;
  if (clear!=1) endGame();
  /*test to see if the game is over*/
}

int testBounds(short changeType, int amount) {
  //if we would hit something, and it's due to ydif, stick us there.
  //hitting something INCLUDES the bottom of the screen, BTW, just in case you
  //were wondering, this is where that check happens. Neener neener. :)
  //if we're rotating or xdiffing or ydiffing and not hitting anything, make it so.
  int i,mycurrent,myposx,myposy,myorientation, stuck;
  short tempblock[16];
  mycurrent=current;
  myposx=posx;
  myposy=posy;
  myorientation=orientation;
  stuck = false;
  if (changeType == CHANGEY) {
    for (i=0;i<16;i++) tempblock[i]=data[mycurrent][myorientation][i];
    if (isHit(myposx,myposy-amount,tempblock) == 1) {
      //stick us there
      setBlock(myposx,myposy,myorientation,mycurrent);
      stuck = true;
    }
    else {
      posy-=amount;
    }
  }
  else if (changeType == CHANGEX) {
    for (i=0;i<16;i++) tempblock[i]=data[current][orientation][i];
    if (isHit(myposx-amount,myposy,tempblock) != 1) posx-=amount;
  }
  else { //changeType == CHANGEROT

    for (i=0;i<16;i++) tempblock[i]=data[current][(orientation+1)%4][i];
    if (isHit(myposx,myposy,tempblock) != 1) orientation=(orientation+1)%4;
  }
  return stuck;
}

void drawBlock(float x, float y, float z, float red, float green, float blue) {
  // save the current position and move to the new one

  glPushMatrix();
  x-=5;
    y-=7;
  glTranslatef(x, y, z);

  // begin to draw a block in this position

  glBegin(GL_QUADS);

  // we set the color to what was passed, but we also make our own shades

  glColor4f(red, green, blue,1.0);
  glVertex3f(-0.5, 0.5, 0.0);
  glVertex3f(0.5, 0.5, 0.0);
  glVertex3f(0.5, -0.5, 0.0);
  glColor4f((1.0-red)/2.0, (1.0-green)/2.0, (1.0-blue)/2.0,1.0);
  glVertex3f(-0.5, -0.5, 0.0);

  glEnd();

  // now, surround it with a pretty line border

  glBegin(GL_LINES);

  glColor4f(0.0, 0.0, 0.0, 1.0);
  glVertex3f(-0.5, 0.5, 0.0);
  glVertex3f(0.5, 0.5, 0.0);
  glVertex3f(-0.5, 0.5, 0.0);
  glVertex3f(-0.5, -0.5, 0.0);
  glVertex3f(0.5, 0.5, 0.0);
  glVertex3f(0.5, -0.5, 0.0);
  glVertex3f(-0.5, -0.5, 0.0);
  glVertex3f(0.5, -0.5, 0.0);

  glEnd();

  // before we finish, restore the matrix

  glPopMatrix();
}

void renderBitmapString(float x, float y, void *font,char *string)
{

  char *c;
  // set position to start drawing fonts
  glRasterPos2f(x, y);
  // loop all the characters in the string
  for (c=string; *c != '\0'; c++) {
    glutBitmapCharacter(font, *c);
  }
}


long getAbsoluteMillis(void) {
  //return GetTickCount(); //TODO: not partable enough??? // needed for windows?
  return glutGet(GLUT_ELAPSED_TIME);  //fine on OS X... seemed bad on windows...
}

void setOrthographicProjection(void) {

  // switch to projection mode
  glMatrixMode(GL_PROJECTION);
  // save previous matrix which contains the
  //settings for the perspective projection
  glPushMatrix();
  // reset matrix
  glLoadIdentity();
  // set a 2D orthographic projection
  gluOrtho2D(0, WIDTH, 0, HEIGHT/2);
  glScalef(1, -1, 1);
  // mover the origin from the bottom left corner
  // to the upper left corner
  glTranslatef(0, -HEIGHT/2, 0);
  glMatrixMode(GL_MODELVIEW);
}

void resetPerspectiveProjection(void) {
  // set the current matrix to GL_PROJECTION
  glMatrixMode(GL_PROJECTION);
  // restore previous settings
  glPopMatrix();
  // get back to GL_MODELVIEW matrix
  glMatrixMode(GL_MODELVIEW);
}

void texturize(void) {

  glPushMatrix();
  glDisable(GL_DEPTH_TEST);
  glEnable(GL_BLEND);

  //glColor4f (1.0f, 1.0f, 1.0f, 1.0f - (1.0f/6.0f)); //TODO: (frames) pick blur
  glColor4f (1.0f, 1.0f, 1.0f, 1.0f - (1.0f/6.0f)); //TODO: (frames) pick blur

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glOrtho(0, WIDTH, HEIGHT, 0, 0, 1);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  glBindTexture(GL_TEXTURE_2D, texture);
  glBegin(GL_QUADS);
    glTexCoord2f(0.0, 1.0);  glVertex2f(0, 0);
    glTexCoord2f(0.0, 0.0);  glVertex2f(0, HEIGHT);
    glTexCoord2f(1.0, 0.0);  glVertex2f(WIDTH, HEIGHT);
    glTexCoord2f(1.0, 1.0);  glVertex2f(WIDTH, 0);
  glEnd();

  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);

  glEnable(GL_DEPTH_TEST);
  glDisable(GL_BLEND);
  glPopMatrix();

  glPushMatrix();
  glTranslatef (0.0f, 0.0f, -200.0f);

  //TODO: DO THE ACTUAL MAGIC OF ADDING SOMETHING TO THE TEXTURE!
  //glClearColor(16.0/(double)flameCount,16.0/(double)flameCount,16.0/(double)flameCount,1);
  //glClearColor(MAXFLAME/(double)flameCount,MAXFLAME/(double)flameCount,MAXFLAME/(double)flameCount,1);
  glClearColor((double)flameCount/MAXFLAME,(double)flameCount/MAXFLAME,(double)flameCount/MAXFLAME,1);
  //glRotatef(360.0f*(flameCount/MAXFLAME),0,0,1); // 010
  glBegin(GL_QUADS);

  // we set the color to what was passed, but we also make our own shades

  glColor4f(.2, .2, .2,1.0);
  glVertex3f(-220, 220, 0.0);
  glColor4f(.2, .9, .2,1.0);
  glVertex3f(220, 220, 0.0);
  glColor4f(.8, .8, .8,1.0);
  glVertex3f(220, -220, 0.0);
  glColor4f(.3,.3, 1.0,1.0);
  glVertex3f(-220, -220, 0.0);

  glEnd();
  //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //TODO: needed?

  glPopMatrix();

}

void renderBlocks(void) {
  int x, y;
  float XLEFT, XRIGHT, YTOP, YBOTTOM;
  XLEFT = -5.5;
  XRIGHT= XLEFT+10.0;
  YTOP = -7.5;
  YBOTTOM = YTOP-0.5;

    glTranslatef(XOFF/2, YOFF/2, ZOFF);

  //increment 'display level'
  if (level != nextlevel) {
    if (rotStyle != ROT_STOP && rotCount > 0) rotStyle = ROT_STOP;
    if (rotCount == 0 || rotted) {
      rotted=false;
      rotCount = 0;
      //level = nextlevel;
      level++;
      levelstring=levels[level];
      if (level == 0) {
        //already set up?
      } else if (level == 1) {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else if (level == 2) {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else if (level == 3) {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else if (level == 4) {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else if (level == 5) {
        rotStyle=ROT_CONTINUE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else if (level == 6) {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else if (level == 7) {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else if (level == 8) {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else if (level == 9) {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      }
    }
  }

  if (level == 0) {
    rotCount = 0;
  } else if (level == 1) {
    glRotatef(180.0f*((float)rotCount/(float)rotMax),5,0,0);
    glTranslatef(0.0f, -5.0*((float)rotCount/(float)rotMax), 0.0f);
  } else if (level == 2) {
    glRotatef(180.0f,5,0,0);
    glTranslatef(0.0f, -5.0, 0.0f);
    glRotatef(180.0f*((float)rotCount/(float)rotMax),0,5,0);
    //glRotatef(360.0f*((float)rotCount/(float)rotMax),1,1,1);
  } else if (level == 3) {
    glRotatef(180.0f*((float)(rotMax-rotCount)/(float)rotMax),5,0,0);
    glRotatef(180.0f*((float)(rotMax-rotCount)/(float)rotMax),0,5,0);
    glTranslatef(0.0f, -5.0*((float)(rotMax-rotCount)/(float)rotMax), 0.0f);
    glRotatef(360.0f*((float)rotCount/(float)rotMax),-10,0,0);
  } else if (level == 4) {
    glRotatef(360.0f*((float)rotCount/(float)rotMax),0,0,0);
    glRotatef(360.0f*((float)rotCount/(float)rotMax),5,0,0);
  } else if (level == 5) {
    glRotatef(360.0f*((float)rotCount/(float)rotMax),0,5,0);
/*
    glRotatef(360.0f*((float)rotCount/(float)rotMax),0,1,0);
    glRotatef(360.0f*((float)rotCount/(float)rotMax),1,0,0);
    glRotatef(360.0f*((float)rotCount/(float)rotMax),0,0,1);
*/
  } else if (level > 5) {
    glRotatef(180.0f*((float)rotCount/(float)rotMax),0,5,0);
    glRotatef(360.0f*((float)rotCount/(float)rotMax),0,1,0);
    glRotatef(360.0f*((float)rotCount/(float)rotMax),1,0,0);
    glRotatef(360.0f*((float)rotCount/(float)rotMax),0,0,1);
  }

  switch(mode) {
  case HIGHSCORE:
  case OVER:
  case RUNNING:
    //for each x/y of the grid, paint what's there... then paint our block in its position?
    for (x=0;x<10;x++) for (y=0;y<20;y++) {
      if (grid[x][y] > -1) {
        drawBlock(x,y,0,colors[grid[x][y]][0],colors[grid[x][y]][1],colors[grid[x][y]][2]);
      }
    }
    if (current != -1) {
      for (x=0;x<4;x++) for (y=0;y<4;y++) {
        if (data[current][orientation][y*4+x] != 0)
          drawBlock(posx+x,posy-y,0,colors[current][0],colors[current][1],colors[current][2]);
      }
    }

    // begin to draw the UNDERLINE

    glBegin(GL_QUADS);

    glColor3f(1.0, 1.0, 0.0);
    glVertex3f(XLEFT, YTOP, 0);
    glVertex3f(XRIGHT, YTOP, 0);
    glVertex3f(XRIGHT, YBOTTOM, 0);
    glVertex3f(XLEFT, YBOTTOM, 0);

    glEnd();

    // now, surround it with a pretty line border

    glBegin(GL_LINES);

    glColor3f(0.0, 0.0, 0.0);
    glVertex3f(XLEFT, YTOP, 0);
    glVertex3f(XRIGHT, YTOP, 0);
    glVertex3f(XLEFT, YTOP, 0);
    glVertex3f(XLEFT, YBOTTOM, 0);
    glVertex3f(XRIGHT, YTOP, 0);
    glVertex3f(XRIGHT, YBOTTOM, 0);
    glVertex3f(XLEFT, YBOTTOM, 0);
    glVertex3f(XRIGHT, YBOTTOM, 0);

    glEnd();

    glTranslatef(-XOFF/2, -YOFF/2, ZOFF);


    setOrthographicProjection();
    glPushMatrix();
    glLoadIdentity();
    sprintf(scoreString,"%d",score);
    sprintf(lineString,"%d",lines);
    glColor3f(0.0,0.0,0.0);
    renderBitmapString(14,20,(void *)font,levelstring);
    if (displayNext) {
      renderBitmapString(288,165,(void *)font,"Next:");
    }
    renderBitmapString(288,226,(void *)font,"Score:");
    renderBitmapString(298,241,(void *)font,scoreString);
    renderBitmapString(288,256,(void *)font,"Lines:");
    renderBitmapString(298,271,(void *)font,lineString);
    glColor3f(0.0,1.0,0.6);
    renderBitmapString(15,19,(void *)font,levelstring);
    if (displayNext) {
      renderBitmapString(289,166,(void *)font,"Next: (F5)");
    }
    renderBitmapString(289,227,(void *)font,"Score:");
    renderBitmapString(299,242,(void *)font,scoreString);
    renderBitmapString(289,257,(void *)font,"Lines:");
    renderBitmapString(299,272,(void *)font,lineString);

    glPopMatrix();
    resetPerspectiveProjection();
    break;
  case PAUSED: break;
  default:
    //TODO: error? probably not, really.
    break;
  }
}

void renderScores(void) {
  static char lineitem[80];
  int i;
  for (i=0;i<TOPN;i++) {
    sprintf(lineitem,"%-20s      %-9d  %d",topnames[i],topscores[i],toplevels[i]);
    renderBitmapString(30,180+i*10,(void *)font,lineitem);
  }
}

void renderScene(void) {
  //TODO: this is my idle function!
  glutSetWindow(mainWindowId); // not really needed, only one window
  timeAtLastRender = timeOfRender;
  timeOfRender = getAbsoluteMillis();
  char lineitem[80];
  int i, x, y;

  frame++;

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //TODO: needed?

  glLoadIdentity();
  glViewport(0,0,512,512);
  //texturize();
  glBindTexture(GL_TEXTURE_2D,texture);
  glCopyTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,0,0,512,512,0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glViewport(0,0,WIDTH,HEIGHT);
  //texturize(); //TODO -- figure out why this second texturize kills rotation on SHOUT

  if (timeOfRender - timeAtLastFlameCount > 20) {
    flameCount+=flameDirection;
    //if (flameCount > 15 || flameCount < 1) flameDirection *= -1;
    //if (flameCount >= MAXFLAME) flameCount = 0;
    if (flameCount > MAXFLAME || flameCount < 1) flameDirection *= -1;
    timeAtLastFlameCount = timeOfRender;
  }

  if (timeOfRender - timeAtLastRotCount > rotMsec) {
    if (!rotted && rotStyle != ROT_NONE) {
      rotCount +=rotDirection;
      if (rotCount < 0) {
        if (rotStyle == ROT_ONCE || rotStyle == ROT_STOP) { rotted = true; rotCount = 0;}
        else if (rotStyle == ROT_BOUNCE) { rotDirection *=-1; rotCount = rotDirection; }
        else if (rotStyle == ROT_CONTINUE) { rotCount += rotMax; }
      } else {
        if (rotCount > rotMax) {
          if (rotStyle == ROT_ONCE || rotStyle == ROT_STOP) { rotted = true; rotCount = rotMax; }
          else if (rotStyle == ROT_BOUNCE) { rotDirection *=-1; rotCount = rotMax+rotDirection; }
          else if (rotStyle == ROT_CONTINUE) { rotCount -= rotMax; }
        }
      }
      timeAtLastRotCount = timeOfRender;
    }
  }

  if (timeOfRender - timeAtLastSecond > 1000) {
    fps = frame*1000.0/(timeOfRender - timeAtLastSecond);
    timeAtLastSecond = timeOfRender;
    frame = 0;
    sprintf(title,"%s [%3d FPS], %d, %d, %d, %d",PROGRAM_NAME,fps,flameCount,rotCount, level, nextlevel);
    if (displayFPS) glutSetWindowTitle(title);
  }


  //TODO: swap renderScenes out depending on mode!! seems more OO.
  switch (mode) {
  case INFO:
    setOrthographicProjection();
    glPushMatrix();
    glLoadIdentity();
    glColor3f(0.0,0.0,0.0);
    renderBitmapString(20,15,(void *)font,"Falling Up");
    renderBitmapString(20,30,(void *)font,"(c) 2004 Kaolin Fire [http://erif.org]");
    renderBitmapString(20,160,(void *)font,"Top scores");
    glColor3f(0.0,1.0,0.6);
    renderBitmapString(21,16,(void *)font,"Falling Up");
    renderBitmapString(21,31,(void *)font,"(c) 2004 Kaolin Fire [http://erif.org]");
    renderBitmapString(21,161,(void *)font,"Top scores");
    glColor3f(1.0,1.0,0.0);
    renderBitmapString(40,60,(void *)font,"F1      --  start");
    renderBitmapString(40,70,(void *)font,"F2      --  pause");
    renderBitmapString(40,80,(void *)font,"F3      --  toggle framerate display");
    renderBitmapString(40,90,(void *)font,"F5      --  toggle 'next piece'");
    renderBitmapString(40,100,(void *)font,"F12/ESC --  bosskey (pause/minimize)");
    renderBitmapString(40,110,(void *)font,"ALT-F4  --  quit :)");

    renderScores();
 
    glPopMatrix();
    resetPerspectiveProjection();
    break;
  case RUNNING:
    if (timeOfRender > timeOfBlockDrop + speed) {
      testBounds(CHANGEY,1);
      timeOfBlockDrop = timeOfRender;
    }
    renderBlocks();
    if (displayNext) {
      if (nextpiece != -1) {
        glPushMatrix();
        glLoadIdentity();
        glTranslatef(9.5, 1.0, ZOFF);
        for (x=0;x<4;x++) for (y=0;y<4;y++) {
          if (data[nextpiece][0][y*4+x] != 0)
            drawBlock(x,4-y,0,colors[nextpiece][0],colors[nextpiece][1],colors[nextpiece][2]);
        }
        glPopMatrix();
      }
    }
    break;
  case OVER:
    renderBlocks();
    setOrthographicProjection();
    glPushMatrix();
    glLoadIdentity();
    glColor3f(0.0,0.0,0.0);
    renderBitmapString(63,135,(void *)font," -- GAME OVER: F1 to play again --");
    glColor3f(0.5,0.5,0.9);
    renderBitmapString(64,136,(void *)font," -- GAME OVER: F1 to play again --");
    glPopMatrix();
    resetPerspectiveProjection();
    break;
  case PAUSED:
    timeOfBlockDrop += (timeOfRender - timeAtLastRender); // extend block drop
    setOrthographicProjection();
    glPushMatrix();
    glLoadIdentity();
    glColor3f(0.0,0.0,0.0);
    renderBitmapString(83,135,(void *)font," -- PAUSED: F2 to continue --");
    glColor3f(0.5,0.5,0.9);
    renderBitmapString(84,136,(void *)font," -- PAUSED: F2 to continue --");
    glPopMatrix();
    resetPerspectiveProjection();
    break;
  case HIGHSCORE:
    setOrthographicProjection();
    glPushMatrix();
    glLoadIdentity();
    renderBitmapString(94,136,(void *)font,"!!! HIGH SCORE !!!");
    renderScores();
    glPopMatrix();
    resetPerspectiveProjection();
    break;
 

  default:
    break; //TODO: error?
  }

  glutSwapBuffers(); // TODO: almost certainly needed... somewhere :) here?

  //glutPostRedisplay(); // TODO: necessary?  only for really cool stuff?
}


void startGame(void) {
  int x, y;
  mode=RUNNING; //TODO: start in INFO mode
  gameOn=true;
  srand(time(NULL));
  lines=0;
  level=0;
  nextlevel=0;
  score=0;
  levelstring=levels[0];
  speed=BASE_SPEED;
  for (x=0;x<10;x++) for (y=0;y<22;y++) grid[x][y]=-1;
  current=(short)(rand()%PIECES);
  nextpiece=(short)(rand()%PIECES);
  posx=(short)3;
  posy=(short)22;
  orientation=(short)0;
  timeOfBlockDrop=0;
  rotCount=0;
  rotStyle=ROT_NONE;
  rotMax=180;
}

void bosskey(void) {
    if (mode == RUNNING) {
      mode=PAUSED;
    }
    glutIconifyWindow();
}

void processNormalKeys(unsigned char key, int x, int y) {
  if (key == 27) {
    bosskey();
  } else if (mode == RUNNING) {
    if (key == ' ') {
      while (!testBounds(CHANGEY,1));
    }
  } else if (mode == OVER) {
    if (key == '\n' || key == ' ') mode = INFO;
  } else if (mode == HIGHSCORE) {
    if (key == 8 || key == 14 || key == 127 || key == '_') { // TODO: what other ascii characters (backspace vs delete?)
      if (replacechar > 0) {
      topnames[replacescore][replacechar]=0;
      replacechar--;
      topnames[replacescore][replacechar]='_';
      }
    } else if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z')) {
      if (replacechar < 20 - 1) {
        topnames[replacescore][replacechar]=key;
        replacechar++;
        topnames[replacescore][replacechar]='_';
      }
    } else if (key == '\n' || key == 13 || key == 10) {
      topnames[replacescore][replacechar]=0;
      writeScores();
      mode = INFO;
    }
  }
}

void processSpecialKeys(int key, int x, int y) {
  switch (key) {
  case GLUT_KEY_DOWN:
    if (mode == RUNNING) testBounds(CHANGEY,1);
    break;
  case GLUT_KEY_LEFT:
    if (mode == RUNNING) testBounds(CHANGEX,1);
    break;
  case GLUT_KEY_RIGHT:
    if (mode == RUNNING) testBounds(CHANGEX,-1);
    break;
  case GLUT_KEY_UP:
    if (mode == RUNNING) testBounds(CHANGEROT,1);
    break;
  case GLUT_KEY_F3:
    displayFPS = !displayFPS;
    glutSetWindowTitle(PROGRAM_NAME);
    break;
  case GLUT_KEY_F5:
    displayNext = !displayNext;
    break;
  case GLUT_KEY_F1:
    if (mode != RUNNING) startGame();
    break;
  case GLUT_KEY_F4:
    if (glutGetModifiers() & GLUT_ACTIVE_ALT) exit(0);
    break;
  case GLUT_KEY_F2:
    if (mode == RUNNING) {
      mode=PAUSED;
    } else if (mode == PAUSED) {
      mode=RUNNING;
    }
    break;
  case GLUT_KEY_F12:  bosskey(); break;
  default: break;
  }
}


void fallingupInit(void) {
  unsigned int *pData, *pDataTemp;
  int i, memory=128*128*4*sizeof(unsigned int);
  mode=INFO;
  pDataTemp = pData = (unsigned int*) malloc((512*512) * 4 * sizeof(unsigned int));
    for (i=0;i<memory;i++) { *pDataTemp=0; pDataTemp++; }

    glGenTextures(1, &texture);
  glBindTexture(GL_TEXTURE_2D, texture);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

  glTexImage2D(GL_TEXTURE_2D, 0, 3, 512, 512, 0, GL_RGB, GL_UNSIGNED_BYTE, pData);  // Use when not wanting mipmaps to be built by openGL
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glClearColor(0.5f,0.5f,0.55f,0.0f);
  free(pData);

  glEnable(GL_LINE_SMOOTH);
  //glEnable(GL_POLYGON_SMOOTH);

  //TODO: init from file if file exists, else write to file!?
  for (i=0;i<TOPN;i++) {
    topnames[i]=makeString(20);
    strcpy(topnames[i],(i%2)?"george":"jenny");
    topscores[i]=(TOPN-i)*(TOPN-i)*1000;
    toplevels[i]=0;
  }
  readScores();
}

void windowResize(int w, int h) {
  glutSetWindow(mainWindowId);
  glViewport(0,0,w,h);
  if (w != WIDTH || h != HEIGHT) {
    glutReshapeWindow(WIDTH,HEIGHT);
  }
  else {
    //glutSetWindow(blockWindowId);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(80,(float)WIDTH/(float)HEIGHT,1.0,5000.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
  }
}

int main (int argc, char **argv) {
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
  glutInitWindowPosition(100,100); //TODO: CENTER?
  glutInitWindowSize(WIDTH,HEIGHT);
  mainWindowId = glutCreateWindow(PROGRAM_NAME);

  glShadeModel(GL_SMOOTH);
  glClearColor(0.5f,0.5f,0.55f,0.0f);

  fallingupInit();

  //glutIgnoreKeyRepeat(1);
  glutKeyboardFunc(processNormalKeys);
  glutSpecialFunc(processSpecialKeys);
  //glutSpecialUpFunc(releaseKey);

  glutDisplayFunc(renderScene);
  glutIdleFunc(renderScene);
  glutReshapeFunc(windowResize);

  glutMainLoop();

  //wcl.hIcon = LoadIcon(NULL, IDI_APPLICATION); /* standard icon */
  //wcl.hIconSm = LoadIcon(NULL, IDI_WINLOGO); /* small icon */
  //wcl.hCursor = LoadCursor(NULL, IDC_ARROW); /* cursor style */
  //wcl.lpszMenuName = "Tetris"; /* NULL if no menu */
  /*load menu accelerators*/
  //hAccel = LoadAccelerators(hThisInst, "Tetris");

  return(0);
}
