﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using Photon.Pun;

[PrimaryObject]
public class LootChestScript : MonoBehaviour {

    Animator anim;                          // References the animator component of the chest
    [HideInInspector]
    public GameGenerator chestGenerator;    // References the gameGenerator script from the level prefab
    public int totalItems;                  // Stores the total number of items our chest will contain
    public bool guaranteeGoldItem;          // Stores whether or not we will force a gold item to spawn
    bool canHaveLoot;                       // Stores whether or not our chest will have loot
    [HideInInspector]
    public List<GameObject> chestItems = new List<GameObject>(), potionItems, greyItems, greenItems, blueItems, purpleItems, goldItems;  // Store index information for spawn tables
    bool opened;                            // Stores whether or not the chest has been opened
    string chestErrorMessage = "";          // Stores the current error message

    PhotonView photonView;                  // Stores the PhotonView for the chest

    [HideInInspector]
    public int chestIndex;                  // Stores the index value of the chest within the level
    [SerializeField] Transform promptHolder;

    // Use this for initialization
    void Awake() {
        SetAllMeshCollidersToIgnoreRaycastsLayer(transform);                            // Sets all chest mesh colliders to ignore raycasts like the build wand.
        opened = false;                                                                 // Chests start closed
		if (!canHaveLoot && SceneManager.GetActiveScene().name != "Tutorial Island") {  // If the chest is marked to be lootable and we are not in the tutorial island
            MakeChildOfNearestLevelPrefab();                                            // Make the chest a child of the nearest level object
        }
        anim = GetComponent<Animator>();                                                // Assign a reference to the chest's animator
        GameObject gameManager = GameObject.FindGameObjectWithTag("Game Generator");    // Assign a reference to the game manager
        if (!gameManager)                                                               // If there is no game manager tagged, find a reference of its type
            chestGenerator = FindObjectOfType<GameGenerator>();
        else {                                                                          // If we found a game manager, get its gameGenerator script component as well as the photon view.
            chestGenerator = gameManager.GetComponent<GameGenerator>();
            photonView = gameManager.GetComponent<PhotonView>();
        }

        InitializeChest();         
        CheckMyParents(transform);                                                      // Check if the loot chest has a parent object and randomizer

        if (!chestGenerator || !canHaveLoot)                                            // If there is no chest generator reference or cannot have loot, end the awake function
            return;

        AddItemsByRarity();                     // Start adding items to list
        AssignChestItemsRandomly();
    }

	void ShowErrorMessage (string error) {
		if (error != chestErrorMessage) {           // If the error received does not match our cached error
			chestErrorMessage = error;              // Cache the error
			Debug.LogError (error);                 // Display the error
			Debug.DebugBreak ();                    // Trigger a breakpoint exception in the script
		} else if (error == chestErrorMessage) {    // If the error received matches our cached error
			Debug.DebugBreak ();                    // Trigger a breakpoint exception in the script
		}
	}

    void SetAllMeshCollidersToIgnoreRaycastsLayer(Transform t) {
        for (int i = 0; i < t.childCount; i++) {                            // For each child of the object's transform
            SetAllMeshCollidersToIgnoreRaycastsLayer(t.GetChild(i));        // Disable their raycast collisions
        }

        MeshCollider collider = t.gameObject.GetComponent<MeshCollider>();  // Get the Mesh Collider component of the object
        if (collider) {                                                     // If a collider is found
            t.gameObject.layer = LayerMask.NameToLayer("Ignore Raycast");   // Disable its raycast collisions as well
        }
    }

    public void MakeChildOfNearestLevelPrefab() {
        GameObject[] objs = GameObject.FindGameObjectsWithTag("Environment");                   // Create a list to hold all environment objects
        GameObject nearest = null;                                                              // Holds the nearest environment object
        float distanceToNearest = Mathf.Infinity;                                               // By default, the object's distance is infinity.
        for (int i = 0; i < objs.Length; i++) {                                                 // For each object tagged as environment, loop
            float distance = Vector3.Distance(objs[i].transform.position, transform.position);  // Get the distance between the chest and the environment object we are currently checking
            distance = Mathf.Abs(distance);                                                     // Make sure that distance is a positive number

            if (distance < distanceToNearest) {                                                 // If the distance is less than the last value of distanceToNearest
                nearest = objs[i];                                                              // Set the current environment object to the nearest object
                distanceToNearest = distance;                                                   // Set the new distanceToNearest to the environment object's distance from the chest for the next check
            }
        }
        if (nearest) {                                                                          // If we find a nearest object
            transform.parent = GetHighestParentWithTag(nearest.transform, "Environment");       // Make the chest a child of the nearest object we found.
        }
    }

