/* 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
 */


#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zlib.h>
#include <string>
#include <math.h>
#include <trio/trio.h>

#include "types.h"
#include "netplay.h"
#include "netplay-driver.h"
#include "mednafen.h"
#include "general.h"
#include "state.h"
#include "movie.h"
#include "endian.h"

int MDFNnetplay=0;

#ifdef NETWORK
static int numlocal;
static NetplaySystemInfoStruct NSI;
static char *OurNick = NULL;

/* NetError should only be called after a MDFND_*Data function returned 0, in the function
   that called MDFND_*Data, to prevent it from being called twice.
*/

static void NetError(char *format, ...)
{
 char *temp = NULL;
 va_list ap;

 va_start(ap, format);
 temp = trio_vaprintf(format, ap);
 va_end(ap);

 MDFND_NetplayText((UTF8 *)temp);
 MDFND_NetworkClose();
 free(temp);
}

void MDFNI_NetplayStop(void)
{
	if(MDFNnetplay)
	{
	 MDFNnetplay = 0;
 	 MDFN_FlushGameCheats(1);	/* Don't save netplay cheats. */
 	 MDFN_LoadGameCheats(0);		/* Reload our original cheats. */
	 if(OurNick)
	 {
	  free(OurNick);
	  OurNick = NULL;
	 }
	}
	else puts("Check your code!");
}

bool MDFNI_NetplayPrestart(uint8 extra[32])
{
        if(!MDFNGameInfo->StartNetplay)
        {
         MDFND_NetplayText((UTF8 *)_("Sorry, network play isn't supported yet for the current emulated system."));
         return(0);
        }
        if(!MDFNGameInfo->StartNetplay(&NSI))
        {
         MDFND_NetplayText((UTF8 *)_("Error intializing system-specific portion of network play."));
         return(0);
        }
        extra[0] = 1; // Protocol version
        extra[1] = NSI.total_controllers;
        extra[2] = NSI.controller_data_type;
	return(1);
}

int MDFNI_NetplayStart(int nlocal)
{
	MDFN_FlushGameCheats(0);	/* Save our pre-netplay cheats. */
	MDFNnetplay = 1;
        numlocal = nlocal;

	if(MDFNMOV_IsPlaying())		/* Recording's ok during netplay, playback is not. */
	 MDFNMOV_Stop();

	MDFNNET_SendCommand(MDFNNPCMD_SETFPS, MDFNGameInfo->fps);
	return(1);
}

int MDFNNET_SendCommand(uint8 cmd, uint32 len)
{
 uint8 buf[1 + numlocal + 4]; // Command, unused, command length

 memset(buf, 0, sizeof(buf));

 buf[0] = cmd;
 MDFN_en32lsb(&buf[1 + numlocal], len);
 if(!MDFND_SendData(buf,numlocal + 1 + 4)) 
 {
  NetError("Could not send command.");
  return(0);
 }
 return(1);
}

void MDFNI_NetplayText(const uint8 *text)
{
 uint32 len;

 len = strlen((char *)text);

 if(!MDFNNET_SendCommand(MDFNNPCMD_TEXT,len)) return;

 if(!MDFND_SendData(text,len))
  NetError("Cound not send text data.");
}

int MDFNNET_SendState(void)
{
 StateMem sm;
 uLongf clen;
 uint8 *cbuf;

 memset(&sm, 0, sizeof(StateMem));

 if(!MDFNSS_SaveSM(&sm, 0, 0))
  return(0);

 clen = sm.len + sm.len / 1000 + 12;
 cbuf = (uint8 *)malloc(4 + clen);
 MDFN_en32lsb(cbuf, sm.len);
 compress2((Bytef *)cbuf + 4, &clen, (Bytef *)sm.data, sm.len, 7);

 if(!MDFNNET_SendCommand(MDFNNPCMD_LOADSTATE,clen + 4))
 {
  free(cbuf);
  NetError(_("Could not send the save state command to the netplay server."));
  return(0);
 }

 if(!MDFND_SendData(cbuf, clen + 4))
 {
  NetError(_("Could not send the save state data to the netplay server."));
  free(cbuf);
  return(0);
 }

 free(sm.data);
 free(cbuf);

 return(1);
}

int MDFNNET_RecvState(uint32 clen)
{
 StateMem sm;
 uint8 *cbuf;

 memset(&sm, 0, sizeof(StateMem));

 if(clen > 4000000) // Sanity check
 {
  NetError("Compressed save state data is too large: %d", clen);
  return(0);
 }
 cbuf = (uint8*)malloc(clen);
 if(!MDFND_RecvData(cbuf, clen))
 {
  NetError("Could not receive compressed save state data.");
  free(cbuf);
  return(0);
 }
 uLongf len = MDFN_de32lsb((uint8 *)cbuf);
 if(len > 4000000)
 {
  NetError("Decompressed save state data is too large: %d", clen);
  free(cbuf);
  return(0);
 }
 uint8 *buf = (uint8 *)malloc(len);
 uncompress((Bytef *)buf, &len, (Bytef *)cbuf + 4, clen - 4);

 sm.data = buf;
 sm.len = len;
 if(!MDFNSS_LoadSM(&sm, 0, 0))
 {
  NetError("Error during save state loading.");
  return(0);
 }
 if(MDFNMOV_IsRecording())
  MDFNMOV_RecordState();
 return(1);
}

