﻿#if UNITY_EDITOR
using UnityEditor;
#endif

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using System.Linq;
using UnityEngine.SceneManagement;
using PhotonHashtable = ExitGames.Client.Photon.Hashtable;

public class GameGenerator : MonoBehaviour {

    [SerializeField]
    public Generator generator;

	public bool generateLevels = true;

    List<Vector3> levelLocations;
    public List<int> levels;

	// Potions aren't collected like other assets, but are placed on the generator beforehand
	// The generator needs to become a prefab for this to work.
	//[HideInInspector]
	public List<GameObject> potions;

    public static int seedCalls;
    static int oldSeed;
    static int hostSeed;

    List<LootChestScript> lootChests;
    LootChestScript[] arrayOfLoot;


    PhotonView view;

    public GridHandler gridHandler;

	public bool startMatchForced = false;

#if UNITY_EDITOR
    [ContextMenu("Collect Assets")]
    void CollectAssets() {

        generator.Clear();
        generator = new Generator();

        List<string[]> myGuids = new List<string[]>();
        string[] lGuids = AssetDatabase.FindAssets("t:Prefab", new string[] { "Assets/Levels" });
        string[] hGuids = AssetDatabase.FindAssets("t:Prefab", new string[] { "Assets/Hairstyles" });
        string[] wGuids = AssetDatabase.FindAssets("t:Prefab", new string[] { "Assets/Prefabs/Resources/Wands" });
		string[] tlGuids = AssetDatabase.FindAssets("t:Prefab", new string[] { "Assets/Do Not Edit OR Delete/Base Levels" });
        string[] mGuids = AssetDatabase.FindAssets("t:Material", new string[] { "Assets/HairColors" });
		string[] tGuids = AssetDatabase.FindAssets("t:Texture", new string[] { "Assets/Textures/Player Textures" });

        myGuids.Add(lGuids);
        myGuids.Add(hGuids);
        myGuids.Add(wGuids);
		myGuids.Add(tlGuids);
        myGuids.Add(mGuids);
        myGuids.Add(tGuids);

        for (int i = 0; i < myGuids.Count; i++) {
            for (int j = 0; j < myGuids[i].Length; j++) {
                string myAssetPath = AssetDatabase.GUIDToAssetPath(myGuids[i][j]);

                if (i < 4)
                    generator.prefabObjects[i].Add(AssetDatabase.LoadAssetAtPath<GameObject>(myAssetPath));
                else if (i == 4)
                    generator.hairColors.Add(AssetDatabase.LoadAssetAtPath<Material>(myAssetPath));
				else
                    generator.playerTextures.Add(AssetDatabase.LoadAssetAtPath<Texture>(myAssetPath));
            }
        }
    }

#endif

    // Use this for initialization
    void Awake() {
        view = GetComponent<PhotonView>();
        lootChests = new List<LootChestScript>();
		if (CheckForAssets ()) {
            // Generates a seed for exploring maps from the Game Island seed ONLY
			int randomSeed = GenerateSeed ();

			if (view && PhotonNetwork.IsConnected && (SceneManager.GetActiveScene ().name != "Launcher" && SceneManager.GetActiveScene ().name != "BR - Esports Launcher")) {


                GetRoomSeed();

                //SetRandomSeed (oldSeed, "", testGameIsland);
				
                //StartCoroutine(SendRandomSeed(randomSeed));
				//PhotonDebugger.IncrementNumberOfMessages("seed");
				//view.RPC("SetRandomSeed", RpcTarget.OthersBuffered, randomSeed);

				//SetRandomSeed(randomSeed);
				return;
			} else if (!PhotonNetwork.IsConnected && (SceneManager.GetActiveScene ().name != "Launcher" && SceneManager.GetActiveScene ().name != "Esports Launcher")) {
                //Debug.LogError("Player is not connected to Photon; a new seed has been generated. Keep trying to connect to get the room seed unless this is the Unity Editor.");

                #if UNITY_EDITOR
                SetRandomSeed(randomSeed, "");
                #else
                StartCoroutine(WaitForRoomSeed());
                #endif

            }

        }
    }
#if UNITY_EDITOR
	void OnValidate () {
		CollectAssets ();
	}
#endif

    IEnumerator WaitForRoomSeed()
    {
        if (!PhotonNetwork.IsConnected)
        {
            yield return new WaitForSeconds(0.2f);
            if (PhotonNetwork.IsConnected)
            {
                GetRoomSeed();
            }
        }

        yield return null;
    }