    Transform GetHighestParentWithTag(Transform child, string tag) {
        if (child.transform.parent && child.transform.parent.tag == tag)                        // If the object has a parent that has the "Environment" tag
            return GetHighestParentWithTag(child.transform.parent, tag);                        // Return the parent
        else
            return child;                                                                       // Otherwise, return the object
    }


    void OnDrawGizmos() {
        CheckMyParents(transform);                      // Disable looting behaviors on all applicable objects
		if (transform.lossyScale != Vector3.one) {      // If the object has been scaled in any way
			if (transform.parent) {                     // If the transform has a parent
				Transform myParent = transform.parent;  // Store the parent's transform
				transform.parent = null;                // Unparent the transform component
				transform.localScale = Vector3.one;     // Reset the scale of the object
				transform.parent = myParent;            // Make object a child of the parent again
			} else {
				transform.localScale = Vector3.one;     // If there is no parent, reset the scale
			}
		}
    }

    void CheckMyParents(Transform kid) {
		if (!kid.parent && SceneManager.GetActiveScene().name != "Tutorial Island") {                                                   // If the transform argument does not have a parent and we are not in the tutorial island
            canHaveLoot = false;                                                                                                        // Force disable the looting behavior
            ShowErrorMessage("The " + transform.name + " chest is not a child of a Level Prefab. Please click on this chest in the Hierarchy and drag it on top of your Level Prefab.");
        }
		else if (kid.parent && !kid.parent.GetComponent<LootRandomizer>() && SceneManager.GetActiveScene().name != "Tutorial Island") { // If the transform has a parent but does not have a randomizer
            if (!kid.parent.parent) {                                                                                                   // If the parent of transform is not a child object
                canHaveLoot = false;                                                                                                    // Force disable the looting behavior
                ShowErrorMessage("The " + transform.name + " chest is not a child of a Level Prefab. Please click on this chest in the Hierarchy and drag it on top of your Level Prefab.");
            }
            else {
                CheckMyParents(kid.parent);                                                                                             // Check the parent of the transform as well
            }
        }
        else {                                                                                                                          // If the chest has a parent and has a randomizer 
            canHaveLoot = true;                                                                                                         // Force enable the looting behavior
			chestErrorMessage = "";                                                                                                     // Set the cached error message as empty
        }
    }

    private void OnTriggerEnter(Collider other)
    {
        if (opened)
            return;

        if (other.gameObject.tag == "Player")
        {
            PlayerBase _pb = other.gameObject.GetComponent<PlayerBase>();

            if (!_pb)
                return;

            if (_pb.IsNetworkPlayer())
                return;

            _pb.ItemUI.lootChestDisplay = gameObject;
        }
    }

    void OnTriggerStay(Collider col) {
        if (col.gameObject.tag == "Player") {                                                                           // If the collider detects a player
            PhotonView playerView = col.gameObject.GetPhotonView();
            if (playerView && !playerView.IsMine && PhotonNetwork.IsConnected)                                          // If the player has a photon view, is not a host but is connected to multiplayer
                return;                                                                                                 // Return the function



            if (Input.GetKeyDown(KeyCode.E) && !opened) {                                                               // If the player pressed E and is not open
                anim.SetTrigger("OpenChest");                                                                           // Trigger the open chest animation
                if (PhotonNetwork.IsConnected && photonView) {                                                          // If connected to multiplayer and the player has a photon view
                    //Debug.LogError("Chest #" + chestIndex + " opened by Player with ID " + playerView.ViewID);
                    PhotonDebugger.IncrementNumberOfMessages("open chest");
                    photonView.RPC("ChestOpened", RpcTarget.OthersBuffered, chestIndex, playerView.ViewID);             // Tell other clients to open the same chest
                }
            }
        }
    }

    private void OnTriggerExit(Collider other)
    {
        if (other.gameObject.tag == "Player")
        {
            PlayerBase _pb = other.gameObject.GetComponent<PlayerBase>();

            if (!_pb)
                return;

            if (_pb.IsNetworkPlayer())
                return;

            if (_pb.ItemUI.lootChestDisplay == gameObject)
                _pb.ItemUI.lootChestDisplay = null;
        }
    }

