using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using System.Xml;

[System.Serializable]
public enum SystemType
{
	brownDwarf = 0,
	redDwarf = 1,
	yellowDwarf = 2,
	whiteGiant = 3,
	blueGiant = 4,
	neutronStar = 5,
	blackHole = 6,
	redGiant = 7,
	whiteDwarf = 8,
	planetarNebula = 9
}

[System.Serializable]
public enum SpaceObjectType
{
	[XmlEnum("s")]
	star = 0,
	[XmlEnum("p")]
	planet = 1,
	[XmlEnum("g")]
	gasGiant = 2,
	//asteroidBelt = 3,
}

[System.Serializable]
public enum PointType
{
	[XmlEnum("r")]
	resoursePoint = 0, // white - metals, minerals etc
	[XmlEnum("b")]
	biologycalPoint = 1, // green - local spieces
	[XmlEnum("a")]
	artificalPoint = 2, // blue - ruins
	[XmlEnum("e")]
	energyPoint = 3, // vulcans, big radiation, old factories, reactors, active ruins, biological energy sources etc
	[XmlEnum("m")]
	activePoint = 4 // missions - active colonies, bases etc
}

[System.Serializable]
public class StarMap
{
	const string starMapSaveFile = "/map.xml";
	[System.Serializable]
	public class InterestPoint
	{
		[XmlAttribute("id")]
		public int id = 0;
		[XmlIgnore]
		public Vector3 position {
			get { return new Vector3(x / 1000f, y / 1000f, z / 1000f);}
			set { x = (int)(value.x * 1000); y = (int)(value.y * 1000); z = (int)(value.z * 1000);}
		}
		[XmlAttribute("x")]
		public int x = 0;
		[XmlAttribute("y")]
		public int y = 0;
		[XmlAttribute("z")]
		public int z = 0;
		[XmlAttribute("v")]
		public int visible = 0; // 0 - fully visible -5 fully invis
		[XmlAttribute("t")]
		public PointType type = PointType.resoursePoint;

		public InterestPoint(PointType newType, Vector3 newPosition, int visibility = 0)
		{
			type = newType;
			position = newPosition;
			visible = visibility;
		}
		public InterestPoint() {}
	}
	[System.Serializable]
	public class SpaceObject
	{
		[XmlAttribute("n")]
		public string name = "";
		[XmlAttribute("t")]
		public SpaceObjectType type = SpaceObjectType.planet;
		public Color32[] colorKeys = null;
		[XmlAttribute("r")]
		public float size = 1f; // radius of planet or asteroid field size
		[XmlAttribute("d")]
		public float distance = 0; // orbital distance
		[XmlAttribute("a")]
		public float angle = 0; // angle
		[XmlAttribute("s")]
		public int seed = 0; // Generation seed
		[XmlArray("points")]
		[XmlArrayItem("p")]
		public InterestPoint[] points = null;
		public SpaceObject() {}
	}

	[System.Serializable]
	public class StarSystem {
		[XmlAttribute("id")]
		public int index = -1;
		[XmlAttribute("name")]
		public string name = "";
		[XmlIgnore]
		public Vector3 position {
			get { return new Vector3(x / 1000f, y / 1000f, z / 1000f);}
			set { x = (int)(value.x * 1000); y = (int)(value.y * 1000); z = (int)(value.z * 1000);}
		}
		[XmlAttribute("x")]
		public int x = 0;
		[XmlAttribute("y")]
		public int y = 0;
		[XmlAttribute("z")]
		public int z = 0;
		[XmlAttribute("t")]
		public SystemType systemType;// visual
		[XmlArray("obj")]
		[XmlArrayItem("o")]
		public List<SpaceObject> objects = new List<SpaceObject>();

		public StarSystem(int id, Vector3 newPosition)
		{
			position = newPosition;
			systemType = SystemType.yellowDwarf;
			index = id;
			name = "star " + id.ToString();
			GenerateSystem(this, id);
		}
		public StarSystem() {}
	}

	static StarMap instance = null;
	[XmlArray("stars")]
	[XmlArrayItem("s")]
	public List<StarSystem> stars = new List<StarSystem>();

	[XmlIgnore]
	public static List<StarSystem> Stars{
		get {
			return instance.stars;
		}
	}


	static StarMap()
	{ // Star map generation or load
		Load ();
	}

	public StarMap() {}

	public static int GetNearestStar(Vector3 position)
	{
		int nearestId = 0;
		float nearestDistance = (position - Stars [nearestId].position).sqrMagnitude;
		for (int i = 1; i < Stars.Count; i++)
		{
			if (nearestDistance > (position - Stars [i].position).sqrMagnitude)
			{
				nearestDistance = (position - Stars [i].position).sqrMagnitude;
				nearestId = i;
			}
		}
		return nearestId;
	}

