#include "system.h"
#include "macs.hpp"
#include "gserver.hpp"
#include "netface.hpp"
#include "timing.hpp"
#include "netcfg.hpp"
#include "id.hpp"
#include "jwindow.hpp"
#include "input.hpp"

extern base_memory_struct *base;
extern net_socket *comm_sock,*game_sock;
extern int registered;

extern net_protocol *prot;
extern join_struct *join_array;
extern window_manager *eh;
extern char *symbol_str(char *name);
extern void service_net_request();

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <signal.h>


game_server::game_server()
{
  player_list=NULL;
  waiting_server_input=1;
  reload_state=0;
}

int game_server::total_players()
{
  player_client *fl=player_list;
  int total=1;
  for (;fl;fl=fl->next) total++;
  return total;
}

void game_server::game_start_wait()
{
  int last_count=0;
  jwindow *stat=NULL;
  event ev;
  int abort=0;
  while (!abort && total_players()<main_net_cfg->min_players)
  {
    if (last_count!=total_players())
    {
      if (stat) eh->close_window(stat);
      char msg[100];
      sprintf(msg,symbol_str("min_wait"),main_net_cfg->min_players-total_players());
      stat=eh->new_window(100,50,-1,-1,new info_field(WINDOW_FRAME_LEFT,WINDOW_FRAME_TOP,ID_NULL,msg,
				       new button(WINDOW_FRAME_LEFT,WINDOW_FRAME_TOP+eh->font()->height()*2,
						  ID_CANCEL,symbol_str("cancel_button"),NULL)  ));
      eh->flush_screen();
      last_count=total_players();
    }

    if (eh->event_waiting())
    {
      do { eh->get_event(ev); }  while (ev.type==EV_MOUSE_MOVE && eh->event_waiting()); 
      eh->flush_screen();
      if (ev.type==EV_MESSAGE && ev.message.id==ID_CANCEL)
        abort=1;
    }
    
    service_net_request();
  }
  if (stat)
  {
    eh->close_window(stat);
    eh->flush_screen();  
  }
}
  
game_server::player_client::~player_client()
{
  delete comm;
  delete data_address;
}

void game_server::check_collection_complete()
{
  player_client *c;
  int got_all=waiting_server_input==0;
  int add_deletes=0;
  for (c=player_list;c && got_all;c=c->next)
  {
    if (c->delete_me())
      add_deletes=1;
    else if (c->has_joined() && c->wait_input())
      got_all=0;
  }

  if (add_deletes)
  {
    player_client *last=NULL;
    for (c=player_list;c;)
    {
      if (c->delete_me())
      {
	base->packet.write_byte(SCMD_DELETE_CLIENT);
	base->packet.write_byte(c->client_id);
	if (c->wait_reload())
	{
	  c->set_wait_reload(0);
	  check_reload_wait();
	}

	if (last) last->next=c->next;
	else player_list=c->next;
	player_client *d=c;
	c=c->next;
	delete d;
      } else 
      {
	last=c;
	c=c->next;
      }
    }
  }

  if (got_all)    // see if we have input from everyone, if so send it out
  {   
    base->packet.calc_checksum();

    for (c=player_list;c;c=c->next)      // setup for next time, wait for all the input
    {
      if (c->has_joined())
      {
	c->set_wait_input(1);      
	game_sock->write(base->packet.data,base->packet.packet_size()+base->packet.packet_prefix_size(),c->data_address);

      }
    }

    base->input_state=INPUT_PROCESSING; // tell engine to start processing
    game_sock->read_unselectable();    // don't listen to this socket until we are prepared to read next tick's game data
    waiting_server_input=1;
  }  
}

void game_server::add_engine_input()
{
  waiting_server_input=0;
  base->input_state=INPUT_COLLECTING;
  base->packet.set_tick_received(base->current_tick);
  game_sock->read_selectable();    // we can listen for game data now that we have server input
  check_collection_complete();
}

void game_server::add_client_input(char *buf, int size, player_client *c)
{
  if (c->wait_input())  // don't add if we already have it
  {
    base->packet.add_to_packet(buf,size);
    c->set_wait_input(0);
    check_collection_complete();
  }
}