    public void SendRandomSeed(int randomSeed) {

        string hostName = PersistentData.localPlayerData.username;

        if (hostName != "")
            Debug.Log("Host is " + hostName + "!");

        PhotonDebugger.IncrementNumberOfMessages("seed");

        // Sends the seed to the room properties to be accessed later.
        //Debug.LogError("Seed Value: " + randomSeed);
        oldSeed = randomSeed;
        PhotonHashtable seedValue = new PhotonHashtable { { "room_seed", randomSeed } };
        PhotonNetwork.CurrentRoom.SetCustomProperties(seedValue);

        //SetRandomSeed(randomSeed, hostName, testGameIsland);
        GetRoomSeed();

        if (!view)
            view = GetComponent<PhotonView>();

        view.RPC("GetRoomSeed", RpcTarget.OthersBuffered);
        //view.RPC("SetRandomSeed", RpcTarget.OthersBuffered, randomSeed, hostName, testGameIsland);
    }

    [PunRPC]
    void GetRoomSeed()
    {
        // Get the seed from the Room Seed property and apply it to the client to begin generating the levels.
        object newSeed;
        PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue("room_seed", out newSeed);
        oldSeed = (int)newSeed;
        //Debug.LogError("Seed: " + newSeed.ToString() + ", Id: " + view.ViewID.ToString());
        Random.InitState((int)newSeed);
        BeginGeneratingLevels();
    }


    [PunRPC]
    void SetRandomSeed(int randomSeed, string hostName) {
        //Debug.LogError("Seed: " + randomSeed.ToString() + ", Id: " + view.ViewID.ToString() + ", Host Username: " + hostName);
        oldSeed = randomSeed;
        if (oldSeed == 0) {
            //Debug.LogError("Client is using an empty seed.");
        }
        Random.InitState(randomSeed);
        
        BeginGeneratingLevels();
    }

    public static int GetOldRandomSeed() {
        return oldSeed;
    }

    public static void SetRandomToOldSeed() {
        Random.InitState(oldSeed);
    }

    public int GenerateSeed() {
        int randomSeedMilli = System.DateTime.Now.Millisecond;
        int randomSeedSecond = System.DateTime.Now.Second;
        int randomSeedMinutes = System.DateTime.Now.Minute;

        string randomSeedString = randomSeedMinutes.ToString("00") + randomSeedSecond.ToString("00") + randomSeedMilli.ToString("0000");

        seedCalls++;

        //Debug.LogError("A seed has been generated " + seedCalls + " times.");

        return int.Parse(randomSeedString);
    }

    void GenerateLevel() {
        //Debug.LogError("Generating levels using seed " + oldSeed);
        levelLocations = new List<Vector3>();

        for (int i = -1; i < 2; i++) {
            for (int j = -1; j < 2; j++) {
                levelLocations.Add(new Vector3(400 * i, 0, 400 * j));
            }
        }

        levels = Enumerable.Range(0, generator.levels.Count).ToList();

        int[] levelIndexes = new int[9];
        for (int k = 0; k < 9; k++) {
            int index = Random.Range(0, levels.Count);
            levelIndexes[k] = levels[index];
            levels.Remove(levels[index]);
        }

        GenerateLevelFromIndexes(levelIndexes);
    }

    void BeginGeneratingLevels() {
        MaterialDrainValue.ResetCurrentMaterialIndex();
        if (generateLevels)
			if (SceneManager.GetActiveScene ().name != "Launcher" && SceneManager.GetActiveScene ().name != "Esports Launcher")
                GenerateLevel();

        StartCoroutine(gridHandler.InitializeGrid());
    }

    void GenerateLevelFromIndexes(int[] indexes) {
        for (int k = 0; k < 9; k++) {
            //Debug.LogError("indexes[" + k + "]: " + indexes[k]);
            GameObject level = Instantiate(generator.levels[indexes[k]], levelLocations[k], Quaternion.Euler(Vector3.zero), transform);
            //generator.levels.Remove(generator.levels[indexes[k]]);
            
            SetLayerAndTagRecursively(level, "Environment");
        }
		transform.position = new Vector3 (-10f, 0f, -10f);
    }

    void SetLayerAndTagRecursively(GameObject obj, string layerName) {
        int layerIndex = LayerMask.NameToLayer(layerName);
        if (null == obj) {
            return;
        }

        obj.layer = layerIndex;
        obj.tag = layerName;

        foreach (Transform child in obj.transform) {
			if (null == child || child.gameObject.layer == LayerMask.NameToLayer ("Level Editor") || child.gameObject.GetComponent<ItemBase>()) {
                continue;
			}
            SetLayerAndTagRecursively(child.gameObject, layerName);
        }
    }

