#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


#include "servership.h"
#include "serverdata.h"
#include "netconfig.h"
#include "scorevals.h"
#include "sounddefs.h"
#include "mainplayerthread.h"
#include "thread.h"
#include "timeutils.h"
#include "sizes.h"
#include "collision.h"


void *StartGame(void *threadid);
void InitServerGame();
void DeletePlayer(int playernum);
void CheckForCollisions();
int CheckForCollision(struct Point point1, float size1,
                      struct Point point2, float size2);
int ShipHit(int playernum, int objnum);
void CreateRandomRock();
void CreateExplosionRandomRock(int objnum);
void CreateExplosion(int objnum, int type);
void AddSound(int playernum, struct Point *SoundPos, char type);
void AddSoundAll(struct Point *SoundPos, char type);
int BounceObjects(ServerObject *obj1, ServerObject *obj2);
float CalcDistance(struct Point *point1, struct Point *point2);
void CreateRandomPosition(struct Point *Position);


SoundStruct Sounds[MAX_PLAYERS][MAX_SOUNDS];
ServerObject *ServerObjects[MAX_OBJECTS];
ServerShip *ServerShips[MAX_PLAYERS];
float MaxRockSize=0.7;
float MinRockSize=0.15;
int NumAsteroids;
float TotalAsteroidSize;
int ServerGameRunning;
float BotRatio;


