/*
 * +================================================================================+
 *      __  ___  ______________  __  __  ______________   ____
 *     / / / / |/ /  _/_  __/\ \/ / / / / /_  __/  _/ /  / __/
 *    / /_/ /    // /  / /    \  / / /_/ / / / _/ // /___\ \
 *    \____/_/|_/___/ /_/     /_/  \____/ /_/ /___/____/___/
 *
 * +================================================================================+
 *
 *    "THE BEER-WARE LICENSE"
 *    -----------------------
 *
 *    As long as you retain this notice you can do whatever you want with this
 *    stuff. However it is expressly forbidden to sell or commercially distribute
 *    Unity Utils or parts of it apart from your games.
 *    If you meet any of the authors some day, and you think this stuff is
 *    worth it, you can buy them a beer in return.
 *
 *    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *    THE SOFTWARE.
 *
 * +================================================================================+
 *
 *    Authors:
 *    David Krummenacher (dave@timbookedtwo.com)
 *
 */

using System.Collections.Generic;
using UnityEngine;

namespace Utils {

	/// <summary>
	/// Allows you to label game objects and get them via the labels.
	/// </summary>
	public class Labeled : MonoBehaviour {

		/// <summary>
		/// A dictionary containg all labels with their game objects.
		/// </summary>
		private static Dictionary<string, List<GameObject>> objects = new Dictionary<string, List<GameObject>>();

		/// <summary>
		/// The labels.
		/// </summary>
		public List<string> Labels;

		void Awake() {
			// Register the component
			foreach(string label in Labels) {
				if (!objects.ContainsKey(label)) objects.Add(label, new List<GameObject>());
				if (!objects[label].Contains(gameObject)) objects[label].Add(gameObject);
			}
		}

		void OnDestroy() {
			// Unregister the component
			foreach(string label in Labels) {
				if (!objects.ContainsKey(label)) {
					objects[label].Remove(gameObject);
					if (objects[label].Count == 0) objects.Remove(label);
				}
			}
		}

		/// <summary>
		/// Logs the whole labels dictionary.
		/// </summary>
		public static void DebugLog() {
			string log = "";

			int keyIndex = 0;
			foreach (KeyValuePair<string, List<GameObject>> pair in objects) {
				log += pair.Key + ": ";

				int valueIndex = 0;
				foreach (GameObject obj in pair.Value) {
					log += obj.name;
					if (valueIndex < pair.Value.Count - 1) log += ", ";
					valueIndex++;
				}

				if (keyIndex < objects.Count - 1) log += "\n";
				keyIndex++;
			}

			Debug.Log(log);
		}

		/// <summary>
		/// Finds the game objects with label.
		/// </summary>
		/// <returns>The game objects.</returns>
		/// <param name="label">The label.</param>
		public static List<GameObject> FindGameObjectsContainingLabel(string label) {
			if (objects.ContainsKey(label)) return objects[label];
			return null;
		}

		/// <summary>
		/// Finds the game objects with labels.
		/// </summary>
		/// <returns>The game objects.</returns>
		/// <param name="labels">The labels.</param>
		public static List<GameObject> FindGameObjectsContainingAnyLabels(string[] labels) {
			List<GameObject> result = new List<GameObject>();

			foreach (string label in labels) {
				foreach(GameObject go in FindGameObjectsContainingLabel(label)) {
					if (!result.Contains(go)) result.Add(go);
				}
			}

			return result;
		}

		/// <summary>
		/// Finds the game objects with labels.
		/// </summary>
		/// <returns>The game objects.</returns>
		/// <param name="labels">The labels.</param>
		public static List<GameObject> FindGameObjectsContainingAnyLabels(List<string> labels) {
			List<GameObject> result = new List<GameObject>();
			
			foreach (string label in labels) {
				foreach(GameObject go in FindGameObjectsContainingLabel(label)) {
					if (!result.Contains(go)) result.Add(go);
				}
			}
			
			return result;
		}

		/// <summary>
		/// Finds only the game objects containg all the labels.
		/// </summary>
		/// <returns>The game objects.</returns>
		/// <param name="labels">The labels.</param>
		public static List<GameObject> FindGameObjectsContainingAllLabels(string[] labels) {
			List<GameObject> result = new List<GameObject>();

			foreach (GameObject go in FindGameObjectsContainingAnyLabels(labels)) {
				bool containsAll = true;

				foreach (string label in labels) {
					if (!go.GetComponent<Labeled>().Labels.Contains(label)) {
						containsAll = false;
						break;
					}
				}

				if (!result.Contains(go) && containsAll) result.Add(go);
			}

			return result;
		}

