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

public enum PacketCode {Message = 0, ServerAvailable = 1, isServer = 2, 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, 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;

	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  {
					Debug.Log("Client is connected");

					clientIDs.Add(connectionId);
				}
			break;
			case NetworkEventType.DataEvent:
				Buffer buffer = new Buffer(recBuffer);

				decode(buffer);
			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.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);
				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:
				unitID = buffer.readInt();
				int moduleDataID = buffer.readInt();
				float newHealth = buffer.readFloat();

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

				GameController.fromID(unitID).setHealth(moduleDataID, newHealth);

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

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

				GameController.fromID(unitID).setFiring(moduleDataID, isFiring);

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

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

				GameController.fromID(unitID).setDisabled(moduleDataID, 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;


				//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:
				unitID = buffer.readInt();
				moduleDataID = buffer.readInt();
				isDisabled = buffer.readBool();

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

				Requests.disableModule(GameController.fromID(unitID), 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(string ip, int port) {
		if (connectingToServer) {
			return;
		}

		byte 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 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);
	}
}