    public void ReleaseTheItems() {
        if (opened) {                                                                           // If the chest is already opened
            return;                                                                             // End the function
        }
        if (PhotonNetwork.IsConnected && !PhotonNetwork.IsMasterClient) {                       // If we are a client connecting to a server
            anim.SetTrigger("OpenChest");                                                       // Play the Open Chest animation
            opened = true;                                                                      // Set opened to true
            return;                                                                             // End the function
        }
        anim.SetTrigger("OpenChest");                                                           // Play the Open Chest animation
        opened = true;                                                                          // Set opened to true
        int[] viewIDs = new int[chestItems.Count];                                              // Create a local array for the items the chest will spawn
        for (int i = 0; i < chestItems.Count; i++) {                                            // For each item that the chest will spawn
            Vector3 direction = Quaternion.Euler(0, (i * 45f) + -72f, 0) * transform.forward;   // Get the forward direction of the chest

            Vector3 pos = transform.position;                                                   // Get the position of the chest
            pos.y += 1.75f;                                                                     // Add a Y offset from the chest's position

            GameObject item = null;                                                             // Create a local gameobject reference
            if (PhotonNetwork.IsConnected && PhotonNetwork.IsMasterClient) {                    // If we are hosting a match
                string resourcePath = "";                                                       // Create a local string to store our file path in for this object

                if (chestItems[i].name.Contains("Potion"))                                      // If the name of our current item contains the word Potion
                    resourcePath = "Potions/";                                                  // Append the start of the file path to look in the potions folder
                else
                    resourcePath = "Wands/";                                                    // Otherwise, append the start of the file path to look in the Wands folder

                item = PhotonNetwork.InstantiateSceneObject(resourcePath + chestItems[i].name, pos, chestItems[i].transform.rotation);  // Instantiate the object over the network and store a local reference to it
                viewIDs[i] = item.GetPhotonView().ViewID;                                       // Replace the current network item index with the item
            }
            else if (!PhotonNetwork.IsConnected) {                                              // If we are not connected to photon
                item = Instantiate(chestItems[i], pos, chestItems[i].transform.rotation);       // Simply instantiate the item
            }

            if (item)                                                                               // If we were able to find a valid item
                StartCoroutine(WaitAndForceItemsOut(item.GetComponent<ItemBase>(), direction));   // Queue the item to our coroutine for launching the items out of the box
        }

        if (PhotonNetwork.IsConnected && photonView) {                                                      // If we are connected to a match
            PhotonDebugger.IncrementNumberOfMessages("open chest");                                         // Add the number of "Open Chest" messages for photon tracking
            photonView.RPC("ForceItemsOutOfChest", RpcTarget.OthersBuffered, chestIndex, (object)viewIDs);  // Use an RPC call to launch the items from the same chest on all clients
        }

        ItemUI _itemUI = FindObjectOfType<ItemUI>();

        if (_itemUI)
            if (_itemUI.lootChestDisplay == gameObject)
                _itemUI.lootChestDisplay = null;
    }

    IEnumerator WaitAndForceItemsOut(ItemBase item, Vector3 dir) {
        yield return new WaitForSecondsRealtime(0.5f);  // Wait for half a second
        item.AddForceToItem(450f, dir);                 // Push the items out from the center of the chest in their preset directions
    }

    void InitializeChest() {
        chestItems = new List<GameObject>();    // Create new drop lists
        greyItems = new List<GameObject>();
        greenItems = new List<GameObject>();
        blueItems = new List<GameObject>();
        purpleItems = new List<GameObject>();
        goldItems = new List<GameObject>();

        chestItems.Clear();                     // Clear any information in the lists
        greyItems.Clear();
        greenItems.Clear();
        blueItems.Clear();
        purpleItems.Clear();
        goldItems.Clear();
    }

    void AddItemsByRarity() {
        AddWandsByRarity();     // Add all wands from our files
        AddPotions();           // Add all potions from our files
    }

