#include "server.hpp"
#include "view.hpp"
#include "dprint.hpp"
#include "jnet.hpp"
#include "level.hpp"
#include "game.hpp"
#include "jrand.hpp"
#include "timing.hpp"
//#include "nfserver.hpp"
//#include "nfclient.hpp"

#ifdef __WATCOMC__
#define getlogin() "DOS user"
#include <dos.h>
#else
#include <unistd.h>
#endif

#include "nfserver.hpp"


//nfs_server *file_server=NULL;

int send_pkt(out_socket *o, packet &pk)
{
//  while (!o->ready_to_write()) 
//    if (file_server) file_server->service_request();
  return o->send(pk);
}



int get_pkt(out_socket *o, packet &pk)
{
//  while (!o->ready_to_read()) 
//    if (file_server) file_server->service_request();
  return o->get(pk);
}




server *game_server;
int start_running=0,sync_check=0;

void server::remove_from_server(view *f)  // should only be called from client side
{
  if (f->connect)
  {
    packet pk;
    uchar cmd=SCMD_QUIT;    // send quit command to server
    pk.write(&cmd,1);
    send_pkt(f->connect,pk);
    delete f->connect;
    f->connect=NULL;
  }
}

server::server(int argc, char **argv)
{
  char name[200];
  int port=20202,no_net=0;
  strcpy(name,getlogin() ? getlogin() : "unknown");
  in=NULL;

  // see if this computer is net capable


  int i;
  // preprocessing stuff before checking for connect to server
  for (i=1;i<argc;i++)
  {
    if (!strcmp(argv[i],"-port"))
    {
      i++;
      if (sscanf(argv[i],"%d",&port)!=1 || port<1 || port>0xffff)
      {
	dprintf("Bad port number, port should be 1..%d\n",0xffff);
	exit(0);
      }
    } else if (!strcmp(argv[i],"-name"))     // name player uses when connecting
    {
      i++;
      strcpy(name,argv[i]);
    }  else if (!strcmp(argv[i],"-nonet"))
    {
      dprintf("Network bypassed, no player will be able to connect\n");
      no_net=1;
    } else if (!strcmp(argv[i],"-sync"))
      sync_check=1;
    
  }

  if (no_net)
    has_net=0;
  else has_net=net_init();

  for (i=1;i<argc;i++)
  {
    if (!strcmp(argv[i],"-net"))
    {
      if (!has_net)
      {
	dprintf("No network detected, load network drivers and try again\n");
	exit(1);
      }
      else
      {
	out_socket *os=NULL;
	i++;
	dprintf("Trying to connect to server %s on port %d\n",argv[i],port);
	if (!os=create_out_socket(argv[i],port))
	{
	  dprintf("%s\n",last_sock_err);
	  dprintf("Make sure server is running...\n");
	  exit(1);
	}
	dprintf("Connected!\n");
	
	join_game(os,name,argv[i]);

      }
    }
  }
 
  if (!player_list)                  // if we are not connecting to a server, become one
  {
    is_server=1;
    if (has_net)
    {
      in=new in_socket(port);
      if (current_sock_err)
      {
	dprintf("%s\n",last_sock_err);
	dprintf("Running single player mode\n");
	has_net=0;
      } //else      
//	file_server=new nfs_server(port+1);
      
    }
    set_local_players(1);
  }
  
}





void server::tick()
{
//  if (file_server)
//    file_server->service_request();
  next_out.reset();          // clear the next packet out..
  check_for_new_players();
  collect_inputs();
}

ulong make_sync_long()
{
  ulong x=0;
  for (view *v=player_list;v;v=v->next)
  {
    x^=v->focus->x;
    x^=v->focus->y;
  }  
  return x^rand_on;
}

