#include "StdAfx.h"

#include <CryMemoryManager_impl.h>  // FIX ME: for now include here directly to get CryMemoryManager wrapper code
#include <platform_impl.h>
#include <PS3Specific.h>

#include <IGameStartup.h>
#include <IEntity.h>
#include <IGameFramework.h>
#include <IConsole.h>

#if defined(CELL_ARCH_CEB)
#include <network.h>
#endif
#include <netex/net.h>
#include <netex/netset.h>
#include <netex/errno.h>
#include <cell/fs/cell_fs_errno.h>
#include <cell/fs/cell_fs_file_api.h>

#ifndef USE_SYSTEM_THREADS
	#include <pthread.h>
#endif

#include <sys/types.h>
#include <sys/process.h>
#include <sys/synchronization.h>
#include <sys/ppu_thread.h>
#include <sys/spu_initialize.h>
#include <sys/paths.h>


//////////////////////////////////////////////////////////////////////////
int sEmptyStringBuffer[] = { -1, 0, 0, 0 };
template<>
string::StrHeader* string::m_emptyStringData = (string::StrHeader*)&sEmptyStringBuffer;
template<>
wstring::StrHeader* wstring::m_emptyStringData = (wstring::StrHeader*)&sEmptyStringBuffer;

ISystem* g_pSystem = 0;
ISystem *GetISystem()
{
	return g_pSystem;
}
bool g_bProfilerEnabled = false;
//////////////////////////////////////////////////////////////////////////

extern "C" IGameStartup* CreateGameStartup();

#ifdef USE_SYSTEM_THREADS
#define RunGame_EXIT(exitCode) (sys_ppu_thread_exit(exitCode))
#else
#define RunGame_EXIT(exitCode) (pthread_exit((void *)exitCode))
#endif

static sys_netset_id_t ps3NetworkSid = -1;
#define PS3_NETWORK_CONF "network.cfg"
#define PS3_LAUNCHER_CONF "launcher.cfg"

#define CNFS_MOUNTPOINT "/work"

static const char ps3DefaultNetConf[] =
  "type nic\n"
	"dhcp\n"
	"vendor \"SCE\"\n"
	"product \"Gigabit Ethernet\"\n"
	"phy_config auto\n";

extern const size_t ps3_cgserver_hostname_maxsize;
extern char *const ps3_cgserver_hostname;
extern int ps3_cgserver_port;
extern const size_t ps3_cnfs_hostname_maxsize;
extern char *const ps3_cnfs_hostname;
extern int ps3_cnfs_port;
extern const size_t ps3_cnfs_export_maxsize;
extern char *const ps3_cnfs_export;
extern const size_t ps3_basedir_maxsize;
extern char *const ps3_basedir;
extern const size_t ps3_autoload_level_maxsize;
extern char *const ps3_autoload_level;
extern int ps3_trace_fopen;
extern const size_t ps3_report_ignore_maxsize;
extern char *const ps3_report_ignore;

#define PS3_STACKSIZE_DEFAULT (0x4000000)
#define PS3_STACKSIZE_MIN (0x100000)
#define PS3_STACKSIZE_MAX (0x8000000)

static size_t ps3_stacksize = PS3_STACKSIZE_DEFAULT;

static void strip(char *s)
{
	char *p = s, *p_end = s + strlen(s);

	while (*p && isspace(*p)) ++p;
	if (p > s) { memmove(s, p, p_end - s + 1); p_end -= p - s; }
	for (p = p_end; p > s && isspace(p[-1]); --p);
	*p = 0;
}

