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

public enum PacketCode {Message = 0, ServerAvailable = 1, isServer = 2, tellServer = 3, tellClient = 4, removePath = 100, changePathAndPos = 101, changeTarget = 102, deployUnit = 103, changeGameSpeed = 104, changePause = 105, destroyUnit = 106, logMessage = 107, setHealth = 108, setFiring = 109, setDisabled = 110, setNullTarget = 111, setTargetSpeed = 112, setStore = 113, storeUnit = 114, createDeployed = 115, createStored = 116, createModuleData = 117, requestMovement = 200, requestChangeTarget = 201, requestOrder = 202, requestChangeGameSpeed = 203, requestChangePause = 204, requestSetDisabled = 205, requestSetNullTarget = 206};

public abstract class NetworkManager {
	private static int selfChannelID;
	private static int hostID;

	private static List<int> clientIDs;
	private static int serverID;
	private static int masterServerID;

	private static bool isAServer = true;

	private static bool connectedToMasterServer = false;
	private static bool connectedToServer = false;

	private static bool connectingToMasterServer = false;
	private static bool connectingToServer = false;

	private static List<int> tempConnections = new List<int>();

	public static void initConnection() {
		NetworkTransport.Init();
		ConnectionConfig config = new ConnectionConfig();
		selfChannelID  = config.AddChannel(QosType.ReliableSequenced);
		HostTopology topology = new HostTopology(config, 64);
		hostID = NetworkTransport.AddHost(topology, 49611);

		byte error;
		masterServerID = NetworkTransport.Connect(hostID, "81.28.207.55", 49610, 0, out error); //connect to master server
	}

	public static void indicateIsServer(bool value) {
		isAServer = value;

		Buffer buffer = new Buffer(PacketCode.isServer);
		buffer.writeBool(value);

		byte error;
		NetworkTransport.Send(hostID, masterServerID, selfChannelID, buffer.getArray(), buffer.getLength(), out error);

		if (value) {
			clientIDs = new List<int>();
		}
	}