void game_server::check_reload_wait()
{
  player_client *d=player_list;
  for (;d;d=d->next) 
   if (d->wait_reload()) return ;    // we are still waiting for someone to reload the game 
  base->wait_reload=0;
}

int game_server::process_client_command(player_client *c)
{
  uchar cmd;
  if (c->comm->read(&cmd,1)!=1) return 0;
  switch (cmd)
  {
    case CLCMD_REQUEST_RESEND :
    {
      uchar tick;
      if (c->comm->read(&tick,1)!=1) return 0;

      fprintf(stderr,"request for resend tick %d (game cur=%d, pack=%d, last=%d)\n",
	      tick,base->current_tick,base->packet.tick_received(),base->last_packet.tick_received());

      if (tick==base->last_packet.tick_received())
      {
	net_packet *pack=&base->last_packet;      
	game_sock->write(pack->data,pack->packet_size()+pack->packet_prefix_size(),c->data_address); 
      }
      return 1;
    } break;
    case CLCMD_RELOAD_START :
    {
      if (reload_state)   // already in reload state, notify client ok to start reloading
      {
        if (c->comm->write(&cmd,1)!=1) 
	  c->set_delete_me(1);
      } else c->set_need_reload_start_ok(1);
      return 1;
    } break;

    case CLCMD_RELOAD_END :
    {
      c->set_wait_reload(0);
      return 1;
    } break;
    case CLCMD_UNJOIN :
    {
      c->comm->write(&cmd,1);        // don't care weither this works or not
      c->set_delete_me(1);
      if (base->input_state==INPUT_COLLECTING)
        check_collection_complete();
    } break;
  }
  return 0;
}


int game_server::process_net()
{
  int ret=0;
  /**************************       Any game data waiting?       **************************/
  if ((base->input_state==INPUT_COLLECTING ||
       base->input_state==INPUT_RELOAD)
       && game_sock->ready_to_read())
  {
    net_packet tmp;
    net_packet *use=&tmp;
    net_address *from;
    int bytes_received=game_sock->read(use->data,PACKET_MAX_SIZE,&from);

    if (from && bytes_received)
    {
      // make sure we got a complete packet and the packet was not a previous game tick packet
      if (bytes_received==use->packet_size()+use->packet_prefix_size())
      {
	unsigned short rec_crc=use->get_checksum();
	if (rec_crc==use->calc_checksum())
	{
	  player_client *f=player_list,*found=NULL;
	  for (;!found &&f;f=f->next)
	  if (f->has_joined() && from->equal(f->data_address))
	    found=f;
	  if (found)
	  {
	    if (base->current_tick==use->tick_received())  	    
	    {
	      if (prot->debug_level(net_protocol::DB_MINOR_EVENT))
	        fprintf(stderr,"(got data from %d)",found->client_id);

//	      fprintf(stderr,"(got packet %d)\n",use->tick_received()); 
//	      { time_marker now,start; while (now.diff_time(&start)<5.0) now.get_time(); } 

	      if (base->input_state!=INPUT_RELOAD)
	        add_client_input((char *)use->packet_data(),use->packet_size(),found);

	    }
	    else if (use->tick_received()==base->last_packet.tick_received())
	    {
	      if (prot->debug_level(net_protocol::DB_IMPORTANT_EVENT))
	        fprintf(stderr,"(sending old %d)\n",use->tick_received());	      

	      // if they are sending stale data we need to send them the last packet so they can catchup
	      net_packet *pack=&base->last_packet;      
	      game_sock->write(pack->data,pack->packet_size()+pack->packet_prefix_size(),found->data_address); 

	    } else if (prot->debug_level(net_protocol::DB_MAJOR_EVENT))
	      fprintf(stderr,"received stale packet (got %d, expected %d)\n",use->tick_received(),base->current_tick);

	  } else
	  {
	    if (prot->debug_level(net_protocol::DB_MAJOR_EVENT))
	    {
	      fprintf(stderr,"received data from unknown client\n");
	      printf("from address "); from->print();
	      printf(" first addr "); player_list->data_address->print(); printf("\n");
	    }
	  }

	} else fprintf(stderr,"received packet with bad checksum\n");
      } else fprintf(stderr,"received incomplete packet\n");
    } else if (!from)
      fprintf(stderr,"received data and no from\n");
    else if (!bytes_received)
      fprintf(stderr,"received 0 byte data\n");
    ret=1;
    if (from) delete from;

  }


  /**************************       Any client with commands?       **************************/
  player_client *c;
  for (c=player_list;c;c=c->next)  
    if (c->comm->error() || (c->comm->ready_to_read() && !process_client_command(c)))
    {
      c->set_delete_me(1);
      check_collection_complete();
    }
    else ret=1;
  
  return 1;
}