int server::process_command(view *f, uchar command, packet &pk)
{
  switch (command)
  {
    case SCMD_QUIT :                          // delete player
    {
      dprintf("Player %d has quit\n",f->player_number);
      return 0;
    } break;

    case SCMD_VIEW_RESIZE :                          // change view area
    {
      ulong view_size[8];          
      if (pk.read((uchar *)view_size,8*4)!=8*4)
      return 0;
      else
      {
	f->resize_view(lltl(view_size[0]),lltl(view_size[1]),lltl(view_size[2]),lltl(view_size[3]));
	f->pan_x=lltl(view_size[4]);
	f->pan_y=lltl(view_size[5]);
	f->shift_down=lltl(view_size[6]);
	f->shift_right=lltl(view_size[7]);
	f->suggest.send_view=0;
	if (is_server)                  // if we are a server, tell everybody about this.
	{
	  uchar cmd=SCMD_VIEW_RESIZE;
	  next_out.write((uchar *)&cmd,1);
	  ushort pn=lstl(f->player_number);
	  next_out.write((uchar *)&pn,2);
	  next_out.write((uchar *)view_size,8*4);
	}
      }	      
    } break;

    case SCMD_WEAPON_CHANGE :                          // change weapon
    {
      ulong new_weap;
      if (pk.read((uchar *)&new_weap,4)!=4)
        return 0;
      else
      {
	f->current_weapon=lltl(new_weap);
	f->suggest.send_weapon_change=0;
	if (is_server)                      // if we are a server, tell everybody about this.
	{
	  uchar cmd=SCMD_WEAPON_CHANGE;
	  next_out.write((uchar *)&cmd,1);
	  ushort pn=lstl(f->player_number);
	  next_out.write((uchar *)&pn,2);
	  next_out.write((uchar *)&new_weap,4);
	}
      }	      
    } break;


    case SCMD_SET_INPUT :                        // set the input from this player
    {
      signed char inp[5];
      if (pk.read((uchar *)inp,5)!=5)
        return 0;
      else		
        f->set_input(inp[0],inp[1],inp[2],inp[3],inp[4]);		
    } break;

    case SCMD_ADD_VIEW :
    {
      view *v=add_view(pk);
      if (v)
      {
	for (view *f=player_list;f && f->next;f=f->next);
	if (f) f->next=v;
	else player_list=f;
      }
    } break;
    case SCMD_SYNC :
    {
      ulong x;
      if (pk.read((uchar *)&x,4)!=4)
        return 0;
      else 
      {
	ulong s=make_sync_long();
	if (lltl(x)!=s)
	  printf("Out of sync, %x!=%x\n",lltl(x),s);
	return 1;
      }
    } break;

    default :
      return 0;
  }   
  return 1;
}

void server::add_change_log(view *f, packet &pk, int number)
{
  if (f->view_changed())
  {
    uchar cmd=SCMD_VIEW_RESIZE;
    pk.write(&cmd,1);
    if (number)
    {
      ushort pn=lstl(f->player_number);
      pk.write((uchar *)&pn,2);
      dprintf("Server : %s resized view %d %d %d %d\n",f->name,
	      f->suggest.cx1,f->suggest.cy1,f->suggest.cx2,f->suggest.cy2);
      f->resize_view(f->suggest.cx1,f->suggest.cy1,f->suggest.cx2,f->suggest.cy2);
      f->suggest.send_view=0;
    } else dprintf("sending resize to server\n");
    ulong view_size[8];          	  
    view_size[0]=lltl(f->suggest.cx1);
    view_size[1]=lltl(f->suggest.cy1);
    view_size[2]=lltl(f->suggest.cx2);
    view_size[3]=lltl(f->suggest.cy2);
    view_size[4]=lltl(f->suggest.pan_x);
    view_size[5]=lltl(f->suggest.pan_y);
    view_size[6]=lltl(f->suggest.shift_down);
    view_size[7]=lltl(f->suggest.shift_right);
    pk.write((uchar *)view_size,8*4);
  }

  if (f->weapon_changed())
  {
    uchar cmd=SCMD_WEAPON_CHANGE;
    pk.write(&cmd,1);
    if (number)
    {
      ushort pn=lstl(f->player_number);
      pk.write((uchar *)&pn,2);
      dprintf("Server : %s change weapon to %d\n",f->name,f->suggest.new_weapon);
      f->current_weapon=f->suggest.new_weapon;
      f->suggest.send_weapon_change=0;
    } else dprintf("sending resize to server\n");
    ulong nw=lltl(f->suggest.new_weapon);
    pk.write((uchar *)&nw,4);
  }
}