static void ps3LoadLauncherConfig(void)
{
	char conf_filename[MAX_PATH];
	char line[1024], *eq = 0;
	int n = 0;

	snprintf(conf_filename, sizeof conf_filename - 1,
			"%s/%s", ps3_basedir, PS3_LAUNCHER_CONF);
	conf_filename[sizeof conf_filename - 1] = 0;
	FILE *fp = fopen(conf_filename, "r");
	if (!fp) return;
	while (true)
	{
		++n;
		if (!fgets(line, sizeof line - 1, fp)) break;
		line[sizeof line - 1] = 0;
		strip(line);
		if (!line[0] || line[0] == '#') continue;
		eq = strchr(line, '=');
		if (!eq)
		{
			fprintf(stderr, "'%s': syntax error in line %i\n",
					conf_filename, n);
			exit(EXIT_FAILURE);
		}
		*eq = 0;
		strip(line);
		strip(++eq);
		if (!strcasecmp(line, "cgserver_host"))
		{
			if (strlen(eq) >= ps3_cgserver_hostname_maxsize)
			{
				fprintf(stderr, "'%s', line %i: cgserver_host value too long\n",
						conf_filename, n);
				exit(EXIT_FAILURE);
			}
			strcpy(ps3_cgserver_hostname, eq);
		} else if (!strcasecmp(line, "cgserver_port"))
		{
			int port = atoi(eq);
			if (port < 1 || port > 65535)
			{
				fprintf(stderr, "'%s', line %i: invalid cgserver_port %i\n",
						conf_filename, n, port);
				exit(EXIT_FAILURE);
			}
			ps3_cgserver_port = port;
		} else if (!strcasecmp(line, "cnfs_host"))
		{
#if defined(CELL_ARCH_CEB)
			if (strlen(eq) >= ps3_cnfs_hostname_maxsize)
			{
				fprintf(stderr, "'%s', line %i: cnfs_host value too long\n",
						conf_filename, n);
				exit(EXIT_FAILURE);
			}
			strcpy(ps3_cnfs_hostname, eq);
#else
			fprintf(stderr,
					"'%s', line %i: cnfs_host not supported for DEH type machines\n",
					conf_filename, n);
#endif
		} else if (!strcasecmp(line, "cnfs_port"))
		{
#if defined(CELL_ARCH_CEB)
			int port = atoi(eq);
			if (port < 1 || port > 65535)
			{
				fprintf(stderr, "'%s', line %i: invalid cnfs_port %i\n",
						conf_filename, n, port);
				exit(EXIT_FAILURE);
			}
			ps3_cnfs_port = port;
#else
			fprintf(stderr,
					"'%s', line %i: cnfs_port not supported for DEH type machines\n",
					conf_filename, n);
#endif
		} else if (!strcasecmp(line, "cnfs_export"))
		{
#if defined(CELL_ARCH_CEB)
			if (strlen(eq) >= ps3_cnfs_export_maxsize)
			{
				fprintf(stderr, "'%s', line %i: cnfs_export value too long\n",
						conf_filename, n);
				exit(EXIT_FAILURE);
			}
			strcpy(ps3_cnfs_export, eq);
#else
			fprintf(stderr,
					"'%s', line %i: cnfs_export not supported for DEH type machines\n",
					conf_filename, n);
#endif
		} else if (!strcasecmp(line, "autoload"))
		{
			if (strlen(eq) >= ps3_autoload_level_maxsize)
			{
				fprintf(stderr, "'%s', line %i: autoload value too long\n",
						conf_filename, n);
				exit(EXIT_FAILURE);
			}
			strcpy(ps3_autoload_level, eq);
		} else if (!strcasecmp(line, "stacksize"))
		{
			long stacksize = atol(eq);
			if (stacksize < PS3_STACKSIZE_MIN)
			{
				ps3_stacksize = PS3_STACKSIZE_MIN;
				fprintf(stderr, "'%s', line %i: stacksize too small, using minvalue %zi\n",
						conf_filename, n, ps3_stacksize);
			} else if (stacksize > PS3_STACKSIZE_MAX)
			{
				ps3_stacksize = PS3_STACKSIZE_MAX;
				fprintf(stderr, "'%s', line %i: stacksize too large, using maxvalue %zi\n",
						conf_filename, n, ps3_stacksize);
			} else
			{
				ps3_stacksize = stacksize;
			}
		} else if (!strcasecmp(line, "trace_fopen"))
		{
			ps3_trace_fopen = atoi(eq);
			if (ps3_trace_fopen < 0)
				ps3_trace_fopen = 0;
		} else if (!strcasecmp(line, "report_ignore"))
		{
			if (strlen(eq) >= ps3_report_ignore_maxsize)
			{
				fprintf(stderr, "'%s', line %i: report_ignore value too long\n",
						conf_filename, n);
				exit(EXIT_FAILURE);
			}
			strcpy(ps3_report_ignore, eq);
		} else
		{
			fprintf(stderr, "'%s': unrecognized config variable '%s' in line %i\n",
					conf_filename, line, n);
			exit(EXIT_FAILURE);
		}
	}
	fclose(fp);
}