int game_server::input_missing()
{

  return 1;
}



int game_server::end_reload(int disconnect)  // notify evryone you've reloaded the level (at server request)
{  
  player_client *c=player_list;
  prot->select(0);

  for (;c;c=c->next) 
    if (!c->delete_me() && c->wait_reload()) 
    {
      if (disconnect)
        c->set_delete_me(1);
      else return 0;
    }

  for (c=player_list;c;c=c->next)   
    c->set_has_joined(1);
  reload_state=0;
  
  return 1;
}

int game_server::start_reload()
{
  player_client *c=player_list;
  reload_state=1;
  prot->select(0);

  for (;c;c=c->next) 
  {
    if (!c->delete_me() && c->need_reload_start_ok())    // if the client is already waiting for reload state to start, send ok
    {
      uchar cmd=CLCMD_RELOAD_START;
      if (c->comm->write(&cmd,1)!=1) { c->set_delete_me(1); }
      c->set_need_reload_start_ok(0);
    }
    c->set_wait_reload(1);
  }
  return 1;
}


int game_server::isa_client(int client_id)
{
  player_client *c=player_list;
  if (!client_id) return 1;
  for (;c;c=c->next) if (c->client_id==client_id) return 1;
  return 0;   
}

int game_server::add_client(int type, net_socket *sock, net_address *from)
{
  if (type==CLIENT_ABUSE)
  {

    if (total_players()>=main_net_cfg->max_players)
    {
      uchar too_many=2;
      sock->write(&too_many,1);
      return 0;
    }

    uchar reg=registered ? 1 : 0;
    if (sock->write(&reg,1)!=1)  
      return 0;


    ushort our_port=lstl(main_net_cfg->port+1),cport;
    char name[256];
    uchar len;
    short nkills=lstl(main_net_cfg->kills);
    
    if (sock->read(&len,1)!=1 ||
	sock->read(name,len)!=len ||
	sock->read(&cport,2)!=2 ||
        sock->write(&our_port,2)!=2 ||
	sock->write(&nkills,2)!=2)
      return 0;

    cport=lstl(cport);

    
    int f=-1,i;
    for (i=0;f==-1 && i<MAX_JOINERS;i++)
      if (!isa_client(i))
      {
        f=i;

	join_struct *j=base->join_list;
	for (;j;j=j->next)
	  if (j->client_id==i)
	    f=-1;
      }


    if (f==-1) return 0;    


    from->set_port(cport);

    ushort client_id=lstl(f);
    if (sock->write(&client_id,2)!=2) { return 0; }
    client_id=f;

    join_array[client_id].next=base->join_list;
    base->join_list=&join_array[client_id];
    join_array[client_id].client_id=client_id;
    strcpy(join_array[client_id].name,name);   
    player_list=new player_client(f,sock,from,player_list);

    return 1;
  } else return 0;
}

int game_server::kill_slackers()
{
  player_client *c=player_list;
  for (;c;c=c->next)
    if (c->wait_input())
      c->set_delete_me(1);
  check_collection_complete();  
  return 1;
}

int game_server::quit()
{
  player_client *c=player_list;  
  while (c)
  {
    player_client *d=c;
    c=c->next;
    delete c;
  }
  player_list=NULL;
  return 1;
}


game_server::~game_server()
{
  quit();
}