int server::send_inputs(view *f)
{
  packet pk;
  add_change_log(f,pk,0);
  signed char inp[6];
  inp[0]=SCMD_SET_INPUT;
  inp[1]=f->x_suggestion;
  inp[2]=f->y_suggestion;
  inp[3]=f->b1_suggestion;
  inp[4]=f->b2_suggestion;
  inp[5]=f->b3_suggestion;
  if (pk.write((uchar *)inp,6)!=6)
    return 0;
  if (!send_pkt(f->connect,pk))
    return 0;
  return 1;
}


void server::collect_inputs()
{
  out_socket *collect_server=NULL;
  for (view *f=player_list;f;)
  {
    view *next=f->next;
    if (is_server)
    {
      if (f->connect)
      {
	packet pk;	
	if (get_pkt(f->connect,pk))
	{
	  while (!pk.eop())
	  {
	    uchar cmd;
	    if (pk.read((uchar *)&cmd,1)==1)
	      if (!process_command(f,cmd,pk))
	      { remove_player(f); f=NULL; }
	  }
	} else 
	{
	  remove_player(f);
	  f=NULL;
	}

      } else
      {
        f->get_input();
	add_change_log(f,next_out,1);
      }	  
    }
    else 
    {
      if (f->local_player())
      {
        f->get_input();
	if (f->connect && !send_inputs(f))      
          remove_from_server(f);
	else if (f->connect)
          collect_server=f->connect;  // take note that we should collect the input back from the server
      }
    }
    f=next;  
  }
  
  if (collect_server)
  {
    packet pk;
    if (!get_pkt(collect_server,pk))
    {
      for (view *f=player_list;f;f=f->next)
        if (f->local_player())
	  remove_from_server(f);
    }
		       
    if (!client_do_packet(pk))
      printf("Error occured while processing packet from server\n");
  }

  if (is_server && in)
    distribute_changes();

}


void server::distribute_changes()
{  
  char cmd;

  for (view *f=player_list;f;f=f->next)
  {   
    cmd=SCMD_SET_INPUT;
    next_out.write((uchar *)&cmd,1);
    ushort pn=lstl(f->player_number);
    next_out.write((uchar *)&pn,2);

    signed char inp[5];
    inp[0]=f->x_suggestion;
    inp[1]=f->y_suggestion;
    inp[2]=f->b1_suggestion;
    inp[3]=f->b2_suggestion;
    inp[4]=f->b3_suggestion;
    next_out.write((uchar *)inp,5);
  }

  if (sync_check)
  {
    cmd=SCMD_SYNC;
    ulong x=lltl(make_sync_long());
    next_out.write((uchar *)&cmd,1);  
    next_out.write((uchar *)&x,4);  
  }

  for (f=player_list;f;)  
  {
    view *n=f->next;
    if (!f->local_player() && f->connect)
      if (!send_pkt(f->connect,next_out))
        remove_player(f);
    f=n;
  }

}

