/*
Copyright (C) 2002-2022 The Exult Team

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.

*/

// Win32 Only file

#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#if defined(_WIN32) && defined(USE_EXULTSTUDIO)

#	include "servewin32.h"

#	include "ignore_unused_variable_warning.h"

#	include <winsock2.h>

#	include <cstring>  /* For debugging msgs. */
#	include <iostream> /* For debugging msgs. */

using std::cerr;
using std::cout;
using std::endl;

namespace Exult_server {
	bool   gWSInitialized = false;
	SOCKET gDataSocket    = INVALID_SOCKET;
	SOCKET gServerSocket  = INVALID_SOCKET;
	HANDLE hPortFile      = INVALID_HANDLE_VALUE;

	// Statics
	static bool InitializeWinsock() {
		if (gWSInitialized) {
			return true;
		}

		WSADATA   gWsaData;
		const int iResult = WSAStartup(MAKEWORD(2, 2), &gWsaData);
		if (iResult != NO_ERROR) {
			cout << "Error at WSAStartup()" << std::endl;
		} else {
			gWSInitialized = true;
		}

		return gWSInitialized;
	}

	bool OpenPortFile(const char* path, bool writing) {
		// Creates a file to store the port number in that is created
		// If this fails on the client, there isn't a server active
		char filename[MAX_PATH];
		strcpy(filename, path);
		// This flag is NT only, and causes CreateFile to fail in 9x.
		const int sharedel = is_win9x() ? 0 : FILE_SHARE_DELETE;

		// The locking is setup to prevent two servers from running for the same
		// gamedat dir it's also setup so the file is deleted when the server
		// shuts down
		hPortFile = CreateFile(
				filename, writing ? GENERIC_WRITE : GENERIC_READ,
				FILE_SHARE_READ | sharedel | (!writing ? FILE_SHARE_WRITE : 0),
				nullptr, writing ? CREATE_ALWAYS : OPEN_EXISTING,
				FILE_ATTRIBUTE_TEMPORARY
						| (writing ? FILE_FLAG_DELETE_ON_CLOSE : 0),
				nullptr);
		return hPortFile != INVALID_HANDLE_VALUE;
	}

	// Hack functions
	int write(int file, const void* v, unsigned int len) {
		ignore_unused_variable_warning(file);
		return send(gDataSocket, static_cast<const char*>(v), len, 0);
	}

	int read(int file, void* v, unsigned int len) {
		ignore_unused_variable_warning(file);
		if (len == 0) {
			return 0;
		}
		return recv(gDataSocket, static_cast<char*>(v), len, 0);
		/*
		WSABUF buffer;
		buffer.buf = (char*)v;
		buffer.len = len;
		DWORD flags = 0;//MSG_PARTIAL;
		DWORD numberRead = 0;
		WSARecv(gDataSocket,&buffer,1,&numberRead,&flags,0,0);
		return numberRead;
		*/
	}

	int close(int file) {
		ignore_unused_variable_warning(file);
		return 0;
	}

	// Server Functions

	bool create_pipe(const char* path) {
		if (!InitializeWinsock()) {
			return false;
		}

		// Create a socket.
		gServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

		if (gServerSocket == INVALID_SOCKET) {
			cerr << "Error at socket(): " << WSAGetLastError() << std::endl;
			close_pipe();
			return false;
		}

		sockaddr_in service;
		service.sin_family      = AF_INET;
		service.sin_addr.s_addr = inet_addr("127.0.0.1");
		service.sin_port
				= 0;    // Random port so we can have multiple instances of
						// exult studio and exult running... in theory

		if (bind(gServerSocket, reinterpret_cast<SOCKADDR*>(&service),
				 sizeof(service))
			== SOCKET_ERROR) {
			cerr << "bind() failed." << std::endl;
			close_pipe();
			return false;
		}

		// Listen on the socket.
		if (listen(gServerSocket, 1) == SOCKET_ERROR) {
			cerr << "Error listening on socket." << std::endl;
			close_pipe();
			return false;
		}

		int socksize = sizeof(service);
		getsockname(
				gServerSocket, reinterpret_cast<SOCKADDR*>(&service),
				&socksize);

		if (!OpenPortFile(path, true)) {
			cerr << "Error creating temporary file in gamedat dir for port "
					"number."
				 << endl;
			close_pipe();
			return false;
		}

		DWORD numWriten;
		WriteFile(hPortFile, &service.sin_port, 2, &numWriten, nullptr);

		cout << "Opened socket for Exult Server on port "
			 << ntohs(service.sin_port) << endl;

		return true;
	}