// Main function to run the game
void *StartGame(void *threadid)
{
#ifdef CYGWIN
  int sleepcount=0;
#endif
  int i, j=0, k=0, l=0;
  int update, keyupdate;
  int secondupdate;
  int threadnum, RunTime;
  unsigned long TimeElapsed, OldTimeElapsed=0;
  int TimeCounter=0;
  int SleepTimesElapsed;
  int AsteroidTime=NewAsteroidTime;
  int MakeAsteroidTime=NewAsteroidTime;
  int SleepTime;
  float Size;
  int GameWaitTime=500;
  char BotName[80];
  struct Point Position, Velocity, Rotation;

  ServerGameRunning=1;

  threadnum=(int)threadid;

  pthread_mutex_lock(&ServerLock);

  NumAsteroids=0;
  TotalAsteroidSize=0.0;

  BotRatio=StartBotRatio;

  for (i=0; i<MAX_PLAYERS; i++) {
    ServerShips[i]=NULL;
  }

  for (i=0; i<MAX_OBJECTS; i++) {
    ServerObjects[i]=NULL;
  }

  if (MaxPlayers==1) {
    ServerGameType=SINGLE_PLAYER;
  }

#ifndef CYGWIN
  InitTime();
#endif

  SleepTime=2000*GameSleep;

  update=FALSE;
  keyupdate=FALSE;
  secondupdate=FALSE;

  InitServerGame();

  for (i=0; i<NumBots; i++) {
    for (j=0; j<MAX_PLAYERS; j++) {
      if (!ServerShips[j]) {
        sprintf(BotName, "Bot %d", i+1);
        CreateRandomPosition(&Position);
        Rotation.x=0.0;
        Rotation.y=0.0;
        Rotation.z=0.0;
        Velocity.x=0.0;
        Velocity.y=0.0;
        Velocity.z=0.0;
        ServerShips[j]=new ServerShip(Position, Velocity, Rotation, SHIP_TYPE,
                                      ShipSize, 1.0, j, BotName, 1);
        break;
      }
    }
  }

  while(!StopServer) {
    // Allow other threads to access shared data while we're sleeping
#ifdef DEBUG
    printf("Server Game Thread Sleeping\n");
#endif
    pthread_mutex_unlock(&ServerLock);

#ifdef CYGWIN
    if (sleepcount != 3) {
      usleep(SleepTime);
      sleepcount++;
    } else {
      sleepcount=0;
    }
#else
    TimeElapsed=GetTimeElapsed();
    if (TimeElapsed < OldTimeElapsed) {
      OldTimeElapsed=TimeElapsed;
    }
    /*
      We need this because usleep is somewhat inaccurate and we may be
      slighly under or over the necessary sleep time
    */
    SleepTimesElapsed=(int)(TimeElapsed/SleepTime)-
                      (int)(OldTimeElapsed/SleepTime);

    if (SleepTimesElapsed < 0 || SleepTimesElapsed > 10) {
      SleepTimesElapsed=0;
    }

    TimeCounter+=1-SleepTimesElapsed;

    if (TimeCounter>0) {
      usleep(SleepTime*TimeCounter);
    }
    OldTimeElapsed=TimeElapsed;

#endif
    pthread_mutex_lock(&ServerLock);
#ifdef DEBUG
    printf("Server Game Thread Awake\n");
#endif

    if (NumConnectedPlayers && ServerGameRunning) {
      // Update the game at a constant rate no matter how long we sleep
      k+=GameSleep;
      if (k >= 10) {
        update=TRUE;
        k-=10;
      } else {
        update=FALSE;
      }

      // Updates for key input
      l+=GameSleep;
      if (l >= 20) {
        keyupdate=TRUE;
        l-=20;
      } else {
        keyupdate=FALSE;
      }

      // Updates that we have to do once a second
      j+=GameSleep;
      if (j >= 1000) {
        secondupdate=TRUE;
        j-=1000;
      } else {
        secondupdate=FALSE;
      }

      MakeAsteroidTime-=GameSleep;
      if (MakeAsteroidTime <= 0) {
        CreateRandomRock();
        MakeAsteroidTime=AsteroidTime;
      }

      // Age all the objects
      if (update) {
        for (i=0; i<MAX_OBJECTS; i++) {
          if (ServerObjects[i]) {
            ServerObjects[i]->UpdatePosition();
            ServerObjects[i]->UpdateRotation();
            if (ServerObjects[i]->AliveTime > 0) {
              if (ServerObjects[i]->ObjectType==NUKE_EXPLOSION_TYPE) {
                ServerObjects[i]->ObjectSize+=NukeExplosionGrowthSpeed;
              }
              ServerObjects[i]->AliveTime--;
              if (ServerObjects[i]->AliveTime==0) {
                delete ServerObjects[i];
                ServerObjects[i]=NULL;
              }
            }
          }
        }
        for (i=0; i<MAX_PLAYERS; i++) {
          if (ServerShips[i]) {
            ServerShips[i]->DoUpdates();
            if (ServerGameType==DEATHMATCH) {
              if (ServerShips[i]->Score>=MaxKills) {
                ServerGameRunning=0;
                continue;
              }
            }
          }
        }
        CheckForCollisions();
      }

      if (secondupdate) {
        if (ServerGameType==SINGLE_PLAYER) {
          AsteroidTime-=AsteroidTimeSpeedup;
          if (AsteroidTime < MinAsteroidTime) {
            AsteroidTime=MinAsteroidTime;
          }
          BotRatio+=BotRatioIncrease;
          if (BotRatio > MaxBotRatio) {
            BotRatio=MaxBotRatio;
          }
        }
      }
    }
    if (NumConnectedPlayers && !ServerGameRunning) {
      GameWaitTime--;
      if (!GameWaitTime) {
        GameWaitTime=500;
        ServerGameRunning=1;
        InitServerGame();
      }
    }
  }
  for (i=0; i<MAX_OBJECTS; i++) {
    if (ServerObjects[i]) {
      delete(ServerObjects[i]);
      ServerObjects[i]=NULL;
    }
  }

  for (i=0; i<MAX_PLAYERS; i++) {
    if (ServerShips[i]) {
      delete(ServerShips[i]);
      ServerShips[i]=NULL;
    }
  }

  pthread_mutex_unlock(&ServerLock);

  pthread_exit(NULL);
}


void InitServerGame()
{
  int i;

  for (i=0; i<MAX_OBJECTS; i++) {
    if (ServerObjects[i]) {
      delete(ServerObjects[i]);
      ServerObjects[i]=NULL;
    }
  }

  for (i=0; i<MAX_PLAYERS; i++) {
    if (ServerShips[i]) {
      ServerShips[i]->InitPlayer();
    }
  }

  for (i=0; i<StartAsteroids; i++) {
    CreateRandomRock();
  }
}


void DeletePlayer(int playernum)
{
  delete ServerShips[playernum];
  ServerShips[playernum]=NULL;
}


