/*   Copyright 2005,2006 Paweł Niegowski
*
*    This file is part of Fenrir.
*
*    Fenrir is free software; you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation; either version 2 of the License, or
*    (at your option) any later version.
*
*    Fenrir is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with Fenrir; if not, write to the Free Software
*    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "network.h"
#include "main.h"
#include "login.h"
#include "console.h"
#include "player.h"
#include "play.h"
#include <string.h>
#include <stdio.h>
#include <time.h>
#include "nl.h"
#define SERVER_PORT_TCP 7777
#define SERVER_PORT_UDP 7778
#define CONNECTION_ATTEMPT_TIMEOUT 5

bool network_disabled = false;

NLsocket tcp_client = 0;
NLsocket udp_client = 0;
bool login_packet_sent = false;
char login_packet[35];
bool connected = false;
bool udp_try_connection = false;
bool reconnect = false;

long int verification_code = 0;
NLaddress server_address;
int connection_attempt_count = 1;

int last_connection_attempt = 0;

int time_diff(NLtime *a, NLtime *b)
{
 return (a->seconds - b->seconds) * 1000 + a->mseconds - b->mseconds;
}

void open_sockets()
{
 udp_client = nlOpen(0,NL_UNRELIABLE);
 //a dummy socket - it will be opened on port 1025, forcing the real one to open on 1026 (1025 is blocked on many firewalls)
 NLsocket foo = nlOpen(1025,NL_RELIABLE);
 nlListen(foo);
 tcp_client = nlOpen(0,NL_RELIABLE);
 nlClose(foo);
 if(tcp_client == NL_INVALID) allegro_message("Couldn't open TCP socket.");
 if(udp_client == NL_INVALID) allegro_message("Couldn't open UDP socket.");
}

void network_notify_disconnect()
{
 if(reconnect)
 {
  if(!(server_address.valid))
  { last_connection_attempt = time(0)-CONNECTION_ATTEMPT_TIMEOUT; return; }
  if(time(0) - last_connection_attempt < CONNECTION_ATTEMPT_TIMEOUT)
   return;
  nlSetAddrPort(&server_address,SERVER_PORT_TCP);
  rest(200);
  nlClose(tcp_client);
  nlClose(udp_client);
  open_sockets();
  if(nlConnect(tcp_client,&server_address) == NL_TRUE)
   connected = true;
  nlSetAddrPort(&server_address,SERVER_PORT_UDP);
  static char msg[100];
  sprintf(msg,"Connection attempt #%d... (%c)",connection_attempt_count++,connected?'O':'X');
  login_setmessage(msg,strlen(msg));
  last_connection_attempt = time(0);
  return;
 }
 if(game_state == GAME_PLAY)
  console_add_line("### Disconnected from server.");  else
 if(game_state == GAME_LOGIN)
  login_setmessage("Connection to server lost.",strlen("Connection to server lost."));
 static char c = ID_DISCONNECTING;
 nlWrite(tcp_client,&c,1);
 rest(200);
 nlClose(tcp_client);
 nlClose(udp_client);
 open_sockets();
 connected = false;
 login_packet_sent = false;
}

long int last_ping_time = 0;
long int last_udp_time = 0;

void network_init()
{
 nlInit();
 nlSelectNetwork(NL_IP);
 nlDisable(NL_BLOCKING_IO);
 open_sockets();
 login_packet_sent = false;
 connected = false;
}


void network_update()
{
 if(network_disabled || !connected) return;
 static int ping_time = 0;
 if(reconnect) ping_time = 0; else ping_time = 3;
 if(server_address.valid && udp_try_connection && time(0) - last_udp_time > 1)
 {
   nlSetAddrPort(&server_address,SERVER_PORT_UDP);
   nlSetRemoteAddr(udp_client,&server_address);
   static char verimsg[5];
   verimsg[0] = ID_LOGIN;
   memcpy(verimsg+1,&verification_code,5);
   nlWrite(udp_client,verimsg,5);
   last_udp_time = time(0);
  }
 if(time(0) - last_ping_time  > ping_time)
 {
  static char pingmsg[1];
  pingmsg[0] = ID_PING;
  if(nlWrite(tcp_client,pingmsg,1) == NL_INVALID)
   network_notify_disconnect();
  else
  {
   reconnect = false;
   nlTime(&last_ping_nltime);
   last_ping_time = time(0);
  }
 }

 if(login_packet_sent == false)
 {
  if(nlWrite(tcp_client,login_packet,strlen(login_packet)+1) == NL_INVALID)
   network_notify_disconnect();
  else
  {
   login_packet_sent = true;
   reconnect = false;
   login_setmessage("Connected, waiting for character data...",strlen("Connected, waiting for character data..."));
  }
 }

 if(!connected) return;

 static Packet p;
 if((p.length = p.orig_length = nlRead(tcp_client,p._data,MAX_PACKET_SIZE)) > 0)
 {
  p.data = p._data;
  while(p.length > 0)
   handle_tcp_packet(&p);
 }
 if((p.length = p.orig_length = nlRead(udp_client,p._data,MAX_PACKET_SIZE)) > 0)
 {
  p.data = p._data;
  handle_udp_packet(&p);
 }
}

void network_cleanup()
{
 network_disconnect();
 nlShutdown();
}

void network_login(char *ip, char *login, char *password)
{
 //if(connected) return;
 char msg[100];
 sprintf(msg,"Connecting to %s...",ip);
 login_setmessage(msg,strlen(msg));
 login_packet_sent = false;
 reconnect = true; connected = true;
 connection_attempt_count = 1;
 last_connection_attempt = time(0);
 login_packet[0] = ID_LOGIN;
 strcpy(login_packet+1,login);
 login_packet[strlen(login)+1] = ' ';
 strcpy(login_packet+strlen(login)+2,password);
 login_packet[strlen(login)+strlen(password)+2] = 0;

 nlGetAddrFromNameAsync(ip,&server_address);
 last_ping_time = 0;
 last_connection_attempt = time(0)-CONNECTION_ATTEMPT_TIMEOUT;
}

void network_disconnect()
{
 network_notify_disconnect();
 rest(100);
}

void network_send_message(char *msg, int length)
{
 if(!connected || network_disabled) return;
 char msg2[length+1];
 msg2[0] = ID_MESSAGE;
 strncpy(msg2+1,msg,length);
 nlWrite(tcp_client,msg2,length+1);
}

void network_send_position(int x, int y)
{
 if(network_disabled) return;
 char msg[7];
 msg[0] = ID_MOVE;
 msg[1] = (x+8) >> 4;
 msg[2] = (y+8) >> 4;
 msg[3] = (x+8)&15 | (((y+8)&15)<<4);
 msg[4] = aura_target[0];
 msg[5] = aura_target[1];
 msg[6] = aura_target[2];
 nlWrite(udp_client,msg,7);
}

void network_send_packet(unsigned char *pack, int length)
{
 if(network_disabled) return;
 nlWrite(tcp_client,pack,length);
}