	void setup_connect() {
		// Not required
	}

	bool try_connect_to_client(const char* path) {
		ignore_unused_variable_warning(path);
		// returns size of data waiting, -1 if disconnected
		fd_set rfds;
		FD_ZERO(&rfds);
		FD_SET(gServerSocket, &rfds);
		TIMEVAL tv;
		tv.tv_sec  = 0;
		tv.tv_usec = 20000;
		if (!select(0, &rfds, nullptr, nullptr, &tv)) {
			return false;
		}

		// Does accept
		gDataSocket = accept(gServerSocket, nullptr, nullptr);
		return gDataSocket != INVALID_SOCKET;
	}

	void disconnect_from_client() {
		// Does cleanup after client has disconnected, don't need to do anything
		// really...
	}

	void close_pipe() {
		// Close socket
		if (hPortFile != INVALID_HANDLE_VALUE) {
			CloseHandle(hPortFile);
		}
		if (gDataSocket != INVALID_SOCKET) {
			closesocket(gDataSocket);
		}
		if (gServerSocket != INVALID_SOCKET) {
			closesocket(gServerSocket);
		}
		if (gWSInitialized) {
			WSACleanup();
		}

		gDataSocket    = INVALID_SOCKET;
		gServerSocket  = INVALID_SOCKET;
		hPortFile      = INVALID_HANDLE_VALUE;
		gWSInitialized = false;
	}

	// Client Functions
	int try_connect_to_server(const char* path) {
		if (!InitializeWinsock()) {
			return false;
		}

		// Create a socket.
		gDataSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

		if (gDataSocket == INVALID_SOCKET) {
			cerr << "Error at socket(): " << WSAGetLastError() << std::endl;
			close_pipe();
			return false;
		}

		sockaddr_in service;
		service.sin_family      = AF_INET;
		service.sin_addr.s_addr = inet_addr("127.0.0.1");
		service.sin_port        = 0;

		if (!OpenPortFile(path, false)) {
			cerr << "Error opening temporary file in gamedat dir with port "
					"number."
				 << endl;
			close_pipe();
			return false;
		}

		DWORD numRead;
		ReadFile(hPortFile, &service.sin_port, 2, &numRead, nullptr);
		CloseHandle(hPortFile);
		hPortFile = INVALID_HANDLE_VALUE;
		if (numRead != 2) {
			cerr << "Error read temporary file in gamedat dir with port number."
				 << endl;
			close_pipe();
			return false;
		}

		if (connect(gDataSocket, reinterpret_cast<SOCKADDR*>(&service),
					sizeof(service))
			== SOCKET_ERROR) {
			cerr << "Failed to connect." << std::endl;
			close_pipe();
			return false;
		}

		// Is exult studio opening socket and connecting to Exult
		return 1;
	}

	void disconnect_from_server() {
		// Does cleanup after server has gone
		close_pipe();
	}

	// General stuff
	int peek_pipe() {
		// returns size of data waiting, -1 if disconnected
		if (gDataSocket == INVALID_SOCKET) {
			return -1;
		}
		fd_set rfds;
		FD_ZERO(&rfds);
		FD_SET(gDataSocket, &rfds);
		TIMEVAL tv;
		tv.tv_sec  = 0;
		tv.tv_usec = 20000;
		if (select(0, &rfds, nullptr, nullptr, &tv)) {
			char      c;
			const int to_get = recv(gDataSocket, &c, 1, MSG_PEEK);
			if (to_get == 0 || to_get == SOCKET_ERROR) {
				closesocket(gDataSocket);
				gDataSocket = INVALID_SOCKET;
				return -1;
			}
			return to_get;
		}
		return 0;
	}

	bool is_broken() {
		if (gDataSocket == INVALID_SOCKET) {
			return true;
		}
		peek_pipe();
		return gDataSocket == INVALID_SOCKET;
	}

	bool notify_connection_lost() {
		return true;
	}

	bool is_win9x() {
		OSVERSIONINFO ver;
		ver.dwOSVersionInfoSize = sizeof(ver);
		GetVersionEx(&ver);
		// Lumping WinME with the rest of them.
		return ver.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS
			   && ver.dwMajorVersion == 4;
	}

}    // namespace Exult_server

#endif