void CheckForCollisions()
{
  int i, j, rc;

  // Check for Object to Object Collisions
  for (i=0; i<MAX_OBJECTS; i++) {
    if (ServerObjects[i]) {
      for (j=i+1; j<MAX_OBJECTS; j++) {
        if (ServerObjects[j]) {
          if (CheckForCollision(ServerObjects[i]->Position,
                                ServerObjects[i]->ObjectSize,
                                ServerObjects[j]->Position,
                                ServerObjects[j]->ObjectSize)) {
            if (ServerObjects[i]->ObjectType <= ServerObjects[j]->ObjectType) {
              if (ObjectToObjectCollisionFunctions[ServerObjects[i]->ObjectType][ServerObjects[j]->ObjectType]) {
                rc=ObjectToObjectCollisionFunctions[ServerObjects[i]->ObjectType][ServerObjects[j]->ObjectType](i, j);
                if (rc==1 || rc==3) {
                  break;
                }
              }
            } else {
              if (ObjectToObjectCollisionFunctions[ServerObjects[j]->ObjectType][ServerObjects[i]->ObjectType]) {
                rc=ObjectToObjectCollisionFunctions[ServerObjects[j]->ObjectType][ServerObjects[i]->ObjectType](j, i);
                if (rc==2 || rc==3) {
                  break;
                }
              }
            }
          }
        }
      }
    }
  }

  // Check for Ship to Object Collisions
  for (i=0; i<MAX_PLAYERS; i++) {
    if (ServerShips[i]) {
      if (ServerShips[i]->AliveTime==-1 && !ServerShips[i]->WarpTime) {
        for (j=0; j<MAX_OBJECTS; j++) {
          if (ServerObjects[j]) {
            if (CheckForCollision(ServerObjects[j]->Position,
                                  ServerObjects[j]->ObjectSize,
                                  ServerShips[i]->Position,
                                  ServerShips[i]->ObjectSize)) {
              if (ObjectToShipCollisionFunctions[ServerObjects[j]->ObjectType]) {
                if (ObjectToShipCollisionFunctions[ServerObjects[j]->ObjectType](j, i)) {
                  break;
                }
              }
            }
          }
        }
      }
    }
  }

  // Check for Ship to Ship Collisions
  for (i=0; i<MAX_PLAYERS; i++) {
    if (ServerShips[i]) {
      if (ServerShips[i]->AliveTime==-1 && !ServerShips[i]->WarpTime) {
        for (j=i+1; j<MAX_PLAYERS; j++) {
          if (ServerShips[j]) {
            if (ServerShips[j]->AliveTime==-1 && !ServerShips[j]->WarpTime) {
              if (CheckForCollision(ServerShips[i]->Position, ServerShips[i]->ObjectSize,
                                    ServerShips[j]->Position, ServerShips[j]->ObjectSize)) {
                if (ShipToShipCollision(i, j)) {
                  break;
                }
              }
            }
          }
        }
      }
    }
  }
}


// Check if the two spheres overlap
int CheckForCollision(struct Point point1, float size1,
                      struct Point point2, float size2)
{
  if (CalcDistance(&point1, &point2) < size1+size2) {
    return(1);
  } else {
    return(0);
  }
}


void CreateRandomRock()
{
  int i;
  struct Point Position, Rotation, Velocity;
  float Size;

  if (NumAsteroids==MAX_ASTEROIDS || TotalAsteroidSize > MaxTotalAsteroidSize) {
    return;
  }

  for (i=0; i<MAX_OBJECTS; i++) {
    if (!ServerObjects[i]) {
      CreateRandomPosition(&Position);
      Rotation.x=float(rand()%50-25)/50.0;
      Rotation.y=float(rand()%50-25)/50.0;
      Rotation.z=float(rand()%50-25)/50.0;
      Velocity.x=float(rand()%50-25)/6000.0;
      Velocity.y=float(rand()%50-25)/6000.0;
      Velocity.z=float(rand()%50-25)/6000.0;
      Size=((float(rand()%50+1)/50)*(MaxRockSize-MinRockSize))+MinRockSize;
      ServerObjects[i]=new ServerObject(Position, Velocity, Rotation, ASTEROID_TYPE, Size, Size*Size*Size*400.0);
      break;
    }
  }
}