    void AddWandsByRarity() {
        for (int i = 0; i < chestGenerator.generator.wands.Count; i++) {                                                    // For each wand in our files
            if (!chestGenerator.generator.wands[i]) {                                                                       // If there are no wands
                //Debug.LogError("no wand ?");
                continue;
            }
            if (!chestGenerator.generator.wands[i].GetComponent<NewWandScript>()) {                                            // If there is no wand script on the object
                //Debug.LogError("no wand script?");
                continue;
            }
            if (chestGenerator.generator.wands[i].GetComponent<NewWandScript>().Rarity == Rarity.Basic) {                 // If the rarity of the wand is Basic
                greyItems.Add(chestGenerator.generator.wands[i]);                                                           // Add the wand to the Grey list
            }
            else if (chestGenerator.generator.wands[i].GetComponent<NewWandScript>().Rarity == Rarity.Normal) {           // If the rarity of the wand is Normal
                greenItems.Add(chestGenerator.generator.wands[i]);                                                          // Add the wand to the Green list
            }
            else if (chestGenerator.generator.wands[i].GetComponent<NewWandScript>().Rarity == Rarity.Unique) {           // If the rarity of the wand is Unique
                blueItems.Add(chestGenerator.generator.wands[i]);                                                           // Add the wand to the Blue list
            }
            else if (chestGenerator.generator.wands[i].GetComponent<NewWandScript>().Rarity == Rarity.Super) {            // If the rarity of the wand is Super
                purpleItems.Add(chestGenerator.generator.wands[i]);                                                         // Add the wand to the Purple list
            }
            else if (chestGenerator.generator.wands[i].GetComponent<NewWandScript>().Rarity == Rarity.Extraordinary) {    // If the rarity of the wand is Extraordinary
                goldItems.Add(chestGenerator.generator.wands[i]);                                                           // Add the wand to the Gold list
            }
        }
    }

    void AddPotions() {
        potionItems = chestGenerator.potions;   // Add any generated potions to the potionItems list
    }


    void AssignChestItemsRandomly() {
        int[] rarities = new int[totalItems];                                                   // Create an array of rarities from our totalItems index
        int[] indexes = new int[totalItems];                                                    // Create an index array from our totalItems index

        int potionCount = 0;                                                                    // Create a local integer to track the number of potions

        if (guaranteeGoldItem) {                                                                // If we are forcing a gold item to spawn
            int randomGold = Random.Range(0, goldItems.Count);                                  // Generate a random index ID from our goldItems array
            rarities[0] = (int)Rarity.Extraordinary;                                            // Set the first value in the rarities array to be our randomized gold item
            indexes[0] = randomGold;                                                            // Set the first value in the indexes array to be our randomized gold item value

            if (chestItems == null)

            chestItems.Add(goldItems[randomGold]);                                              // Add the gold item to our chestItems list
            goldItems.Clear();                                                                  // Clear the goldItems list so that no more can spawn
        }

        while (chestItems.Count != totalItems) {                                                // While the number of items in our chest does not equal our totalItems value
            int potionCheck = (potionCount < 2) ? Random.Range(0, 4) : 0;                       // Check if there will be any potions generated in the chest

            int randomVal = (guaranteeGoldItem) ? Random.Range(1, 96) : Random.Range(1, 101);   // Limit the range of randomly selected items if we have already spawned a gold item
            if (randomVal <= 35 && greyItems.Count != 0) {                                      // If the random spawn value is at or below 35 and we have any grey items
                randomVal = Random.Range(0, greyItems.Count);                                   // Select a random index value from our greyItems list
                rarities[chestItems.Count] = (int)Rarity.Basic;                                 // Add the item to the rarities and indexes arrays
                indexes[chestItems.Count] = randomVal;
                AssignChestItem(Rarity.Basic, randomVal);                                       // Assign the item to the chest
            }
            else if (randomVal <= 65 && greenItems.Count != 0) {                                // If the random spawn value is at or above 65 and we have at least one green item in our index
                if (potionCheck == 3) {                                                         // If the random potion value is 3
                    AddItemToChestFromList(ref potionItems, 0, false);                          // Add the potion item to the chest index
                    potionCount++;                                                              // Increment the number of potions currently in the chest
                    continue;                                                                   // Return to the top of the loop
                }
                randomVal = Random.Range(0, greenItems.Count);                                  // Select a random index value from our greenItems list
                rarities[chestItems.Count] = (int)Rarity.Normal;                                // Add the item to the rarities and indexes arrays
                indexes[chestItems.Count] = randomVal;
                AssignChestItem(Rarity.Normal, randomVal);                                      // Assign the item to the chest
            }
            else if (randomVal <= 85 && blueItems.Count != 0) {                                 // If the random spawn value is at or above 85 and we have at least one blue item in our index
                if (potionCheck == 3) {                                                         // Repeat as above
                    AddItemToChestFromList(ref potionItems, 1, false);
                    potionCount++;
                    continue;
                }
                randomVal = Random.Range(0, blueItems.Count);
                rarities[chestItems.Count] = (int)Rarity.Unique;
                indexes[chestItems.Count] = randomVal;
                AssignChestItem(Rarity.Unique, randomVal);
            }
            else if (randomVal <= 95 && purpleItems.Count != 0) {                               // If the random spawn value is at or above 95 and we have at least one purple item in our index
                if (potionCheck == 3) {                                                         // Repeat as above
                    AddItemToChestFromList(ref potionItems, 2, false);
                    potionCount++;
                    continue;
                }
                randomVal = Random.Range(0, purpleItems.Count);
                rarities[chestItems.Count] = (int)Rarity.Super;
                indexes[chestItems.Count] = randomVal;
                AssignChestItem(Rarity.Super, randomVal);
            }
            else if (goldItems.Count != 0) {                                                    // If have at least one gold item in our index
                if (potionCheck == 3) {                                                         // Repeat as above
                    AddItemToChestFromList(ref potionItems, 3, false);
                    potionCount++;
                    continue;
                }
                randomVal = Random.Range(0, goldItems.Count);
                rarities[chestItems.Count] = (int)Rarity.Extraordinary;
                indexes[chestItems.Count] = randomVal;
                AssignChestItem(Rarity.Extraordinary, randomVal);
            }
            else {
                break;                                                                          // If we have no remaining items to spawn, break the loop
            }
        }

        //if (PhotonNetwork.IsConnected && PhotonNetwork.IsMasterClient && photonView) {
        //    photonView.RPC("AssignChestItemsRemote", RpcTarget.OthersBuffered, (object)rarities, (object)indexes);
        //}
    }