void NetplayUpdate(uint8 *joyp)
{
 uint8 buf[NSI.total_controllers + 1];
 uint8 outgoing_buffer[1 + numlocal];

 outgoing_buffer[0] = 0; // This is not a command, duh!
 memcpy(outgoing_buffer + 1, joyp, numlocal);

 if(!MDFND_SendData(outgoing_buffer, 1 + numlocal))
 {
  NetError("Sending joystick update data failed.");
  return;
 }

 do
 {
  if(!MDFND_RecvData(buf, NSI.total_controllers + 1))
  {
   NetError("Could not receive joystick update data.");
   return;
  }
  //printf("%d %02x %02x %02x %02x %02x %02x\n", NSI.total_controllers + 1, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
  switch(buf[NSI.total_controllers])
  {
   case 0: break; // No command

   default: MDFN_DoSimpleCommand(buf[NSI.total_controllers]);break;

   case MDFNNPCMD_SAVESTATE:	
			if(!MDFNNET_SendState())
			{
			 return;
			}
	  	 	break;
   case MDFNNPCMD_LOADSTATE:  
			if(!MDFNNET_RecvState(MDFN_de32lsb(buf)))
			{
			 return;
			}
			MDFN_DispMessage((UTF8 *)_("Remote state loaded."));
			break;
   case MDFNNPCMD_TEXT:
			{
			 uint32 totallen = MDFN_de32lsb(buf);
			 if(totallen > 2000) // Sanity check
			 {
                          NetError("Text length is too long: %d", totallen);
			  return;
			 }
			 uint32 nicklen;
			 uint8 neobuf[totallen + 1];
			 char *textbuf = NULL;
			 if(!MDFND_RecvData(neobuf, totallen))
			 {
			  NetError("Could not receive text data.");
			  return;
			 }
			 nicklen = MDFN_de32lsb(neobuf);
			 if(nicklen > totallen) // Sanity check
			 {
			  NetError("Received nickname length is too long: %d", nicklen);
			  return;
			 }
                         neobuf[totallen] = 0;

			 if(nicklen)
			 {
			  uint8 nickbuf[nicklen + 1];
			  memcpy(nickbuf, neobuf + 4, nicklen);
			  nickbuf[nicklen] = 0;
			  if(OurNick && !strcasecmp(OurNick, (char *)nickbuf))
                           trio_asprintf(&textbuf, "> %s", &neobuf[4 + nicklen]);
			  else
			   trio_asprintf(&textbuf, "<%s> %s", nickbuf, &neobuf[4 + nicklen]);
			 }
		         else
			 {
			  trio_asprintf(&textbuf, "* %s", &neobuf[4]);
			 }
                         MDFND_NetplayText((UTF8*)textbuf);
			 free(textbuf);			
			}
			break;
   case MDFNNPCMD_YOUJOINED:
   case MDFNNPCMD_YOULEFT:
   case MDFNNPCMD_PLAYERLEFT:
   case MDFNNPCMD_PLAYERJOINED:
			{
	                 uint32 len = MDFN_de32lsb(buf);
			 uint8 neobuf[len + 1];
			 char *textbuf = NULL;
			 char mergedstr[] = " merged into:  ";

                         if(!MDFND_RecvData(neobuf, len))
			 {				  
			  NetError("Unable to receive data for join/part message");
			  return;
			 }
			 neobuf[len] = 0; // NULL-terminate the string

			 if(neobuf[1]) // Player is merged?
			 {
			  mergedstr[strlen(mergedstr) - 1] = '1' + (int)rint(log(neobuf[1]) / log(2));
			 }
			 else
			  mergedstr[0] = 0;

			 if(buf[NSI.total_controllers] == MDFNNPCMD_YOULEFT)
			 {
			  // Uhm, not supported yet!
			 }
			 else if(buf[NSI.total_controllers] == MDFNNPCMD_YOUJOINED)
			 {
			  if(OurNick) // This shouldn't happen, really...
			  {
			   free(OurNick);
			   OurNick = NULL;
			  }
			  OurNick = strdup((char*)neobuf + 2);
                          trio_asprintf(&textbuf, _("* You, %s, have connected as player: %s%s%s%s%s%s"), neobuf + 2, (neobuf[0] & 1) ? "1 " : "",
                                       (neobuf[0] & 2) ? "2 " : "", (neobuf[0] & 4) ? "3 " : "", (neobuf[0] & 8) ? "4 " : "", 
				       (neobuf[0] & 0x10) ? "5 " : "", mergedstr);
			 }
			 else if(buf[NSI.total_controllers] == MDFNNPCMD_PLAYERLEFT)
			 {
                                  trio_asprintf(&textbuf, _("* %s has left(player: %s%s%s%s%s%s)"), neobuf + 2, (neobuf[0] & 1) ? "1 " : "",
                                        (neobuf[0] & 2) ? "2 " : "", (neobuf[0] & 4) ? "3 " : "", (neobuf[0] & 8) ? "4 " : "", 
					(neobuf[0] & 0x10) ? "5 " : "", mergedstr);
			 }
			 else
			 {
                                  trio_asprintf(&textbuf, _("* %s has connected as player: %s%s%s%s%s%s"), neobuf + 2, (neobuf[0] & 1) ? "1 " : "",
					(neobuf[0] & 2) ? "2 " : "", (neobuf[0] & 4) ? "3 " : "", (neobuf[0] & 8) ? "4 " : "", 
					(neobuf[0] & 0x10) ? "5 " : "", mergedstr);
			 }
	                 MDFND_NetplayText((UTF8*)textbuf);
			 free(textbuf);
			}
			break;
  }
 } while(buf[NSI.total_controllers]);

 memcpy(joyp, buf, NSI.total_controllers);
}

#endif