		/// <summary>
		/// Finds only the game objects containg all the labels.
		/// </summary>
		/// <returns>The game objects.</returns>
		/// <param name="labels">The labels.</param>
		public static List<GameObject> FindGameObjectsContainingAllLabels(List<string> labels) {
			List<GameObject> result = new List<GameObject>();
			
			foreach (GameObject go in FindGameObjectsContainingAnyLabels(labels)) {
				bool containsAll = true;
				
				foreach (string label in labels) {
					if (!go.GetComponent<Labeled>().Labels.Contains(label)) {
						containsAll = false;
						break;
					}
				}
				
				if (!result.Contains(go) && containsAll) result.Add(go);
			}
			
			return result;
		}

		/// <summary>
		/// Compares the label.
		/// </summary>
		/// <returns><c>true</c>, if the label is contained in the labeled component, <c>false</c> otherwise.</returns>
		/// <param name="labeled">The labeled component.</param>
		/// <param name="label">The label to compare.</param>
		public static bool ContainsLabel(Labeled labeled, string label) {
			return labeled.Labels.Contains(label);
		}

		/// <summary>
		/// Compares the labels.
		/// </summary>
		/// <returns><c>true</c>, if any of the labels is in the labeled, <c>false</c> otherwise.</returns>
		/// <param name="labeled">The labeled component.</param>
		/// <param name="labels">The labels to compare.</param>
		public static bool ContainsAnyLabels(Labeled labeled, string[] labels) {
			foreach(string label in labels) {
				if (labeled.Labels.Contains(label)) return true;
			}

			return false;
		}

		/// <summary>
		/// Compares the labels.
		/// </summary>
		/// <returns><c>true</c>, if any of the labels is in the labeled, <c>false</c> otherwise.</returns>
		/// <param name="labeled">The labeled component.</param>
		/// <param name="labels">The labels to compare.</param>
		public static bool ContainsAnyLabels(Labeled labeled, List<string> labels) {
			foreach(string label in labels) {
				if (labeled.Labels.Contains(label)) return true;
			}

			return false;
		}

		/// <summary>
		/// Compares the labels.
		/// </summary>
		/// <returns><c>true</c>, if all labels are in the labeled, <c>false</c> otherwise.</returns>
		/// <param name="labeled">The labeled component.</param>
		/// <param name="labels">The labels to compare.</param>
		public static bool ContainsAllLabels(Labeled labeled, string[] labels) {
			foreach(string label in labels) {
				if (!labeled.Labels.Contains(label)) return false;
			}

			return true;
		}

		/// <summary>
		/// Compares the labels.
		/// </summary>
		/// <returns><c>true</c>, if all labels are in the labeled, <c>false</c> otherwise.</returns>
		/// <param name="labeled">The labeled component.</param>
		/// <param name="labels">The labels to compare.</param>
		public static bool ContainsAllLabels(Labeled labeled, List<string> labels) {
			foreach(string label in labels) {
				if (!labeled.Labels.Contains(label)) return false;
			}
			
			return true;
		}

		/// <summary>
		/// Compares the label.
		/// </summary>
		/// <returns><c>true</c>, if the label is contained in this labeled component, <c>false</c> otherwise.</returns>
		/// <param name="label">The label to compare.</param>
		public bool ContainsLabel(string label) {
			return Labels.Contains(label);
		}
		
		/// <summary>
		/// Compares the labels.
		/// </summary>
		/// <returns><c>true</c>, if any of the labels is in this labeled, <c>false</c> otherwise.</returns>
		/// <param name="labels">The labels to compare.</param>
		public bool ContainsAnyLabels(string[] labels) {
			foreach(string label in labels) {
				if (Labels.Contains(label)) return true;
			}
			
			return false;
		}
		
		/// <summary>
		/// Compares the labels.
		/// </summary>
		/// <returns><c>true</c>, if any of the labels is in this labeled, <c>false</c> otherwise.</returns>
		/// <param name="labels">The labels to compare.</param>
		public bool ContainsAnyLabels(List<string> labels) {
			foreach(string label in labels) {
				if (Labels.Contains(label)) return true;
			}
			
			return false;
		}
		
		/// <summary>
		/// Compares the labels.
		/// </summary>
		/// <returns><c>true</c>, if all labels are in this labeled, <c>false</c> otherwise.</returns>
		/// <param name="labels">The labels to compare.</param>
		public bool ContainsAllLabels(string[] labels) {
			foreach(string label in labels) {
				if (!Labels.Contains(label)) return false;
			}
			
			return true;
		}
		
		/// <summary>
		/// Compares the labels.
		/// </summary>
		/// <returns><c>true</c>, if all labels are in this labeled, <c>false</c> otherwise.</returns>
		/// <param name="labels">The labels to compare.</param>
		public bool ContainsAllLabels(List<string> labels) {
			foreach(string label in labels) {
				if (!Labels.Contains(label)) return false;
			}
			
			return true;
		}
	}
}