    void AssignChestItemsRemote(int[] rarities, int[] indexes) {    // This is an unused function used in testing to spawn all possible items from a single chest
        for (int i = 0; i < rarities.Length; i++) {                 // For each rarity type
            AssignChestItem((Rarity)rarities[i], indexes[i]);       // Assign the indexed item from the rarity list to the chest
        }
    }

    void AssignChestItem(Rarity rarity, int index) {
        switch (rarity) {                                               // Depending on the rarity
            case Rarity.Basic:
                AddItemToChestFromList(ref greyItems, index, true);     // Find the matching item from our item color array and add the item to the chest list
                break;
            case Rarity.Normal:
                AddItemToChestFromList(ref greenItems, index, true);
                break;
            case Rarity.Unique:
                AddItemToChestFromList(ref blueItems, index, true);
                break;
            case Rarity.Super:
                AddItemToChestFromList(ref purpleItems, index, true);
                break;
            case Rarity.Extraordinary:
                AddItemToChestFromList(ref goldItems, index, true);
                break;
        }
    }

    void AddItemToChestFromList(ref List<GameObject> rarityItemsList, int index, bool removeItem) {
        chestItems.Add(rarityItemsList[index]);             // Add the item prefab to the chestItems list
        if (removeItem) {                                   // If we are forcing unique items per spawn
            rarityItemsList.Remove(rarityItemsList[index]); // Remove the item we've added from our list of possible spawns
        }
    }

    public void ForceItemsOutOfChest(int[] viewIDs) {                                               // This function can only be called over a network
        anim.SetTrigger("OpenChest");                                                               // Trigger the Open Chest animation
        if (PhotonNetwork.IsConnected && !PhotonNetwork.IsMasterClient) {                           // If we are a client connecting to a lobby
            for (int i = 0; i < viewIDs.Length; i++) {                                              // For each item we're sending over the network
                PhotonView view = PhotonView.Find(viewIDs[i]);                                      // Create a reference for that item
                if (!view) {                                                                        // If this is not a valid network item
                    //Debug.Log("Passed an invalid viewID");
                }
                GameObject item = view.gameObject;                                                  // Create a reference to the gameobject from its network ID

                Vector3 direction = Quaternion.Euler(0, (i * 45f) + -72f, 0) * transform.forward;   // Create a local vector for the item to face
                StartCoroutine(WaitAndForceItemsOut(item.GetComponent<ItemBase>(), direction));   // Queue the item to our coroutine for launching the items out of the box
            }
        }
    }

    [ExecuteInEditMode]
    void OnValidate() {
        if (totalItems > 5)     // Clamps the number of items that can be in a chest
            totalItems = 5;
        if (totalItems < 2)
            totalItems = 2;
    }
}
