/* 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 "../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;
  uint8 buf[5];
  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);
}

#include <vector>

std::vector<std::string> TextLog;

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.
}


static volatile int viewable = 0;
void NetplayText_InMainThread(uint8 *text)
{
 TextLog.push_back(std::string((char *)text));
 viewable = 1;
 SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
}

void Netplay_ToggleTextView(void)
{
 viewable ^= 1;
 if(viewable)
  SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
 else
  SDL_EnableKeyRepeat(0, 0);
}

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

static std::vector<std::string> netplay_kb_buffer;
#include "../ConvertUTF.h"
int NetplayEventHook(const SDL_Event *event) // Called from main thread, of course.
{
  if(!viewable) return(1);
  switch(event->type)
  {
   case SDL_KEYDOWN:
		    if(event->key.keysym.mod & KMOD_ALT)
		     break;
                    switch(event->key.keysym.sym)
                    {
		     case SDLK_RETURN:
		     {
		      std::string concat_str;
		      for(unsigned int i = 0; i < netplay_kb_buffer.size(); i++)
		       concat_str += netplay_kb_buffer[i];

		      char *tmp_c_str = strdup(concat_str.c_str());
		      if(!strncmp(tmp_c_str, "/server ", strlen("/server ")))
		      {
		       MDFNI_SetSetting("nethost", strdup(tmp_c_str + strlen("/server ")));
		       free(tmp_c_str);
		       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 = tmp_c_str;
		       SDL_PushEvent(&evt);
		      }
		      netplay_kb_buffer.clear();
		     }
		     break;
		     case SDLK_BACKSPACE: if(netplay_kb_buffer.size())
			{
				netplay_kb_buffer.erase(netplay_kb_buffer.end() - 1, netplay_kb_buffer.end());
			}
		        break;
                     default:
                     {
                      uint8 utf8_buffer[8];
                      UTF8 *dest_ptr = utf8_buffer;
		      memset(utf8_buffer, 0, sizeof(utf8_buffer));
                      const UTF16 *start_utf16 = &event->key.keysym.unicode;
                      ConvertUTF16toUTF8(&start_utf16, (UTF16 *)&event->key.keysym.unicode + 1, &dest_ptr, &utf8_buffer[8], lenientConversion);
			//puts((char*)utf8_buffer);
                      netplay_kb_buffer.push_back(std::string((char *)utf8_buffer));
		      //printf("%04x\n", *start_utf16);
                     }
                     break;
                    }
                    break;
  }
 return(1);
}



#define MK_COLOR_A(r,g,b,a) ( ((a)<<tmp_surface->format->Ashift) | ((r)<<tmp_surface->format->Rshift) | ((g) << tmp_surface->format->Gshift) | ((b) << tmp_surface->format->Bshift))
#define DO_COLOR_HALF(v) ( (((v&0xff)>>1)&0xFF) | ((v&0xFF00)>>1)&0xFF00 | ((v&0xFF0000)>>1)&0xFF0000 | ((v&0xFF000000)>>1)&0xFF000000 )
#define DO_COLOR_FOURTH(v) ( (((v&0xff)>>2)&0xFF) | ((v&0xFF00)>>2)&0xFF00 | ((v&0xFF0000)>>2)&0xFF0000 | ((v&0xFF000000)>>2)&0xFF000000 )

uint32 DrawTextTrans(uint32 *dest, int pitch, uint32 width, uint8 *textmsg, uint32 fgcolor, int centered);
uint32 DrawTextTransShadow(uint32 *dest, int pitch, uint32 width, uint8 *textmsg, uint32 fgcolor, uint32 shadcolor, int centered);

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

 uint32 pitch32 = surface->pitch >> 2;
 uint32 w = src_rect->w;
 uint32 h = src_rect->h;
 uint32 *pixels = (uint32 *)surface->pixels + src_rect->x + src_rect->y * pitch32;
 SDL_Surface *tmp_surface = SDL_CreateRGBSurface(SDL_SWSURFACE | SDL_SRCCOLORKEY, 1024, 13 + 1, 32, surface->format->Rmask, surface->format->Gmask, surface->format->Bmask, surface->format->Amask);
 SDL_SetColorKey(tmp_surface, SDL_SRCCOLORKEY, 0);

 for(unsigned int y = 0; y < h; y++)
 {
  uint32 *row = pixels + y * pitch32;
  for(unsigned int x = 0; x < w; x++)
  {
   //printf("%d %d %d\n", y, x, pixels);
   row[x] = MK_COLOR_A(0, 0, 0, 0x80);
   //row[x] = MK_COLOR_A(0x00, 0x00, 0x00, 0x7F);
  }
 }


 int32 destline = ((h - 13) / 13) - 1;
 int32 vec_index = TextLog.size() - 1;

 while(destline >= 0 && vec_index >= 0)
 {
  int32 pw = DrawTextTransShadow((uint32 *)tmp_surface->pixels, tmp_surface->pitch, tmp_surface->w, (UTF8 *)TextLog[vec_index].c_str(), 
	MK_COLOR_A(0xff, 0xff, 0xff, 0xFF), MK_COLOR_A(0x00, 0x00, 0x01, 0xFF), 0);
  int32 numlines = (uint32)ceil((double)pw / w);

  while(numlines > 0 && destline >= 0)
  {
   int32 offs = (numlines - 1) * w;
   SDL_Rect tmp_rect, dest_rect;
   tmp_rect.x = offs;
   tmp_rect.y = 0;
   tmp_rect.h = 13;
   tmp_rect.w = (pw - offs) > (int32)w ? w : pw - offs;

   dest_rect.x = src_rect->x;
   dest_rect.y = src_rect->y + destline * 13;
   dest_rect.w = src_rect->w;
   dest_rect.h = src_rect->h;
   
   SDL_SetAlpha(tmp_surface, 0, SDL_ALPHA_OPAQUE);
   SDL_BlitSurface(tmp_surface, &tmp_rect, surface, &dest_rect);
   numlines--;
   destline--;
  }
  SDL_FillRect(tmp_surface, NULL, 0);
  vec_index--;
 }

 {
  SDL_FillRect(tmp_surface, NULL, 0x00);

  std::string concat_str;
  concat_str = "#>";
  for(unsigned int i = 0; i < netplay_kb_buffer.size(); i++)
   concat_str += netplay_kb_buffer[i];

  concat_str += (SDL_GetTicks() & 0x100) ? "_" : " ";
  SDL_Rect tmp_rect, dest_rect;

  tmp_rect.w = DrawTextTransShadow((uint32 *)tmp_surface->pixels, tmp_surface->pitch, tmp_surface->w, (UTF8*)concat_str.c_str(), MK_COLOR_A(0xff, 0xff, 0xff, 0xff), MK_COLOR_A(0x00, 0x00, 0x01, 0xFF), 0);
  tmp_rect.h = dest_rect.h = 13;
  tmp_rect.x = 0;
  tmp_rect.y = 0;

  if(tmp_rect.w >= w)
  {
   tmp_rect.x = tmp_rect.w - w; 
   tmp_rect.w -= tmp_rect.x;
  }
  dest_rect.w = tmp_rect.w;
  dest_rect.x = src_rect->x;
  dest_rect.y = src_rect->y + h - 14;

  //printf("%d %d %d %d\n", tmp_rect.x, tmp_rect.w, tmp_rect.y, tmp_rect.h);
  SDL_SetAlpha(tmp_surface, 0, SDL_ALPHA_OPAQUE);
  SDL_BlitSurface(tmp_surface, &tmp_rect, surface, &dest_rect);
 }
 SDL_FreeSurface(tmp_surface);
}

#endif

