/* Mednafen - Multi-system Emulator
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef NETWORK
#include "main.h"
#include <stdarg.h>
#include <stdio.h>
#include <SDL_net.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "netplay.h"
#include "console.h"
#include "../md5.h"

#include <trio/trio.h>

int MDFNDnetplay=0; 

static TCPsocket Socket = NULL;

static void en32(uint8 *buf, uint32 morp)
{
 buf[0]=morp;
 buf[1]=morp>>8;
 buf[2]=morp>>16;
 buf[3]=morp>>24;
}

static void PrintNetStatus(const char *s)
{
 MDFND_NetplayText((uint8 *)s);
}

static void PrintNetError(const char *format, ...)
{
 char temp[2048];

 va_list ap;

 va_start(ap, format);
 trio_vsnprintf(temp, 2048, format, ap);
 MDFND_NetplayText((uint8 *)temp);
// MDFND_PrintError(temp);

 va_end(ap);
}

int MDFND_NetworkConnect(void) // Called in game thread usually
{
 IPaddress IPa;

 int local_players = MDFN_GetSettingUI("netlocalplayers");
  
 if(Socket) // Disconnect if we're already connected.  TODO:  Refactor this.
 {
  MDFND_NetworkClose();
 }

 if(SDLNet_Init() == -1)
 {
  PrintNetStatus(_("*** Error intializing SDL_net!"));
  return(0);
 }

 std::string nickname = MDFN_GetSettingS("netnick");
 std::string remote_host = MDFN_GetSettingS("nethost");
 unsigned int remote_port = MDFN_GetSettingUI("netport");
 std::string game_key = MDFN_GetSettingS("netgamekey");

 if(SDLNet_ResolveHost(&IPa, remote_host.c_str(), remote_port) == -1)
 {
  PrintNetError(_("*** Error resolving host \"%s\"!"), remote_host.c_str());
  return(0);
 }

 Socket = SDLNet_TCP_Open(&IPa);
 if(!Socket)
 {
  PrintNetError(_("*** Error connecting to remote host \"%s\" on port %u!"), remote_host.c_str(), remote_port);
  return(0);
 }
 
 PrintNetStatus(_("*** Sending initialization data to server."));
 {
  uint8 *sendbuf;
  uint32 sblen;

   sblen = 4 + 16 + 16 + 64 + 1 + nickname.size();
   sendbuf = (uint8 *)malloc(sblen);
   memset(sendbuf, 0, sblen);
                           
   en32(sendbuf, sblen - 4);
   if(game_key != "")
   {
    md5_context md5;
    uint8 md5out[16];

    md5.starts();
    md5.update(CurGame->MD5, 16);
    md5.update((uint8 *)game_key.c_str(), game_key.size());
    md5.finish(md5out);
    memcpy(sendbuf + 4, md5out, 16);
   }
   else
    memcpy(sendbuf + 4, CurGame->MD5, 16);

   std::string connect_password = MDFN_GetSettingS("netpassword");

   if(connect_password != "")
   {
    md5_context md5;
    uint8 md5out[16];
   
    md5.starts();
    md5.update((uint8*)connect_password.c_str(), connect_password.size());
    md5.finish(md5out);
    memcpy(sendbuf + 4 + 16, md5out, 16);
   }
                        
   memset(sendbuf + 4 + 16 + 16, 0, 64);

   MDFNDnetplay = 1;

   if(!MDFNI_NetplayPrestart(&sendbuf[4 + 16 + 16 + 0]))
   {
    free(sendbuf);
    return(0);
   }
   
   sendbuf[4 + 16 + 16 + 32] = MDFN_GetSettingUI("netmerge");

   sendbuf[4 + 16 + 16 + 64] = local_players;

   if(nickname != "")
    memcpy(sendbuf + 4 + 16 + 16 + 64 + 1,nickname.c_str(),nickname.size());

  SDLNet_TCP_Send(Socket, sendbuf, sblen);
  free(sendbuf);
 }

 if(!MDFNI_NetplayStart(local_players))
 {
  return(0);
 }
 PrintNetStatus(_("*** Connection established.\n"));

 SDL_Event evt;
 evt.user.type = SDL_USEREVENT;
 evt.user.code = CEVT_NETPLAYGUISET;
 evt.user.data1 = (void *)1;
 SDL_PushEvent(&evt);

 return(1);
}


int MDFND_SendData(const void *data, uint32 len)
{
 SDLNet_TCP_Send(Socket, (void *)data, len); // Stupid non-constness!
 return(1);
}

int MDFND_RecvData(void *data, uint32 len)
{
  NoWaiting&=~2;
   
  SDLNet_SocketSet funfun;

  funfun = SDLNet_AllocSocketSet(1);
  SDLNet_TCP_AddSocket(funfun, Socket);

  for(;;)
  {
   switch(SDLNet_CheckSockets(funfun, 100000))
   {
    case 0: SDLNet_FreeSocketSet(funfun);continue;
    case -1: SDLNet_FreeSocketSet(funfun); printf("RecvData Failed on select(): %d\n", len); return(0);
   }

   if(SDLNet_SocketReady(Socket))
   {
    while(len)
    {
     int32 boop = SDLNet_TCP_Recv(Socket, data, len);
     if(boop <= 0)
     {
      puts(SDLNet_GetError());
      return(0);
     }
     data = (uint8 *)data + boop;
     len -= boop;
    }
    SDLNet_FreeSocketSet(funfun);
    funfun = SDLNet_AllocSocketSet(1);
    SDLNet_TCP_AddSocket(funfun, Socket);
    if(SDLNet_CheckSockets(funfun, 0) == 1)
      NoWaiting|=2;
    SDLNet_FreeSocketSet(funfun);
    return(1);
   }

   SDLNet_FreeSocketSet(funfun);
  }
  printf("RecvData Failed: %d\n", len);
  return 0;
}

void MDFND_NetworkClose(void)
{
 if(Socket)
  SDLNet_TCP_Close(Socket);
 Socket = NULL;

 if(MDFNDnetplay)
  MDFNI_NetplayStop();
 MDFNDnetplay = 0;
 NoWaiting&=~2;

 SDL_Event evt;
 evt.user.type = SDL_USEREVENT;
 evt.user.code = CEVT_NETPLAYGUISET;
 evt.user.data1 = (void *)0;
 SDL_PushEvent(&evt);
}

void MDFND_NetplayText(const uint8 *text)
{
 char *tot = (char *)malloc(strlen((char *)text) + 1);
 char *tmp;
 strcpy(tot, (char *)text);
 tmp = tot;

 while(*tmp)
 {
  if(*tmp < 0x20) *tmp = ' ';
  tmp++;
 }

 SDL_Event evt;
 evt.user.type = SDL_USEREVENT;
 evt.user.code = CEVT_DISPLAYNETPLAYTEXT ;
 evt.user.data1 = tot;
 SDL_PushEvent(&evt);

 // "tot" will be free'd after the text is handled in the main thread.
}

class NetplayConsole : public MDFNConsole
{

	virtual bool TextHook(UTF8 *text)
	{
                      if(!strncmp((char *)text, "/server ", strlen("/server ")))
                      {
                       MDFNI_SetSetting("nethost", strdup((char *)text + strlen("/server ")));
                       free(text);
                       SDL_Event evt;
                       evt.user.type = SDL_USEREVENT;
                       evt.user.code = CEVT_NETPLAYCONNECT;
                       evt.user.data1 = malloc(sizeof(int));
                       *(int *)evt.user.data1 = 1;
                       SDL_PushEvent(&evt);
                      }
                      else
                      {
                       SDL_Event evt;
                       evt.user.type = SDL_USEREVENT;
                       evt.user.code = CEVT_NETPLAYTEXT;
                       evt.user.data1 = text;
                       SDL_PushEvent(&evt);
                      }
	 return(1);
	}

};

static NetplayConsole NetConsole;

static volatile int viewable = 0;
void NetplayText_InMainThread(uint8 *text)
{
 NetConsole.WriteLine(text);
 viewable = 1;
}

void Netplay_ToggleTextView(void)
{
 viewable ^= 1;
}

int Netplay_GetTextView(void)
{
 return(viewable);
}

void DrawNetplayTextBuffer(SDL_Surface *surface, const SDL_Rect *src_rect)
{
 if(!viewable) return;
 NetConsole.Draw(surface, src_rect);
}

int NetplayEventHook(const SDL_Event *event)
{
 if(!viewable) return(1);
 return(NetConsole.Event(event));
}

#endif