void CreateExplosionRandomRock(int objnum)
{
  int i, j, numRocks;
  struct Point Position, Rotation, Velocity, Momentum;
  float Size;

  numRocks=rand()%2+2;
  Momentum.x=0.0;
  Momentum.y=0.0;
  Momentum.z=0.0;

  for (j=0; j<numRocks; j++) {
    if (NumAsteroids==MAX_ASTEROIDS) {
      return;
    }
    for (i=0; i<MAX_OBJECTS; i++) {
      if (!ServerObjects[i]) {
        Position.x=ServerObjects[objnum]->Position.x;
        Position.y=ServerObjects[objnum]->Position.y;
        Position.z=ServerObjects[objnum]->Position.z;
        Rotation.x=float(rand()%50-25)/50.0;
        Rotation.y=float(rand()%50-25)/50.0;
        Rotation.z=float(rand()%50-25)/50.0;
        Velocity.x=float(rand()%50-25)/6000.0+ServerObjects[objnum]->Velocity.x;
        Velocity.y=float(rand()%50-25)/6000.0+ServerObjects[objnum]->Velocity.y;
        Velocity.z=float(rand()%50-25)/6000.0+ServerObjects[objnum]->Velocity.z;
        Size=(ServerObjects[objnum]->ObjectSize*0.8*
              (1.0/sqrt((float)numRocks))*
              (1.0-(float)(rand()%20)/50.0));
        Velocity.x-=Momentum.x/Size;
        Velocity.y-=Momentum.y/Size;
        Velocity.z-=Momentum.z/Size;
        Momentum.x+=(Velocity.x-ServerObjects[objnum]->Velocity.x)*Size;
        Momentum.y+=(Velocity.y-ServerObjects[objnum]->Velocity.y)*Size;
        Momentum.z+=(Velocity.z-ServerObjects[objnum]->Velocity.z)*Size;

        ServerObjects[i]=new ServerObject(Position, Velocity, Rotation,
                                          ASTEROID_TYPE, Size, Size*Size*Size*400.0);
        break;
      }
    }
  }
}


void CreateExplosion(int objnum, int type)
{
  int i;
  struct Point Rotation, Velocity;

  Rotation.x=0.0;
  Rotation.y=0.0;
  Rotation.z=0.0;
  Velocity.x=0.0;
  Velocity.y=0.0;
  Velocity.z=0.0;

  for (i=0; i<MAX_OBJECTS; i++) {
    if (!ServerObjects[i]) {
      if (type==0) {
        ServerObjects[i]=new ServerObject(ServerObjects[objnum]->Position,
                                          Velocity, Rotation,
                                          EXPLOSION_TYPE, 1.0, 0.0, 1);
      } else if (type==1) {
        ServerObjects[i]=new ServerObject(ServerShips[objnum]->Position,
                                          Velocity, Rotation,
                                          BIG_EXPLOSION_TYPE, 1.0, 0.0, 1);
      }
      break;
    }
  }
}


void AddSound(int playernum, struct Point *SoundPos, char type)
{
  int i;

  if (ServerShips[playernum]) {
    if (!ServerShips[playernum]->Bot) {
      for (i=0; i<NUM_SOUNDS; i++) {
        if (Sounds[playernum][i].type==-1) {
          Sounds[playernum][i].Position.x=SoundPos->x;
          Sounds[playernum][i].Position.y=SoundPos->y;
          Sounds[playernum][i].Position.z=SoundPos->z;
          Sounds[playernum][i].type=type;
          break;
        }
      }
    }
  }
}


void AddSoundAll(struct Point *SoundPos, char type)
{
  int i;

  for (i=0; i<MAX_PLAYERS; i++) {
    AddSound(i, SoundPos, type);
  }
}