	public void GenerateStars()
	{
		stars = new List<StarSystem> ();
		int count = 0;
		Vector3 spaceSize = new Vector3 (1.8f, 1f, 1.8f);
		const int size = 30;
		for (int i = 0; i < size; i++) {
			for (int j = 0; j < size; j++) {
				float distanceFromCenter = Mathf.Sqrt((i - size / 2f) * (i - size / 2f) + (j - size / 2f) * (j - size / 2f));
				if (distanceFromCenter > size / 6)
				{
					if (Random.Range(0, 100f) > 50)
					{
						continue;
					}
				}
				else if (distanceFromCenter > size / 4)
				{
					if (Random.Range(0, 100f) > 20)
					{
						continue;
					}
				}
				else if (distanceFromCenter > size / 2)
				{
					continue;
				}
				else if (Random.Range(0, 100f) > 90)
				{
					continue;
				}
				float shift = 0;
				if (i % 2 == 0)
				{
					shift = 0.5f;
				}
				StarSystem system = new StarSystem (
					count,
					new Vector3 (
					(i - size / 2f) * 3f + Random.Range (-spaceSize.x, spaceSize.x),
					Random.Range (-spaceSize.y, spaceSize.y),
					(shift + j - size / 2f) * 3f + Random.Range (-spaceSize.z, spaceSize.z)));
				count ++;
				stars.Add (system);
			}
		}
	}

	static void GenerateSystem(StarSystem system, int starIndex)
	{
		// add star
		system.objects.Add( new SpaceObject() {name = "Star", type = SpaceObjectType.star, colorKeys = GenerateStarColorKeys()});
		// add medium planets
		for(int i = 0; i < Random.Range(3, 8); i++)
		{
			SpaceObject planet = new SpaceObject();
			planet.name = "planet " + i.ToString();
			planet.type = SpaceObjectType.planet;
			planet.size = Random.Range(0.6f, 1.2f) * 0.4f;
			planet.distance = (i + 1f) * 2f + Random.Range(-0.4f, + 0.4f);
			planet.angle = Random.Range(0f, 360f);
			if (Random.Range(1, 6) == 1)
			{
				planet.size = Random.Range(1.6f, 2.1f) * 0.4f;
				planet.type = SpaceObjectType.gasGiant;
			}
			if (planet.type == SpaceObjectType.planet)
			{
				List<InterestPoint> points = new List<InterestPoint>();
				// add usual resources - 90%
				if (Random.Range(0, 100) < 90)
				{
					for(int r = 0; r < Random.Range(10, 45); r++)
					{
						points.Add(new InterestPoint(PointType.resoursePoint, Random.onUnitSphere * planet.size, Random.Range(0, 2)));
					}
				}
				// add life - 10%
				if (Random.Range(0, 100) < 20)
				{
					for(int r = 0; r < Random.Range(10, 25); r++)
					{
						points.Add(new InterestPoint(PointType.biologycalPoint, Random.onUnitSphere * planet.size, Random.Range(0, 3)));
					}
				}
				// add ruins - 10%
				if (Random.Range(0, 100) < 15)
				{
					for(int r = 0; r < Random.Range(4, 12); r++)
					{
						points.Add(new InterestPoint(PointType.artificalPoint, Random.onUnitSphere * planet.size, Random.Range(0, 4)));
					}
				}
				// add energy - 10%
				if (Random.Range(0, 100) < 10)
				{
					for(int r = 0; r < Random.Range(4, 10); r++)
					{
						points.Add(new InterestPoint(PointType.energyPoint, Random.onUnitSphere * planet.size, Random.Range(0, 4)));
					}
				}
				if (points.Count > 0)
				{
					for(int p = 0; p < points.Count; p++)
					{
						points[p].id = p;
					}
					planet.points = points.ToArray();
				}
			}
			planet.colorKeys = GenerateColorKeys();
			system.objects.Add(planet);
		}
	}

	static Color32[] GenerateColorKeys()
	{
		Color32[] colorKeys = new Color32[Random.Range(3, 8)];
		for (int c = 0; c < colorKeys.Length; c++)
		{
			colorKeys[c] = new Color32((byte)Random.Range(0, 255), (byte)Random.Range(0, 255), (byte)Random.Range(0, 255), 255);
		}
		return colorKeys;
	}
	
	static Color32[] GenerateStarColorKeys()
	{
		Color32[] colorKeys = new Color32[Random.Range(3, 8)];
		for (int c = 0; c < colorKeys.Length; c++)
		{
			byte redGreen = (byte)Random.Range(150, 255);
			byte blue = (byte)Random.Range(150, 255);
			colorKeys[c] = new Color32(redGreen, redGreen, blue, 255);
		}
		return colorKeys;
	}

	public static void Save(string fileName = starMapSaveFile)
	{
		if (instance != null)
		{
			SaveToFile(instance, fileName);
		}
	}
	
	public static void Load(string fileName = starMapSaveFile)
	{
		instance = LoadFromFile (fileName);
		if (instance == null)
		{
			instance = new StarMap();
			instance.GenerateStars();
			Save ();
		}
	}
	
	public static StarMap LoadFromFile(string fileName = starMapSaveFile)
	{
		if (File.Exists (Application.persistentDataPath + fileName))
		{
			XmlSerializer serialReader = new XmlSerializer (typeof(StarMap));
			Stream writer = new FileStream (Application.persistentDataPath + fileName, FileMode.Open);
			var playerInfo = (StarMap)serialReader.Deserialize (writer);
			writer.Close ();
			return playerInfo;
		}
		else
		{
			return null;
		}
	}
	
	public static void SaveToFile (StarMap structure, string fileName = starMapSaveFile)
	{
		XmlSerializer serialWrite = new XmlSerializer(typeof(StarMap));
		XmlTextWriter writer = new XmlTextWriter (Application.persistentDataPath + fileName, System.Text.UTF8Encoding.UTF8);
		writer.Formatting = Formatting.Indented;
		writer.IndentChar = ' ';
		serialWrite.Serialize (writer, structure);
		writer.Close();
	}
}