void server::check_for_new_players()
{
  if (is_server && has_net)
  {
    out_socket *nd=in->check_for_connect();
    if (nd)
    {
      packet pk;
//      pk.write_long(file_server->get_port());      
      if (!send_pkt(nd,pk))
      {
	printf("error writing to connection\n");
	return ;	
      } 
      
//      while (!file_server->service_request()) milli_wait(1000);

      if (!get_pkt(nd,pk))
      {
	printf("error reading from connection\n");
	return ;	
      } else
      {

	char name[100];
	pk.get_string(name,100);
	printf("Joined by player %s\n",name);
	pk.reset();
	uchar ok=1;
	pk.write((uchar *)&ok,1);      // write ok to join
	send_pkt(nd,pk);

	/**************** Read suggested view size from client ****/
	if (!get_pkt(nd,pk))
	{
	  printf("error reading view info from connection\n");
	  return ;	
	}	
	long cx1,cy1,cx2,cy2;
	if (pk.read((uchar *)&cx1,4)!=4) return ;  cx1=lltl(cx1); 
	if (pk.read((uchar *)&cy1,4)!=4) return ;  cy1=lltl(cy1); 
	if (pk.read((uchar *)&cx2,4)!=4) return ;  cx2=lltl(cx2); 
	if (pk.read((uchar *)&cy2,4)!=4) return ;  cy2=lltl(cy2); 

	/**************** Create the player  *******************/
	for (view *f=player_list;f && f->next;f=f->next);      // find last player, add one for pn
	int i,st=0;
	for (i=0;i<total_objects;i++)
	  if (!strcmp(object_names[i],"START"))
	    st=i;

	game_object *o=create(current_start_type,0,0);
	game_object *start=current_level->get_random_start(320,NULL);
	if (start) { o->x=start->x; o->y=start->y; }
	else { o->x=100; o->y=100; }

	f->next=new view(o,NULL,f->player_number+1);
	o->set_controller(f->next);

	current_level->add_object(o);
	view *v=f->next;
	v->cx1=cx1; 
	v->cy1=cy1;
	v->cx2=cx2;
	v->cy2=cy2;
	v->connect=nd;
	strcpy(v->name,name);


	if (current_level->send(nd))
	{	
	  uchar cmd=SCMD_ADD_VIEW;
	  next_out.write((uchar *)&cmd,1);
	  v->write_packet(next_out);


	  /********** Send all of the views to the player **********/ 
	  pk.reset();
	  ushort tv=0;
	  for (f=player_list;f;f=f->next) tv++;
	  tv=lstl(tv);
	  pk.write((uchar *)&tv,2);
	  if (!send_pkt(nd,pk)) return ;

	  for (f=player_list;f;f=f->next)
	  {
	    pk.reset();
	    f->write_packet(pk);
	    if (!send_pkt(nd,pk)) return ;
	  }

	  pk.reset();
	  ushort r=lstl(rand_on);
	  pk.write((uchar *)&r,2);       // write current random seed
	  pk.write((uchar *)rtable,1024*2);
	  send_pkt(nd,pk);

	}
      }      
    }
  }
}




int server::join_game(out_socket *os, char *name, char *server_name)
{
  char *re="Error occured while reading from server\n";
  packet pk;

  if (!get_pkt(os,pk))                  // read join status packet, 0 means we can't join
  { fputs(re,stderr); exit(0); }
  long nfs_port;
  if (pk.read((uchar *)&nfs_port,4)!=4)
  { fputs(re,stderr); exit(0); }

//  connect_to_nfs_server(server_name,lltl(nfs_port));




  pk.write((uchar *)name,strlen(name)+1);  // send or name and see if it's ok to join in
  if (!send_pkt(os,pk))
  {    
    printf("Unable to write to server\n");
    exit(0);
  }

  if (!get_pkt(os,pk))                  // read join status packet, 0 means we can't join
  { fputs(re,stderr); exit(0); }
  
  uchar stat;
  if (pk.read((uchar *)&stat,1)!=1)
  { fputs(re,stderr); exit(0); }

  if (stat==0)
  {
    printf("Sorry, this server is refusing you (%s)\n",name);
    exit(0);
  }


  if (current_level)
    delete current_level;

  long vs[4]={lltl(320/2-155),lltl(200/2-95),lltl(320/2+155),lltl(200/2+70)};
  pk.write((uchar *)vs,4*4);
  if (!send_pkt(os,pk))   { printf("Unable to write to server\n"); exit(0);  }
  

  current_level=new level(os);
  if (current_level->load_failed())
  {
    printf("Error occured while downloading level\n");
    exit(1);
  }

  if (!get_pkt(os,pk)) 
  {
    printf("Unable to read views from server\n");
    exit(0);
  }
  ushort tv;
  if (pk.read((uchar *)&tv,2)!=2)
  { fputs(re,stderr); exit(0); }
  tv=lstl(tv);
  view *last=NULL;
  for (int i=0;i<tv;i++)
  {
    if (!get_pkt(os,pk)) { fputs(re,stderr); exit(0); }

    view *v=add_view(pk);
    if (v)
    {
      printf("added view %d\n",v->player_number);
      if (last)
        last->next=v;
      else player_list=v;
      last=v;
    } else printf("no view created, why?\n");

  }   

  if (!get_pkt(os,pk)) { fputs(re,stderr); exit(0); }
  if (pk.read((uchar *)&rand_on,2)!=2)    // read the current random seed used by the server.
  { fputs(re,stderr); exit(0); }
  rand_on=lstl(rand_on);
  ushort *rtab=(ushort *)jmalloc(1024*2,"tmp rtab");
  if (!pk.read((uchar *)rtab,1024*2)) { fputs(re,stderr); exit(0); }  // read the rand table

  for (int j=0;j<1024*2;j++)
    if (((uchar *)rtab)[j]!=((uchar *)rtable)[j])
    { printf("rtables differ on byte %d\n",j); exit(0); }
    
  jfree(rtab);



  if (last) 
  {
    last->Drawable=1;
    last->connect=os;
  }

  start_running=1;
  is_server=0;
  return 1;
}






