#include <Body.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <iostream.h>
#include <unistd.h>
#include <userInterface.h>
#include <sys/time.h>

#define TEXTURE_DIR    "textures/"
#define LINE_LENGTH    255
#define MAX_TREE_DEPTH 10
#define COMMENT_CHAR   '#'
#define BODY_DELIM     "."
#define AU             149597870

int   checkArgs   (int myargc, char **myargv);
void  display     ();
void  init        ();
Body* makeScene   ();
void  printUsage  (char *progname);
int   parseNames  (char *fullname, char *names[MAX_TREE_DEPTH]);
void  reshape     (int w, int h);
int   update      ();

char   planet_file[LINE_LENGTH];
int    fileset = 0;
double disExp = 1.0, radExp = 0.1, masExp = 1.0,
       orbExp = 1.0, rotExp = 1.0;
Body*  root;

long int ltime = 0, timestep = 100000;

int main (int argc, char **argv) {

  if (!checkArgs(argc, argv))
    printUsage(argv[0]);

  srand48(getpid());
  
  glutInitWindowSize(900, 600);
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutCreateWindow(argv[0]);
  init();
  glutReshapeFunc(reshape);
  userInterfaceInit();
  glutKeyboardFunc(keyboard);

  root = makeScene();
  root->setExp(disExp, radExp, masExp, orbExp, rotExp);
  root->dump();
  
  glutDisplayFunc(display);
  glutMainLoop();

  delete root;
  
  return 0;

}

/* initialize state */
void init (void) {
  GLfloat mat_diffuse[]    = {  0.5, 0.5,  0.5,   1.0 };
  GLfloat mat_specular[]   = {  0.2, 0.2,  0.2,   1.0 };  /* white highlight */
  GLfloat mat_shininess[]  = { 50.0 };
  GLfloat light_position[] = { 0.0, 0.0,  50.0,   0.0 };

  glClearColor (0.0, 0.0, 0.0, 0.0);

  glShadeModel (GL_SMOOTH);
  glDepthFunc  (GL_LESS);

  glMaterialfv (GL_FRONT,  GL_DIFFUSE,   mat_diffuse);
  glMaterialfv (GL_FRONT,  GL_SPECULAR,  mat_specular);
  glMaterialfv (GL_FRONT,  GL_SHININESS, mat_shininess);

  glLightfv    (GL_LIGHT0, GL_POSITION,  light_position);

  glEnable(GL_NORMALIZE);  /* Make my normals unit length for me. */
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);

  glEnable(GL_DEPTH_TEST);
}

int update () {
  struct timeval ctime;

  gettimeofday(&ctime, NULL);
  if (labs(ctime.tv_usec - ltime) > timestep) {
    ltime = ctime.tv_usec;
    return 1;
  } else
    return 0;
}

/* Clear window and draw surface. */
void display (void) {
  GLfloat  matrix[16];
  static int curtime = 0;

  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
  glPushMatrix();
  glLoadIdentity();
  gluLookAt(0, 0, 65,   0, 0, 0,   0, 1, 0);
  glRotatef(45.0, 1.0, 0.0, 0.0);
  glMultMatrixf(matrix);

  glEnable(GL_TEXTURE_2D);
  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); 
  if (update()) {
    curtime += 1.0;
    root->draw((curtime + 1.0) / 5.0);
  } else
    root->draw((curtime + 1.0) / 5.0);

  //cout << curtime << endl;

  glDisable(GL_TEXTURE_2D);
  glPopMatrix();
  glutSwapBuffers();
}

/* Handle window resize. */
void reshape (int w, int h) {
  glViewport(0, 0, (GLsizei) w, (GLsizei) h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 100.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(0, 0, 0,   0, 0, -1,   0, 1, 0);
}


Body* makeScene () {
  char line[LINE_LENGTH], *names[MAX_TREE_DEPTH], fullname[50],
       texture[25], texpath[50];
  float dis, rad, mass, orb, rot;
  Body *root = NULL, *parent = NULL, *child = NULL;
  int cur_par = 0, name_cnt = 0;
  FILE *fptr;

  if (fileset)
    fptr = fopen(planet_file, "r");
  else
    fptr = stdin;

  while (fgets(line, LINE_LENGTH, fptr) != NULL) {
    if (line[0] == COMMENT_CHAR || isspace(line[0])) {
      while (fgets(line, LINE_LENGTH, fptr) != NULL) {
        if (line[0] != COMMENT_CHAR && !isspace(line[0]))
          break;
      }
      if (line[0] == COMMENT_CHAR || isspace(line[0]))
	break;
    }

    sscanf(line, "%s %f %f %f %f %f %s\n", fullname,
           &dis, &rad, &mass, &orb, &rot, texture);
    parseNames(fullname, names);
    strcpy(texpath, TEXTURE_DIR);
    strcat(texpath, texture);

    dis *= AU;
    
    parent = root;
    if (names[0] == NULL) {
      cerr << "Invalid File Format: No Name" << endl;
      exit(-1);
    }
    else if (root == NULL) {
      root   = new Body(names[0], dis, rad, mass, orb, rot, texpath);
      parent = root;
    }
    else if (!strcmp(root->getName(), names[0])) {
      name_cnt = 1;
      while (names[name_cnt] != NULL) {
	cur_par = parent->getChildByName(names[name_cnt]);
        if (cur_par >= 0)
  	  parent = parent->getChild(cur_par);
        else if (names[name_cnt + 1] == NULL) {
	  child = new Body(names[name_cnt], dis, rad, mass, orb, rot, texpath);
	  parent->addChild(child);
	  child->setParent(parent);
	} else {
	  cout << endl << line << endl;
	  cerr << "Invalid File Format: Bad Name Sequence" << endl;
	  exit(-1);
	}
	name_cnt++;
      }
    }
    else {
      cerr << "Invalid File Format: Bad Root" << endl;
      exit(-1);
    }
  }

  return root;
}

int parseNames (char *fullname, char *names[MAX_TREE_DEPTH]) {
  char *ptr;
  int len = 0, num = 0;

  ptr = strtok(fullname, BODY_DELIM);
  do {
    len = strlen(ptr);
    names[num] = new char[len + 1];
    strncpy(names[num], ptr, len);
    names[num][len] = 0;
    ptr = strtok(NULL, BODY_DELIM);
    num++;
  } while (ptr != NULL);
  names[num] = NULL;

  return num;
}

// checkArgs() checks the command line arguments and assigns all variables
// not listed on command to zero.
int checkArgs(int myargc, char **myargv) {
  int i;
  
  for (i = 1; i < myargc; i += 2) {

    if (i + 1 == myargc) return 0;

    if (!strcmp(myargv[i], "-file")) {
      strcpy(planet_file, myargv[i+1]);
      fileset = 1;
    }
    else if (!strcmp(myargv[i], "-dis"))
      disExp = atof(myargv[i+1]);
    else if (!strcmp(myargv[i], "-rad"))
      radExp = atof(myargv[i+1]);
    else if (!strcmp(myargv[i], "-orb"))
      orbExp = atof(myargv[i+1]);
    else if (!strcmp(myargv[i], "-mass"))
      masExp = atof(myargv[i+1]);
    else if (!strcmp(myargv[i], "-rot"))
      rotExp = atof(myargv[i+1]);
    else if (!strcmp(myargv[i], "-help"))
      return 0;
    else if (!strcmp(myargv[i], "-h"))
      return 0;
    else
      return 0;
  }
  return 1;
}

void printUsage(char *progname) {
  cout << "Usage - " << endl;
  exit(1);
}