int BounceObjects(ServerObject *obj1, ServerObject *obj2)
{
  struct Point npos1, npos2;
  struct Point RelPosition, RelVelocity, ForcePoint, Force;
  float dist1, dist2, dist3;
  float Angle;
  float ForceAngle;

  npos1.x=obj1->Position.x+obj1->Velocity.x/100.0;
  npos1.y=obj1->Position.y+obj1->Velocity.y/100.0;
  npos1.z=obj1->Position.z+obj1->Velocity.z/100.0;
  npos2.x=obj2->Position.x+obj2->Velocity.x/100.0;
  npos2.y=obj2->Position.y+obj2->Velocity.y/100.0;
  npos2.z=obj2->Position.z+obj2->Velocity.z/100.0;

  dist1=CalcDistance(&obj1->Position, &obj2->Position);
  dist2=CalcDistance(&npos1, &npos2);

  if (dist2 >= dist1) {
    return(0);
  }

  RelPosition.x=obj1->Position.x-obj2->Position.x;
  RelPosition.y=obj1->Position.y-obj2->Position.y;
  RelPosition.z=obj1->Position.z-obj2->Position.z;
  RelVelocity.x=obj1->Velocity.x-obj2->Velocity.x;
  RelVelocity.y=obj1->Velocity.y-obj2->Velocity.y;
  RelVelocity.z=obj1->Velocity.z-obj2->Velocity.z;
  ForcePoint.x=RelVelocity.x+RelPosition.x;
  ForcePoint.y=RelVelocity.y+RelPosition.y;
  ForcePoint.z=RelVelocity.z+RelPosition.z;
  dist1=sqrt(RelPosition.x*RelPosition.x+RelPosition.y*RelPosition.y+RelPosition.z*RelPosition.z);
  dist2=sqrt(RelVelocity.x*RelVelocity.x+RelVelocity.y*RelVelocity.y+RelVelocity.z*RelVelocity.z);
  dist3=sqrt(ForcePoint.x*ForcePoint.x+ForcePoint.y*ForcePoint.y+ForcePoint.z*ForcePoint.z);

  ForceAngle=(dist1*dist1+dist2*dist2-dist3*dist3)/(2*dist1*dist2);
  Force.x=(RelPosition.x/dist1)*ForceAngle*dist2;
  Force.y=(RelPosition.y/dist1)*ForceAngle*dist2;
  Force.z=(RelPosition.z/dist1)*ForceAngle*dist2;

  obj1->Velocity.x+=(Force.x*2.0)/(1.0+(obj1->ObjectMass/obj2->ObjectMass));
  obj1->Velocity.y+=(Force.y*2.0)/(1.0+(obj1->ObjectMass/obj2->ObjectMass));
  obj1->Velocity.z+=(Force.z*2.0)/(1.0+(obj1->ObjectMass/obj2->ObjectMass));
  obj2->Velocity.x-=(Force.x*2.0)/(1.0+(obj2->ObjectMass/obj1->ObjectMass));
  obj2->Velocity.y-=(Force.y*2.0)/(1.0+(obj2->ObjectMass/obj1->ObjectMass));
  obj2->Velocity.z-=(Force.z*2.0)/(1.0+(obj2->ObjectMass/obj1->ObjectMass));

  return((int)(sqrt(Force.x*Force.x+Force.y*Force.y+Force.z*Force.z)*100.0*(float)CollisionDamage));
}


float CalcDistance(struct Point *point1, struct Point *point2)
{
  float xdist, ydist, zdist;

  if (point1->x > point2->x) {
    xdist=point1->x-point2->x;
  } else {
    xdist=point2->x-point1->x;
  }
  if (xdist > 2*ServerWorldWidth-xdist) {
    xdist=2*ServerWorldWidth-xdist;
  }
  if (point1->y > point2->y) {
    ydist=point1->y-point2->y;
  } else {
    ydist=point2->y-point1->y;
  }
  if (ydist > 2*ServerWorldWidth-ydist) {
    ydist=2*ServerWorldWidth-ydist;
  }
  if (point1->z > point2->z) {
    zdist=point1->z-point2->z;
  } else {
    zdist=point2->z-point1->z;
  }
  if (zdist > 2*ServerWorldWidth-zdist) {
    zdist=2*ServerWorldWidth-zdist;
  }

  return(sqrt(xdist*xdist+ydist*ydist+zdist*zdist));
}


void CreateRandomPosition(struct Point *Position)
{
  int i, j;
  int found;

  for (i=0; i<20; i++) {
    Position->x=(float(rand()%50-25)/25.0)*ServerWorldWidth;
    Position->y=(float(rand()%50-25)/25.0)*ServerWorldWidth;
    Position->z=(float(rand()%50-25)/25.0)*ServerWorldWidth;

    found=1;
    for (j=0; j<MAX_OBJECTS; j++) {
      if (ServerObjects[j]) {
        if (CheckForCollision(ServerObjects[j]->Position,
                              ServerObjects[j]->ObjectSize,
                              *Position, 0.3)) {
          found=0;
          break;
        }
      }
    }
    if (!found) {
      continue;
    }
    for (j=0; j<MAX_PLAYERS; j++) {
      if (ServerShips[j] && ServerShips[j]->AliveTime==-1) {
        if (CheckForCollision(ServerShips[j]->Position,
                              ServerShips[j]->ObjectSize,
                              *Position, 1.0)) {
          found=0;
          break;
        }
      }
    }
    if (found) {
      break;
    }
  }
}