static int ps3InitializeNetwork(void)
{
	int err = 0;
	char network_conf_filename[MAX_PATH];
	
	// Initialize the network.
	err = sys_net_initialize_network();
	if (err != 0)
	{
		fprintf(stderr, "PS3 network initialization failed, error %i\n", err);
		return -1;
	}

	// Load the network configuration file PS3_NETWORK_CONF.
	snprintf(network_conf_filename, sizeof network_conf_filename - 1,
			"%s/%s", ps3_basedir, PS3_NETWORK_CONF);
	network_conf_filename[sizeof network_conf_filename - 1] = 0;
	FILE *fp = fopen(network_conf_filename, "r");
	char *netConf = 0;
	if (!fp)
	{
		fprintf(stderr, "WARNING: Can not open network configuration file '%s': %s\n",
				network_conf_filename, strerror(errno));
	} else
	{
		size_t netConfSize;
		fseek(fp, 0, SEEK_END);
		netConfSize = (size_t)ftell(fp);
		fseek(fp, 0, SEEK_SET);
		netConf = new char[netConfSize + 1];
		fread(netConf, 1, netConfSize, fp);
		netConf[netConfSize] = 0;
		fclose(fp);
	}

	// Create the network settings.
	ps3NetworkSid = sys_netset_open(
		netConf ? netConf : ps3DefaultNetConf, NULL, 0);
	if (ps3NetworkSid < 0)
	{
		fprintf(stderr, "sys_netset_open() failed (%d, %d)\n",
				ps3NetworkSid, sys_net_errno);
		if (netConf) delete[] netConf;
		return -1;
	}
	err = sys_netset_if_up(ps3NetworkSid, 15000 /* 15sec timeout */, 0);
	if (err < 0)
	{
		fprintf(stderr, "sys_netset_if_up() failed (%d, %d)\n",
				err, sys_net_errno);
		if (netConf) delete[] netConf;
		sys_netset_close(ps3NetworkSid, 0);
		return -1;
	}
	if (netConf) delete[] netConf;
	netConf = NULL;

#if defined(CELL_ARCH_CEB)
	// If CNFS is not configured, then we're done here.
	if (!ps3_cnfs_hostname[0]) return 0;

	// Mount the configured CNFS volume.
	// NOTE: CNFS is no longer supported for Cell SDK 0.8.x!
	CellFsUtilLsp lsp;
	memset(&lsp, 0, sizeof lsp);
	snprintf(lsp.device, sizeof lsp.device,
			"CELL_FS_NETFS://%s:%i/%s",
			ps3_cnfs_hostname, ps3_cnfs_port, ps3_cnfs_export);
	lsp.device[sizeof lsp.device - 1] = 0;
	lsp.partition_id = 0;
	const char *mountOptions[1] = { NULL };
	err = cellFsUtilMount(
			&lsp,
			CELL_FS_NETFS,
			CNFS_MOUNTPOINT,
			CELL_FS_UTIL_BLOCK,
			CELL_FS_UTIL_MOUNT_RW,
			CELL_FS_UTIL_MOUNT_EXCLUSIVE,
			mountOptions);
	if (err != 0)
	{
		fprintf(stderr, "cellFsUtilMount() failed (%d, %d)\n",
				err, sys_net_errno);
		sys_netset_close(ps3NetworkSid, 0);
		return -1;
	}

	// Set the CNFS mountpoint as the path prefix.
	strncpy(ps3_basedir, CNFS_MOUNTPOINT, ps3_basedir_maxsize);
	ps3_basedir[ps3_basedir_maxsize - 1] = 0;
#endif

	return 0;
}

