/*   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 "console.h"
#include "main.h"
#include "map.h"
#include "login.h"
#include "player.h"
#include "play.h"
#include "palette.h"
#include "damage.h"
#include "rune.h"
#include "monster.h"
#include "gui.h"
#include "item.h"
#include "skill.h"

int current_ping = 0;
int security_counter = 0;
NLtime last_ping_nltime;

void save_packet(Packet *p)
{
 FILE *f = fopen("packetlog.txt","a");
 if(!f) { allegro_message("Couldn't save packet data!\n"); return; }
 for(int i = 0; i < p->orig_length; i++)
 {
  if(p->_data + i == p->data)
   fprintf(f,"#");
  fprintf(f,"%u.",p->_data[i]);
 }
 fprintf(f,"\n");
 fclose(f);
}


void handle_tcp_packet(Packet *p)
{
 if(p->data == p->_data) security_counter = 0;
 security_counter++;
 if(security_counter > 500) { p->length = 0; allegro_message("handle_tcp_packet is looping endlessly (protocol error?)\n"); save_packet(p); return; }
 //save_packet(p);
 //debug
/* if(game_state == GAME_PLAY)
 {
  char _msg[200];
  sprintf(_msg,"Packet type: %d Packet total length: %d",p->data[0],p->length);
  console_add_line(_msg);
 }*/

 if(p->data[0] == ID_DISCONNECTING)
 {
  p->data++;
  p->length--;
  network_notify_disconnect();
  return;
 }
 if(p->data[0] == ID_PING)
 {
  static NLtime t;
  nlTime(&t);
  p->data++;
  p->length--;
  current_ping = time_diff(&t,&last_ping_nltime);
  return;
 }

 if(p->data[0] == ID_UDP_ACCEPTED)
 {
  udp_try_connection = false;
  console_add_line("Connected to server. Press F1 for context help.",strlen("Connected to server. Press F1 for context help."),255);
  p->data++;
  p->length--;
  return;
 }
 if(game_state == GAME_LOGIN)
 {
  if(p->data[0] == ID_MESSAGE)
  {
   network_notify_disconnect();
   login_setmessage((char*)p->data+2,p->data[1]);
   p->length -= 2 + p->data[1];
   p->data += 2 + p->data[1];
   return;
  }
  if(p->data[0] == ID_LOGIN)
  {
   if(p->length < 42) return;
   memcpy(&verification_code,p->data+1,4);
   udp_try_connection = true;
   login_cleanup();
   game_state = GAME_PLAY;
   hotkey_reset();
   create_player(p->data+1,41);
   play_init();
   p->data += 42;
   p->length -= 42;
   return;
  }
 } //GAME_LOGIN
 if(game_state == GAME_PLAY)
 {
  if(p->data[0] == ID_MESSAGE)
  {
   if(p->length < 2) return;
   console_add_line((char*)p->data+2,p->data[1]);
   p->length -= 2+p->data[1];
   p->data += 2+p->data[1];
   return;
  }
  if(p->data[0] == ID_CHAT)
  {
   if(p->length < 3) return;
   if(objects[(int)p->data[1]])
   {
    new ChatSymbol((int)p->data[1]);
    BITMAP *head = objects[(int)p->data[1]]->get_head();
    if(head) console_add_line(head,(char*)p->data+3,p->data[2],p->data[1]);
    p->length -= 3+p->data[2];
    p->data += 3+p->data[2];
    return;
   }
   p->length -= 3+p->data[2];
   p->data += 3+p->data[2];
   console_add_line((char*)p->data+3,p->data[2]);
   return;
  }
  if(p->data[0] == ID_PLAYER)
  {
   new OtherPlayer(p->data+2,p->data[1]);
   p->length -= 2+p->data[1];
   p->data += 2+p->data[1];
   save_packet(p);
   return;
  }
  if(p->data[0] == ID_MAP)
  {
   if(p->length < 7) return;
   static unsigned int map;
   static int charnum;
   charnum = p->data[1];
   memcpy(&map,p->data+2,2);
   next_charnum = charnum;
   next_map = map;
   next_x = (p->data[4]&255) << 4;
   next_y = (p->data[5]&255) << 4;
   next_map_flags = p->data[6];
   if(map_loaded)
   {
    event_func = map_change;
    network_disabled = true;
    for(int i=0; i<256; i++)
     objects[i] = 0;
    objects[next_charnum] = player;
    player->obj_num = next_charnum;
    RGB black = { 0, 0, 0 };
    palette_fade_out(black);
   } else
   {
    map_change();
   }
   p->data += 7;
   p->length -= 7;
   return;
  }//ID_MAP
  if(p->data[0] == ID_QUIT)
  {
   if(p->length < 2) return;
   if(objects[(int)(p->data[1]&255)])
   {
    objects[(int)(p->data[1]&255)]->kill_counter = KILL_DELAY;
    objects[(int)(p->data[1]&255)] = 0;
   }
   p->data += 2;
   p->length -= 2;
   return;
  } //ID_QUIT
  if(p->data[0] == ID_PVP)
  {
   if(p->length < 2) return;
   if(p->data[1])
   {
    console_add_line("### PvP mode is ON.");
    pvp_mode = true;
   }
   else
   {
    console_add_line("### PvP mode is OFF.");
    pvp_mode = false;
   }
   p->data += 2;
   p->length -= 2;
   return;
  }//ID_PVP
  if(p->data[0] == ID_DELAY)
  {
   if(p->length < 3) return;
   memcpy(&(player->soft_delay),p->data+1,2);
   nlTime(&(player->soft_time));
   p->data += 3;
   p->length -= 3;
   return;
  }//ID_DELAY
  if(p->data[0] == ID_ATTACK)
  {
   if(p->length < 4) return;
   if(objects[(int)(p->data[1])] && objects[(int)(p->data[2])])
    objects[(int)(p->data[1])]->attack(p->data[2],p);
   return;
  }//ID_ATTACK
  if(p->data[0] == ID_DEATH)
  {
   if(p->length < 2 || !objects[(int)p->data[1]]) return;
   objects[(int)(p->data[1])]->dead = !objects[(int)(p->data[1])]->dead;
   p->data += 2;
   p->length -= 2;
   return;
  }//ID_DEATH
  if(p->data[0] == ID_HPMP)
  {
   if(p->length < 5) return;
   memcpy(&(player->hp),p->data+1,2);
   memcpy(&(player->mp),p->data+3,2);
   p->data += 5;
   p->length -= 5;
   return;
  }//ID_HPMP
  if(p->data[0] == ID_MAXHPMP)
  {
   if(p->length < 9) return;
   memcpy(&(player->hp),p->data+1,2);
   memcpy(&(player->mp),p->data+3,2);
   memcpy(&(player->mhp),p->data+5,2);
   memcpy(&(player->mmp),p->data+7,2);
   hpbar->max = player->mhp;
   mpbar->max = player->mmp;
   p->data += 9;
   p->length -= 9;
   return;
  }//ID_HPMP
  if(p->data[0] == ID_RUNE)
  {
   if(p->length < 4) return;
   new Rune(p->data[1],p->data[2],p->data[3]);
   p->data += 4;
   p->length -= 4;
   return;
  }//ID_RUNE
  if(p->data[0] == ID_MONSTER)
  {
   if(p->length < 7) return;
   unsigned int type = 0;
   memcpy(&type,p->data+2,2);
   objects[(int)p->data[1]] = new ParticleMonster(p->data[4]*16,p->data[5]*16,type);
   objects[(int)p->data[1]]->speed = p->data[6] / 60.0 * 16.0;
   p->data += 7;
   p->length -= 7;
   return;
  }//ID_MONSTER
  if(p->data[0] == ID_LEVELUP)
  {
   if(p->length < 2) return;
   if(!objects[(int)p->data[1]]) return;
   new FlashText("LEVEL UP!",objects[(int)p->data[1]]);
   if(p->data[1] == player->obj_num && p->length >= 4)
   {
    player->level++;
    free_statpoints = 0;
    memcpy(&free_statpoints,p->data+2,2);
    p->data += 2; p->length -= 2;
   }
   p->data += 2; p->length -= 2;
   return;
  }//ID_LEVELUP
  if(p->data[0] == ID_LOOK)
  {
   if(p->length < 6) return;
   if(!objects[(unsigned int)p->data[1]]) return;
   objects[(int)p->data[1]]->reload_sprites(p->data+2);
   p->data += 6; p->length -= 6;
   return;
  }//ID_LOOK
  if(p->data[0] == ID_SIT)
  {
   if(p->length < 3) return;
   if(!objects[(int)p->data[1]]) return;
   objects[(int)p->data[1]]->sitting = p->data[2];
   p->data += 3; p->length -= 3;
   return;
  }//ID_SIT
  if(p->data[0] == ID_REGEN)
  {
   player->hp = player->mhp;
   player->mp = player->mmp;
   new RegenWave();
   energy -= 5;
   regen_timer = REGEN_DELAY;
   p->data++; p->length--;
   return;
  }//ID_REGEN
  if(p->data[0] == ID_ENERGY)
  {
   if(p->length < 2) return;
   energy = p->data[1];
   p->data += 2; p->length -= 2;
   return;
  }//ID_ENERGY
  if(p->data[0] == ID_TIME)
  {
   if(p->length < 2) return;
   current_time = ((unsigned char*)p->data)[1];
   palette_update_time();
   p->data += 2; p->length -= 2;
   return;
  }//ID_TIME
  if(p->data[0] == ID_ITEMDELAY)
  {
   if(p->length < 2) return;
   item_delay = p->data[1] * 60;
   p->data += 2; p->length -= 2;
   return;
  }
  if(p->data[0] == ID_ITEMUSE)
  {
   if(p->length < 5) return;
   unsigned short num = 0, q = 0;
   memcpy(&num,p->data+1,2);
   memcpy(&q,p->data+3,2);
   item_add(INV_USE,num,q);
   if(menu_active) gui_handle_packet(p->data,5);
   p->data += 5; p->length -= 5;
   return;
  }//ID_ITEMUSE
  if(p->data[0] == ID_ITEMETC)
  {
   if(p->length < 5) return;
   unsigned short num = 0, q = 0;
   memcpy(&num,p->data+1,2);
   memcpy(&q,p->data+3,2);
   item_add(INV_ETC,num,q);
   if(menu_active) gui_handle_packet(p->data,5);
   p->data += 5; p->length -= 5;
   return;
  }//ID_ITEMETC
  if(p->data[0] == ID_ITEMLOOT)
  {
   if(p->length < 5) return;
   unsigned short num = 0, q = 0;
   memcpy(&num,p->data+1,2);
   memcpy(&q,p->data+3,2);
   item_add(INV_LOOT,num,q);
   if(menu_active) gui_handle_packet(p->data,5);
   p->data += 5; p->length -= 5;
   return;
  }//ID_ITEMLOOT
  if(p->data[0] == ID_INVENTORY)
  {
   item_read_inventory(p);
   return;
  }//ID_INVENTORY
  if(p->data[0] == ID_ITEMDEL)
  {
   if(p->length < 6) return;
   unsigned short id = 0, quantity = 0;
   if(p->data[1] != INV_EQUIP)
   {
    memcpy(&id,p->data+2,2);
    memcpy(&quantity,p->data+4,2);
    item_remove(p->data[1],id,quantity);
   }
   if(menu_active) gui_handle_packet(p->data,6);
   p->data += 6; p->length -= 6;
   return;
  }//ID_ITEMDEL
  if(p->data[0] == ID_USEDITEM)
  {
   if(p->length < 4) return;
   if(!objects[(int)p->data[1]]) return;
   unsigned int id;
   memcpy(&id,p->data+2,2);
   item_render_use(objects[(int)p->data[1]],id);
   p->data += 4;
   p->length -= 4;
   return;
  }//ID_USEDITEM
  if(p->data[0] == ID_HOTKEY_LIST)
  {
   p->data++; p->length--;
   for(int i = 0; i < 16; i++)
   {
    if(p->length < 1) { allegro_message("Warning: received corrupted hotkey list\n"); save_packet(p); return; }
    hotkeys[i].type = p->data[0];
    switch(p->data[0])
    {
     case HOTKEY_ITEM: memcpy(&(hotkeys[i].id),p->data+1,2);
                       p->data += 2; p->length -= 2;
                       break;
     default: break;
    }
    p->data++; p->length--;
   }
   return;
  }//ID_HOTKEY_LIST
  if(p->data[0] == ID_HOTKEY)
  {
   if(p->length < 3) return;
   if(p->data[1] > 15) return;
   hotkeys[(int)p->data[1]].delay = hotkeys[(int)p->data[1]].orig_delay = p->data[2]*15;
   if(p->data[2] == 0) new SkillFlash(hotkey_colors[(int)p->data[1]],player);
   p->data += 3; p->length -= 3;
   return;
  }//ID_HOTKEY
  if(p->data[0] == ID_LOOTDROP)
  {
   if(p->length < 4) return;
   if(!objects[(int)p->data[1]]) return;
   new LootIndicator(p->data[2]*16+8,p->data[3]*16+8,objects[(int)p->data[1]]);
   p->data += 4; p->length -= 4;
   return;
  }//ID_ITEMDROP
 }
 //received unrecognized data, better tell user about that
 char msg[100];
 sprintf(msg,"Warning: unrecognized packet type received (possibly you are using an outdated client, please update)\n Packet type: %u Length: %d",p->data[0],p->length);
 allegro_message(msg);
 save_packet(p);
 p->length = 0;
}

void handle_udp_packet(Packet *p)
{
 if(game_state != GAME_PLAY) return;
 if(p->data[0] == ID_MOVE)
 {
  if(p->length < 3) return;
  static unsigned char *ptr;
  unsigned int map = 0;
  memcpy(&map,p->data+1,2);
  if(map_num != map) { /*console_add_line("Dropped UDP packet.");*/ return; }
  ptr = p->data+3;
  static unsigned int id, x, y, detail;
  for(int i=0; i<(p->length-1)/4; i++)
  {
   id = *(ptr++) & 255;
   x = *(ptr++) & 255;
   y = *(ptr++) & 255;
   detail = *(ptr++) & 255;
   if(!objects[id])  continue;
   if(objects[id] != player && objects[id]->stunlock) continue;
   objects[id]->net_x = (x<<4) + (detail&15) - 8;
   objects[id]->net_y = (y<<4) + (detail>>4) - 8;
   objects[id]->net_updated = true;
  }
/*  char msg[400];
  sprintf(msg,"%d players UDP updated. (%d)",(p->length-1)/4,p->length);
  console_add_line(msg,strlen(msg));*/
 }//ID_MOVE
}