void server::remove_player(view *f)
{
  uchar cmd=SCMD_REMOVE_VIEW;
  next_out.write((uchar *)&cmd,1);
  ushort pn=lstl(f->player_number);
  next_out.write((uchar *)&pn,2);
  if (f==player_list)
    player_list=player_list->next;
  else
  {
    for (view *v=player_list;v && v->next!=f;v=v->next);
    v->next=f->next;
  }

  if (f->connect) delete f->connect;
  f->focus->set_controller(NULL);
  delete f;  
}


/*int server::send_level(net_descriptor *os)
{

	  cmd=SCMD_ADD_VIEW;
	  next_out.write((uchar *)&cmd,1);

    ushort pn=lstl(new_player->player_number);
    next_out.write((uchar *)&pn,2);
    ushort type=lstlli(new_player->focus->otype);
    next_out.write((uchar *)&type,2);
    ulong x=lltl(new_player->focus->x),y=lltl(new_player->focus->y);
    next_out.write((uchar *)&x,4);
    next_out.write((uchar *)&y,4);    
  }*/



#define TOT_VIEW_VARS 32
view *server::add_view(packet &pk)
{
  ulong x[TOT_VIEW_VARS];
  if (!pk.read((uchar *)x,TOT_VIEW_VARS*4)) return NULL;
  for (int i=0;i<TOT_VIEW_VARS;i++) x[i]=lltl(x[i]);
  int skip=0;
  for (view *f=player_list;f;f=f->next)
    if (f->player_number==x[0])
      skip=1;
  
  if (skip)
  {
    pk.advance(total_objects*4);
    char nm[200];
    pk.get_string(nm,100);
    return NULL;
  }
  else
  {
    game_object *o=current_level->number_to_object(x[24]);
    if (!o)
    {
      o=create(x[25],x[26],x[27]);
      current_level->add_object(o);
    }
    view *v=new view(o,NULL,x[0]);
    o->set_controller(v);

    v->cx1=x[1];   v->cy1=x[2];   v->cx2=x[3];  v->cy2=x[4];
    v->lives=x[5];
    v->pan_x=x[6];       v->pan_y=x[7];
    v->no_xleft=x[8];    v->no_xright=x[9];  v->no_ytop=x[10];  v->no_ybottom=x[11];  
    v->last_x=x[12];     v->last_y=x[13];
    v->last_left=x[14];  v->last_right=x[15]; v->last_up=x[16]; v->last_down=x[17]; 
    v->last_b1=x[18];    v->last_b2=x[19];    v->last_b3=x[20]; v->last_hp=x[21];  
    v->last_ammo=x[22];  v->last_type=x[23]; v->visor_time=x[28]; v->current_weapon=x[29];
    v->secrets=x[30];    v->kills=x[31];

    pk.read((uchar *)v->weapons,total_objects*4);
    pk.get_string(v->name,100);


    return v;
  }
}






int server::client_do_packet(packet &pk)
{
  int rp=pk.get_read_position();

  int er=0;
  while (!pk.eop() && !er)
  {
    uchar cmd;
    if (pk.read(&cmd,1)!=1)
      er=1;
    else
    {
      view *f=NULL;
      int fail=0;
      if (cmd!=SCMD_ADD_VIEW && cmd!=SCMD_SYNC)
      {	
	ushort player;
	if (pk.read((uchar *)&player,2)!=2)
	  er=1;
	player=lstl(player);
	for (f=player_list;f && f->player_number!=player;f=f->next);
	if (!f) fail=1;
      }
      if (!fail)      
      {
	if (!process_command(f,cmd,pk))
	  er=1;
      }
      else 
	er=1;
    }
  }
  pk.set_read_position(rp);
  return !er;
}



server::~server()
{
  if (in) delete in;  
}