    bool CheckForAssets() {
        if (generator.levels.Count < 9) {
			for (int i = 0; i < generator.testLevels.Count; i++) {
				generator.levels.Add (generator.testLevels [i]);
				if (generator.levels.Count == 9)
					break;
			}
			if (generator.levels.Count < 9) {
				Debug.LogError ("You need at least nine (9) levels to generate a map. Please add more levels (Prefab) to the Assets/Levels folder.");
				return false;
			}
        }
        if (generator.wands.Count < 1) {
            Debug.LogError("You need at least one (1) wand to generate a map. Please add more wands (Prefab) to the Assets/Wands folder.");
			return false;
        }
        if (generator.hairStyles.Count < 1) {
            Debug.LogError("You need at least one (1) hairstyle to generate a player. Please add more hairstyles (Prefab) to the Assets/HairStyles folder.");
			return false;
        }
        if (generator.hairColors.Count < 1) {
            Debug.LogError("You need at least one (1) haircolor to generate a player. Please add more haircolors (Material) to the Assets/HairColors folder.");
			return false;
        }
        if (generator.playerTextures.Count < 1) {
            Debug.LogError("You need at least one (1) player texture to generate a player. Please add more player textures (Texture) to the Assets/Textures/PlayerTextures folder.");
			return false;
        }

		return true;
    }

    // Update is called once per frame
    void Update() {
        // might do something here to automate asset collection
    }


    // LOOT CHEST RPCs
    [PunRPC]
    public void ChestOpened(int chestIndex, int playerID) {

        if (GetChest(chestIndex) != null){
            //Debug.LogError("Chest #" + chestIndex + " opened by Player with ID " + playerID);
        }

        if (lootChests.Count <= 0) {
            GetLootChests();
        }

        LootChestScript chestScript = GetChest(chestIndex);

        if (!chestScript) {
            //Debug.LogError("No chest with ID #" + chestIndex + " found.");
            return;
        }
        chestScript.ReleaseTheItems();
    }

    [PunRPC]
    public void ForceItemsOutOfChest(int chestIndex, int[] viewIDs) {
        if (lootChests.Count <= 0) {
            GetLootChests();
        }
        LootChestScript chestScript = GetChest(chestIndex);
        if (!chestScript)
            return;

        chestScript.ForceItemsOutOfChest(viewIDs);
    }

	[PunRPC]
	public void ForcePlayerDrop() {
		startMatchForced = true;
	}

    LootChestScript GetChest(int chestIndex) {

        if (lootChests.Count <= 0)
        {
            //Debug.LogError("No Chests are in the list");
            return null;
        }

        LootChestScript script = lootChests.Find(chest => chest.chestIndex == chestIndex);

        if (!script) {
            //Debug.LogError("No chest found with the index " + chestIndex + ", checking the array of loot.");
            arrayOfLoot = FindObjectsOfType<LootChestScript>();
            foreach (LootChestScript arrayChest in arrayOfLoot)
            {
                if(arrayChest.chestIndex == chestIndex)
                {
                    //Debug.LogError("Found chest in the array!");
                    return arrayChest;
                }
            }
            //Debug.LogError("Still no chest =[");
            return null;
        }

        return script;
    }

    void GetLootChests() {
        GameObject[] objs = GameObject.FindGameObjectsWithTag("LootChest");
        for(int i = 0; i < objs.Length; i++) {
            LootChestScript script = objs[i].GetComponent<LootChestScript>();
            if(script)
                lootChests.Add(script);
        }

        
    }
}

[System.Serializable]
public class Generator {
	[ReadOnly]
    public List<List<GameObject>> prefabObjects;
	[ReadOnly]
    public List<GameObject> levels, hairStyles, wands, testLevels;

	[ReadOnly]
    public List<Material> hairColors;
	[ReadOnly]
    public List<Texture> playerTextures;


    public Generator() {

        levels = new List<GameObject>();
        hairStyles = new List<GameObject>();
        wands = new List<GameObject>();
		testLevels = new List<GameObject>();

        hairColors = new List<Material>();
        playerTextures = new List<Texture>();

        prefabObjects = new List<List<GameObject>>();
        prefabObjects.Add(levels);
        prefabObjects.Add(hairStyles);
        prefabObjects.Add(wands);
		prefabObjects.Add(testLevels);

    }

    public void Clear() {
        levels.Clear();
        hairStyles.Clear();
		wands.Clear();
		testLevels.Clear();
        hairColors.Clear();
        playerTextures.Clear();
        prefabObjects.Clear();
    }
}
