using System;
using UnityEngine;

public class Buffer {
	private int index;
	private byte[] array;

	public Buffer(PacketCode code, int bufferSize) {
		array = new byte[bufferSize];
		index = 0;

		writeInt((int)code);
	}

	public Buffer(PacketCode code) : this(code, getSize(code)){
	}

	public Buffer(byte[] array) {
		this.array = array;
		index = 0;
	}

	public static int getSize(PacketCode code) {
		switch(code) {
			default:
				return 1024;
				
			case PacketCode.removePath:
				return 8;
			case PacketCode.changePathAndPos:
				return 1024;
			case PacketCode.changeTarget:
				return 12;
			case PacketCode.deployUnit:
				return 8;
			case PacketCode.changeGameSpeed:
				return 8;
			case PacketCode.changePause:
				return 5;
			case PacketCode.destroyUnit:
				return 8;
			case PacketCode.logMessage:
				return 1024;
			case PacketCode.setHealth:
				return 12;
			case PacketCode.setFiring:
				return 9;
			case PacketCode.setDisabled:
				return 9;
			case PacketCode.setNullTarget:
				return 8;
			case PacketCode.setTargetSpeed:
				return 16;
			case PacketCode.setStore:
				return 12;
			case PacketCode.storeUnit:
				return 8;
			case PacketCode.createDeployed:
				return 12;
			case PacketCode.createStored:
				return 1024; // upper bound of 4 + 4 + 84 * 3 * 4
			case PacketCode.createModuleData:
				return 1024; // upper bound of 4 + 4 + 36 * 7 * 4
			case PacketCode.fireMissile:
				return 16;
			case PacketCode.setEMCON:
				return 9;
			case PacketCode.setStoredLoadingState:
				return 12;
			case PacketCode.addToMissileControl:
				return 12;
			case PacketCode.removeFromMissileControl:
				return 12;
			case PacketCode.addToIlluminated:
				return 12;
			case PacketCode.removeFromIlluminated:
				return 12;
			case PacketCode.addToTransferts:
				return 16;
			case PacketCode.removeFromTransferts:
				return 12;
			case PacketCode.setCIWSAmmo:
				return 12;
			case PacketCode.setLoadout:
				return 12;
			case PacketCode.setPositionTarget:
				return 20;

				
			//Requests : not important
		}
	}

	public byte[] getArray() {
		return array;
	}

	public int getLength() {
		return index;
	}

	public void writeBool(bool value) {
		byte[] conversion = BitConverter.GetBytes(value);

		for (int i = 0; i < conversion.Length; i++) {
			array[index + i] = conversion[i];
		}

		index += conversion.Length;
	}

	public void writeInt(int value) {
		byte[] conversion = BitConverter.GetBytes(value);

		for (int i = 0; i < conversion.Length; i++) {
			array[index + i] = conversion[i];
		}

		index += conversion.Length;
	}

	public void writeFloat(float value) {
		byte[] conversion = BitConverter.GetBytes(value);

		for (int i = 0; i < conversion.Length; i++) {
			array[index + i] = conversion[i];
		}

		index += conversion.Length;
	}

	public void writeChar(char value) {
		byte[] conversion = BitConverter.GetBytes(value);

		for (int i = 0; i < conversion.Length; i++) {
			array[index + i] = conversion[i];
		}

		index += conversion.Length;
	}

	public void writeString(string value) {
		writeInt(value.Length);
		foreach(char c in value.ToCharArray()) {
			writeChar(c);
		}
	}

	public void writeVertex(Vertex vertex) {
		writeFloat(vertex.x());
		writeFloat(vertex.y());
		writeFloat(vertex.z());
	}

	public void writeVector(Vector vector) {
		writeFloat(vector.x());
		writeFloat(vector.y());
		writeFloat(vector.z());
	}

	public void writeWaypoint(Waypoint waypoint) {
		if (waypoint is StaticWaypoint) {
			StaticWaypoint staticWaypoint = (StaticWaypoint) waypoint;
			writeChar('a');
			writeVertex(staticWaypoint.getVertex());
		}
		else if (waypoint is UnitWaypoint) {
			UnitWaypoint unitWaypoint = (UnitWaypoint) waypoint;
			writeChar('b');
			writeInt(unitWaypoint.getTarget().getID());
		}
		else if (waypoint is TargetWaypoint) {
			TargetWaypoint targetWaypoint = (TargetWaypoint) waypoint;
			writeChar('c');
			writeInt(targetWaypoint.getUnit().getID());
			writeInt(targetWaypoint.getTarget().getID());
			writeFloat(targetWaypoint.getDistance());
			writeFloat(targetWaypoint.getAltitude());
		}
		else if (waypoint is LandingWaypoint) {
			LandingWaypoint landingWaypoint = (LandingWaypoint) waypoint;
			writeChar('d');
			writeInt(landingWaypoint.getTarget().getID());
			writeFloat(landingWaypoint.getDistance());
			writeFloat(landingWaypoint.getAltitude());
		}
		else {
			Debug.LogError("This type of waypoint cannot be written in buffer");
		}
	}

	public void writePath(Path path) {
		if (path == null) {
			writeBool(false);
		}
		else {
			writeBool(true);
			writeInt(path.size());
			foreach(Waypoint waypoint in path.toList()) {
				writeWaypoint(waypoint);
			}
		}
	}

	public bool readBool() {
		bool value = BitConverter.ToBoolean(array, index);
		index += 1;
		return value;
	}

	public int readInt() {
		int value = BitConverter.ToInt32(array, index);
		index += 4;
		return value;
	}

	public float readFloat() {
		float value = BitConverter.ToSingle(array, index);
		index += 4;
		return value;
	}

	public char readChar() {
		char value = BitConverter.ToChar(array, index);
		index += 2;
		return value;
	}

	public string readString() {
		string value = "";
		int length = readInt();
		for (int i = 0; i < length; i++) {
			value += readChar();
		}

		return value;
	}

	public Vertex readVertex() {
		float x = readFloat();
		float y = readFloat();
		float z = readFloat();

		return new Vertex(x, y, z);
	}

	public Vector readVector() {
		float x = readFloat();
		float y = readFloat();
		float z = readFloat();

		return new Vector(x, y, z);
	}

	public Waypoint readWaypoint() {
		char c = readChar();

		if (c == 'a') {
			Vertex v = readVertex();
			return new StaticWaypoint(v);
		}
		else if (c == 'b') {
			int unitID = readInt();
			return new UnitWaypoint(GameController.fromID(unitID));
		}
		else if (c == 'c') {
			int unitID = readInt();
			int targetID = readInt();
			float distance = readFloat();
			float altitude = readFloat();
			return new TargetWaypoint(GameController.fromID(unitID), GameController.fromID(targetID), distance, altitude);
		}
		else if (c == 'd') {
			int targetID = readInt();
			float distance = readFloat();
			float altitude = readFloat();
			return new LandingWaypoint(GameController.fromID(targetID), distance, altitude);
		}

		Debug.LogError("This type of waypoint is unknown");

		return null;
	}

	public Path readPath() {
		bool isValid = readBool();

		if (!isValid) {
			return null;
		}

		Path path = new Path();

		int size = readInt();
		for (int i = 0; i < size; i++) {
			Waypoint waypoint = readWaypoint();
			path.addWaypoint(waypoint);
		}

		return path;
	}
}