static void ps3FinalizeNetwork(void)
{
	int err = 0;

	if (ps3NetworkSid >= 0)
	{
		err = sys_netset_if_down(ps3NetworkSid, -1, 0);
		if (err < 0)
		{
			fprintf(stderr, "sys_netset_if_down() failed (%d, %d)\n",
				err, sys_net_errno);
		}
		err = sys_netset_close(ps3NetworkSid, 0);
		if (err < 0)
		{
			fprintf(stderr, "sys_netset_close() failed (%d, %d)\n",
				err, sys_net_errno);
		}
		ps3NetworkSid = -1;
	}
	sys_net_finalize_network();
}

int RunGame(const char *commandLine)
{
	int exitCode = 0;

	if (ps3InitializeNetwork() == -1)
	{
		RunGame_EXIT(1);
	}

	sys_spu_initialize(6, 6); // TODO: Michael, please find better a place for SPU initialization

	SSystemInitParams startupParams;
	memset(&startupParams, 0, sizeof(SSystemInitParams));

	startupParams.hInstance = 0;
	startupParams.sLogFileName = "fp_log.txt";
	strcpy(startupParams.szSystemCmdLine, commandLine);

	// create the startup interface
	IGameStartup* pGameStartup = CreateGameStartup();
	const char *const szAutostartLevel
		= ps3_autoload_level[0] ? ps3_autoload_level : NULL;

	if (!pGameStartup)
	{
		fprintf(stderr, "ERROR: Failed to create the GameStartup Interface!\n");
		RunGame_EXIT(1);
	}

	// run the game
	IGame *game = pGameStartup->Init(startupParams);
	if (game)
	{
		exitCode = pGameStartup->Run(szAutostartLevel);
		pGameStartup->Shutdown();
		pGameStartup = 0;
		RunGame_EXIT(exitCode);
	}

	// if initialization failed, we still need to call shutdown
	pGameStartup->Shutdown();
	pGameStartup = 0;
	ps3FinalizeNetwork();

	fprintf(stderr, "ERROR: Failed to initialize the GameStartup Interface!\n");
	RunGame_EXIT(exitCode);

	// Not reached.
	return 0;
}

void ps3InitFileList(void);

// An unreferenced function.  This function is needed to make sure that
// unneeded functions don't make it into the final executable.  The function
// section for this function should be removed in the final linking.
void this_function_is_not_used(void)
{
}

//-------------------------------------------------------------------------------------
// Name: main()
// Desc: The application's entry point
//-------------------------------------------------------------------------------------
int main()
{
	int err;

	ps3LoadLauncherConfig();
	ps3InitFileList();

#ifdef USE_SYSTEM_THREADS
	// sys_ppu_thread and pthread can _not_ be mixed!
	sys_ppu_thread_t gameThread = 0;

	// We'll run the game in a separate thread created with an extended stack size.
  err = sys_ppu_thread_create(
		&gameThread,
		reinterpret_cast<void (*)(uint64_t)>(RunGame),
		(uint64)(UINT_PTR)"",
		0, // Maximum priority
		ps3_stacksize,
		SYS_PPU_THREAD_CREATE_JOINABLE,
		"GameThread");
	assert(err == CELL_OK);
	uint64_t result = 0;
	err = sys_ppu_thread_join(gameThread, &result);
	assert(err == CELL_OK);
#else
	pthread_t gameThread = 0;
	pthread_attr_t gameThreadAttr;

	pthread_attr_init(&gameThreadAttr);
	pthread_attr_setstacksize(&gameThreadAttr, gameThreadStackSize);
	pthread_attr_setdetachstate(&gameThreadAttr, PTHREAD_CREATE_JOINABLE);
	err = pthread_create(
		&gameThread,
		&gameThreadAttr,
		reinterpret_cast<void *(*)(void *)>(RunGame),
		(void *)"");
	assert(err == 0);
	err = pthread_join(gameThread, 0);
	assert(err == 0);
#endif

	//RunGame("");
	return 0;
}

// vim:sw=2:ts=2:si