	public static void 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);
		switch (recData)
		{
			case NetworkEventType.ConnectEvent:
				if (connectionId == masterServerID) {
					Debug.Log("Connected to master server");
					connectedToMasterServer = true;
				}
				else if (connectionId == serverID) {
					Debug.Log("Connected to server");
					connectedToServer = true;
					Immortal.startGame();
				}
				else  if (!tempConnections.Contains(connectionId) && isAServer) {
					Debug.Log("Client is connected");

					clientIDs.Add(connectionId);

					foreach(DeployedUnit unit in GameController.getDeployedUnits()) {
						Buffer buffer = new Buffer(PacketCode.createDeployed);
						buffer.writeInt(unit.getID());
						buffer.writeString(unit.getClass().getName());

						toClient(connectionId, buffer);
					}

					foreach(StoredUnit unit in GameController.getStoredUnits()) {
						Buffer buffer = new Buffer(PacketCode.createStored);
						buffer.writeInt(unit.getID());
						buffer.writeString(unit.getClass().getName());

						toClient(connectionId, buffer);
					}

					for (int i = 0; i <= GameController.getModuleDatas().Count / 10; i++) {
						Buffer buffer = new Buffer(PacketCode.createModuleData);
						int size = Mathf.Min(10, GameController.getModuleDatas().Count - i * 10);
						buffer.writeInt(size);
						for (int j = 0; j < size; j++) {
							ModuleData data = GameController.getModuleDatas()[i * 10 + j];
							buffer.writeInt(data.getID());
							buffer.writeInt(data.getParent());
							buffer.writeString(data.getTemplate().getUniqueName());
						}
						toClient(connectionId, buffer);
					}

					foreach(DeployedUnit unit in GameController.getDeployedUnits()) {
						Requests.updatePath(unit);
					}
					foreach(StoredUnit unit in GameController.getStoredUnits()) {
						Requests.setStore(unit, unit.getStore());
					}
					foreach(ModuleData data in GameController.getModuleDatas()) {
						Requests.setHealth(data, data.getHealth());
					}
				}
			break;
			case NetworkEventType.DataEvent:
				Buffer received = new Buffer(recBuffer);

				decode(received);
			break;
			case NetworkEventType.DisconnectEvent:
				if (connectionId == masterServerID) {
					Debug.Log("Disconnected from master server");
				}
				else if (connectionId == serverID) {
					Debug.Log("Disconnected from server");
					connectingToServer = false;
				}
			break;
		}
	}

	private static void decode(Buffer buffer) {
		byte error;
		int unitID;

		int code = buffer.readInt();

		switch((PacketCode)code) {
			default:
				Debug.LogError("Unknown message format");
			break;
			case PacketCode.Message:
				string msg = buffer.readString();

				Debug.Log(msg);

			break;
			case PacketCode.ServerAvailable:
				string ip = buffer.readString();
				int port = buffer.readInt();

				Debug.Log("Game server available at " + ip + ":" + port);

				Immortal.addServerToList(ip, port);

			break;
			case PacketCode.tellClient:
				ip = buffer.readString();
				port = buffer.readInt();

				Debug.Log("Sending message to client at " + ip + ":" + port);

				int tempID = NetworkTransport.Connect(hostID, ip, port, 0, out error);
				tempConnections.Add(tempID);
			break;

			case PacketCode.removePath:
				unitID = buffer.readInt();

				GameController.fromID(unitID).removePath();
			break;
				
			case PacketCode.changePathAndPos:
				unitID = buffer.readInt();
				Vertex position = buffer.readVertex();
				float angle = buffer.readFloat();
				Path newPath = buffer.readPath();

				Debug.Log("New path & pos received");

				GameController.fromID(unitID).setPosition(position);
				GameController.fromID(unitID).setAngle(angle);

				if (newPath == null) {
					GameController.fromID(unitID).removePath();
				}
				else {
					GameController.fromID(unitID).setPath(newPath, false);
				}

			break;
			case PacketCode.changeTarget:
				unitID = buffer.readInt();
				int targetID = buffer.readInt();

				Debug.Log("New target received");

				GameController.fromID(unitID).setTarget(GameController.fromID(targetID));

			break;
			case PacketCode.deployUnit:
				int storedUnitID = buffer.readInt();

				Debug.Log("Unit deployed");

				GameController.storedFromID(storedUnitID).deploy();

			break;
			case PacketCode.changeGameSpeed:
				float newGameSpeed = buffer.readFloat();

				Debug.Log("Game speed changed");

				GameController.setGameSpeed(newGameSpeed);

			break;
			case PacketCode.changePause:
				bool pauseValue = buffer.readBool();

				Debug.Log("Paused changed");

				GameController.setPause(pauseValue);

			break;
			case PacketCode.destroyUnit:
				unitID = buffer.readInt();

				Debug.Log("Unit destroyed");

				GameController.destroyUnit(GameController.fromID(unitID));

			break;
			case PacketCode.logMessage:
				string message = buffer.readString();

				Debug.Log("Log message : " + message);

			break;
			case PacketCode.setHealth:
				int moduleDataID = buffer.readInt();
				float newHealth = buffer.readFloat();

				Debug.Log("New module data health received : " + ", " + moduleDataID + " , " + newHealth);

				GameController.moduleDataFromID(moduleDataID).setHealth(newHealth);

			break;
			case PacketCode.setFiring:
				moduleDataID = buffer.readInt();
				bool isFiring = buffer.readBool();

				Debug.Log("New module data firing received : " + ", " + moduleDataID + " , " + isFiring);

				GameController.moduleDataFromID(moduleDataID).setFiring(isFiring);

			break;
			case PacketCode.setDisabled:
				moduleDataID = buffer.readInt();
				bool isDisabled = buffer.readBool();

				Debug.Log("New module data disabled received : " + ", " + moduleDataID + " , " + isDisabled);

				GameController.moduleDataFromID(moduleDataID).setDisabled(isDisabled);

			break;
			case PacketCode.setNullTarget:
				unitID = buffer.readInt();

				Debug.Log("Null target received");

				GameController.fromID(unitID).setTarget(null);

			break;
			case PacketCode.setTargetSpeed:
				unitID = buffer.readInt();
				float newTargetSpeed = buffer.readFloat();

				Debug.Log("Null target speed");

				GameController.fromID(unitID).setTargetSpeed(newTargetSpeed);

			break;
			case PacketCode.setStore:
				storedUnitID = buffer.readInt();
				moduleDataID = buffer.readInt();

				Debug.Log("Stored module changed");

				GameController.storedFromID(storedUnitID).changeStore(GameController.moduleDataFromID(moduleDataID));

			break;
			case PacketCode.storeUnit:
				unitID = buffer.readInt();

				Debug.Log("Unit stored");

				GameController.destroyUnit(GameController.fromID(unitID));
				StoredUnit newStored = new StoredUnit(GameController.fromID(unitID));

			break;
			case PacketCode.createDeployed:
				unitID = buffer.readInt();
				string className = buffer.readString();

				Debug.Log("Deployed unit created");

				GameController.addDeployedUnit(new DeployedUnit(unitID, className));

			break;
			case PacketCode.createStored:
				unitID = buffer.readInt();
				className = buffer.readString();

				Debug.Log("Stored unit created");

				StoredUnit unit = new StoredUnit(unitID, className); //automatically adds the stored unit

			break;
			case PacketCode.createModuleData:
				int size = buffer.readInt();
				for (int i = 0; i < size; i++) {
					moduleDataID = buffer.readInt();
					unitID = buffer.readInt();
					string moduleName = buffer.readString();

					Debug.Log("Module data created : " + moduleDataID);

					ModuleData data = GameController.moduleFromUniqueName(moduleName).getData(unitID); //automatically adds the module data
					data.setID(moduleDataID);

					Unit parentUnit = GameController.unitFromID(unitID);
					parentUnit.addModuleData(data);
				}

			break;


				//REQUESTS
			case PacketCode.requestMovement:
				unitID = buffer.readInt();
				Vertex destination = buffer.readVertex();

				Debug.Log("Movement requested");

				Requests.moveUnit(GameController.fromID(unitID), destination);

			break;
			case PacketCode.requestChangeTarget:
				unitID = buffer.readInt();
				targetID = buffer.readInt();

				Debug.Log("Target change requested");

				Requests.changeTarget(GameController.fromID(unitID), GameController.fromID(targetID));

			break;
			case PacketCode.requestOrder:
				unitID = buffer.readInt();
				string order = buffer.readString();

				Debug.Log("Order requested");

				Requests.unitOrder(GameController.fromID(unitID), order);

			break;
			case PacketCode.requestChangeGameSpeed:
				newGameSpeed = buffer.readFloat();

				Debug.Log("Game speed change requested");

				Requests.changeGameSpeed(newGameSpeed);

			break;
			case PacketCode.requestChangePause:
				pauseValue = buffer.readBool();

				Debug.Log("Pause change requested");

				Requests.changePause(pauseValue);
			break;
			case PacketCode.requestSetDisabled:
				moduleDataID = buffer.readInt();
				isDisabled = buffer.readBool();

 				Debug.Log("New module data disabled request received");

				Requests.disableModule(GameController.moduleDataFromID(moduleDataID), isDisabled);
			break;
			case PacketCode.requestSetNullTarget:
				unitID = buffer.readInt();

				Debug.Log("Null target request received");

				Requests.setNullTarget(GameController.fromID(unitID));
			break;
		}
	}

	public static void connectToServer(Tuple<string, int> info) {
		connectToServer(info._1, info._2);
	}

	public static void connectToServer(string ip, int port) {
		if (connectingToServer) {
			return;
		}

		byte error;

		Buffer buffer = new Buffer(PacketCode.tellServer);
		buffer.writeString(ip);
		buffer.writeInt(port);
		NetworkTransport.Send(hostID, masterServerID, selfChannelID, buffer.getArray(), buffer.getLength(), out error);


		serverID = NetworkTransport.Connect(hostID, ip, port, 0, out error);

		connectingToServer = true;
	}

	public static bool isServer() {
		return isAServer;
	}

	public static bool isConnectedToMasterServer() {
		return connectedToMasterServer;
	}

	public static bool isConnectedToServer() {
		return connectedToServer;
	}

	public static void toServer(Buffer buffer) {

		byte error;
		NetworkTransport.Send(hostID, serverID, selfChannelID, buffer.getArray(), buffer.getLength(),  out error);
	}

	public static void toClients(Buffer buffer) {

		if (clientIDs == null) { //Dev
			clientIDs = new List<int>();
		}

		foreach(int clientID in clientIDs) {
			byte error;
			NetworkTransport.Send(hostID, clientID, selfChannelID, buffer.getArray(), buffer.getLength(),  out error);
		}
	}

	public static void toClient(int clientID, Buffer buffer) {

		if (clientIDs == null) { //Dev
			clientIDs = new List<int>();
		}

		byte error;
		NetworkTransport.Send(hostID, clientID, selfChannelID, buffer.getArray(), buffer.getLength(),  out error);
	}

	public static void addToLog(int unitID, string message) {
		//if unitID belongs to this player, just add to the log
		//else, send it to the right client

		addToLog(message); //temporary
	}

	public static void addToLog(string message) {
		Buffer buffer = new Buffer(PacketCode.logMessage);
		buffer.writeString(message);

		toClients(buffer);

		Debug.Log("Log message : " + message);
	}
}
