using System.Collections.Generic;
using UnityEngine.Networking;
using UnityEngine;
using UnityEngine.Assertions;

public delegate void NetworkReaderDelegate(NetworkReader reader);

public abstract class NetworkManager {
	
	private static bool isAServer = false;
	private static bool connectedToServer = false;

	private static List<Tuple<int, Buffer>> toSend = new List<Tuple<int, Buffer>>();
	private static int reliableChannelID;
	//private static int unreliableChannelID;
	private static int hostID;

	private static List<int> clientIDs = new List<int>();
	private static int serverID;

	private static Dictionary<short, NetworkReaderDelegate> handlers = new Dictionary<short, NetworkReaderDelegate>();

	public static void initConnection() {
		GlobalConfig globalConfig = new GlobalConfig();
		globalConfig.ReactorMaximumReceivedMessages = 10000;
		globalConfig.ReactorMaximumSentMessages = 10000;
		globalConfig.ReactorModel = ReactorModel.SelectReactor;

		NetworkTransport.Init(globalConfig);
		ConnectionConfig config = new ConnectionConfig();
		reliableChannelID  = config.AddChannel(QosType.ReliableSequenced);
		//unreliableChannelID  = config.AddChannel(QosType.UnreliableSequenced);
		HostTopology topology = new HostTopology(config, 64);
		if (isAServer)
			hostID = NetworkTransport.AddHost(topology, 213431);
		else
			hostID = NetworkTransport.AddHost(topology, 213430);

		registerHandler(PacketCode.Message, Actions.writeMessage);
		registerHandler(PacketCode.Move, Actions.move);
		registerHandler(PacketCode.EVA, Actions.EVA);
		registerHandler(PacketCode.Spawn, Actions.spawn);
		registerHandler(PacketCode.ControllerChange, Actions.changeController);
		registerHandler(PacketCode.NewPlayer, Actions.newPlayer);

		registerHandler(PacketCode.RequestMessage, Requests.sendMessage);
		//registerHandler(PacketCode.RequestMove, Requests.move);
		registerHandler(PacketCode.RequestEVA, Requests.EVA);
		registerHandler(PacketCode.RequestControllerChange, Requests.changeController);

		byte error;
		if (!isAServer)
			NetworkTransport.Connect(hostID, "127.0.0.1", 213431, 0, out error);
		else
			GameController.initGame();

		Log.append("Started");
	}

	public static bool manageNetworkInputs() {
		int recHostId;
		int connectionID;
		int channelId;
		byte[] recBuffer = new byte[1024];
		int bufferSize = 1024;
		int dataSize;
		byte error;

		NetworkEventType recData = NetworkTransport.Receive(out recHostId, out connectionID, out channelId, recBuffer, bufferSize, out dataSize, out error);

		if (error != 0 && error != 6) {
			Debug.LogError("Network error : " + error);
		}

		switch (recData) {
			case NetworkEventType.Nothing:
				return false;
			case NetworkEventType.ConnectEvent:
				onConnected(connectionID);
			break;
			case NetworkEventType.DisconnectEvent:
				onDisconnected(connectionID);
			break;
			case NetworkEventType.DataEvent:
				decode(recBuffer, connectionID);
			break;
		}
		return true;
	}

	public static void onConnected(int connectionID) {
		if (isAServer) {
			Log.append("Client connected");
			Debug.Log("Client Connected");
			registerNewPlayer(connectionID);
		}
		else {
			serverID = connectionID;
			Log.append("Client connected");
			Debug.Log("Connected");
		}
	}

	public static void onDisconnected(int connectionID) {
		if (isAServer) {
			Log.append("Client disconnected");
			Debug.Log("Client Connected");
			clientIDs.Remove(connectionID);
		}
		else {
			Debug.Assert(false); //For now server disconnect is not supported
			Log.append("Client disconnected");
			Debug.Log("Disconnected from server");
		}
	}

	public static bool isServer() {
		return isAServer;
	}

	public static bool isConnectedToServer() {
		return connectedToServer;
	}

	private static void registerHandler(short messageType, NetworkReaderDelegate lambda) {
		handlers.Add(messageType, lambda);
	}

	private static void decode(byte[] array, int connectionID) {
		NetworkReader reader = new NetworkReader(array);
		short type = reader.ReadInt16();
		if (type == PacketCode.RequestMove) {
			Requests.move(reader, connectionID);
			return;
		}
		handlers[type].Invoke(reader);
	}

	private static byte[] encode(Buffer buffer) {
		NetworkWriter writer = new NetworkWriter();
		writer.Write(buffer.getID());
		buffer.Serialize(writer);
		return writer.AsArray();
	}

	public static void toServer(Buffer buffer) {
		toSend.Add(new Tuple<int, Buffer>(serverID, buffer));
		tryToSend();
	}

	public static void toClients(Buffer buffer, int exceptConnectionID = -1) {
		foreach(int clientID in clientIDs) {
			if (clientID != exceptConnectionID)
				toSend.Add(new Tuple<int, Buffer>(clientID, buffer));
		}
		tryToSend();
	}

	public static void toClient(Buffer buffer, int clientID) {
		toSend.Add(new Tuple<int, Buffer>(clientID, buffer));
		tryToSend();
	}

	public static void tryToSend() {
		List<Tuple<int, Buffer>> toBeRemoved = new List<Tuple<int, Buffer>>();

		foreach(Tuple<int, Buffer> tuple in toSend) {
			byte error;
			byte[] array = encode(tuple._2);
			NetworkTransport.Send(hostID, tuple._1, reliableChannelID, array, array.Length, out error);

			if (error != 0) {
				break; //otherwise, will not send in order
			}
			else {
				toBeRemoved.Add(tuple);
			}
		}

		foreach(Tuple<int, Buffer> tuple in toBeRemoved) {
			toSend.Remove(tuple);
		}
	}

	private static void registerNewPlayer(int connectionID) {
		// Shared Game Objects
		foreach (GameObject obj in GameController.listSharedGameObjects) {
			SpawnBuffer buffer = new SpawnBuffer();
			buffer.type = obj.GetComponent<SharedGameObject>().type;
			buffer.index = obj.GetComponent<SharedGameObject>().index;

			if (buffer.type == ObjectType.LargeSpaceShip) {
				buffer.position = obj.transform.GetChild(0).position;
				buffer.rotation = obj.transform.GetChild(0).rotation.eulerAngles.z;
			}
			else {
				buffer.position = obj.transform.position;
				buffer.rotation = obj.transform.rotation.eulerAngles.z;
			}

			buffer.id = obj.GetComponent<SharedGameObject>().id;

			if (buffer.type == ObjectType.Character) {
				buffer.parentSpaceShipID = obj.GetComponent<CharController>().largeSpaceShipID;
			}
			else {
				buffer.parentSpaceShipID = -1;
			}

			buffer.sendToClient(connectionID);
		}

		//Players
		foreach (Player player in GameController.players.Values) {
			NewPlayerBuffer buffer = new NewPlayerBuffer();
			buffer.characterID = player.characterController.gameObject.GetComponent<SharedGameObject>().id;
			buffer.playerID = player.id;
			buffer.isSelf = false;
			buffer.sendToClient(connectionID);
		}

		clientIDs.Add(connectionID);

		int characterID = Requests.spawn(ObjectType.Character, 0, new Vector2(1, 0.5f), 0.0f, 0);
		Requests.newPlayer(characterID, connectionID);
	}
}
