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

public enum ItemType
{
    BuilderWand,
    Wand,
    Potion
}


[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(SphereCollider))]
public class ItemBase : MonoBehaviourPunCallbacks {

	[HideInInspector]
	public GameObject myOwner;
	[HideInInspector]
	public Image background;
	[HideInInspector]
	public Vector3 lastPos, ItemRotation;
    [HideInInspector] 
    public LayerMask layerMask = new LayerMask();
    [HideInInspector]
    public bool inInventory { get; protected set; }
    public bool isEquipped { get; protected set; }
    public bool canStack { get { return m_canStack; } protected set { m_canStack = value; } }
    private bool m_canStack = false;
	[HideInInspector]
    public Rigidbody rBody { get; protected set; }
	[HideInInspector]
	public PlayerBase OwnerPB { get; protected set; }
    [HideInInspector]
    public int stackNumber;
    
    [HideInInspector]
    public ItemType itemType;

    SphereCollider sphereCollider;

    private float m_sizeMod = 1;
    Vector3 parabolaPoint1;
    Vector3 parabolaPoint2;
    Vector3 parabolaPoint3;

    // Use this for initialization
    public void Awake ()
    {
        isEquipped = false;
        lastPos = transform.position;
		RotateByType ();
		rBody = GetComponent<Rigidbody> ();
        sphereCollider = gameObject.GetComponent<SphereCollider>();
        layerMask = LayerMask.GetMask("Environment");
    }
	
	protected void Update ()
    {
		if (!inInventory)
			HandleOnGround ();
    }

    /// <summary>
    /// External call to edit internal variables
    /// Why not make the variable public?
    /// </summary>
    public void EnableSphereCollider()
    {
        sphereCollider.enabled = true;
    }

    /// <summary>
    /// Rotates according to our item type
    /// </summary>
	void RotateByType()
    {
        switch (itemType)
        {
            case (ItemType.Potion):
                ItemRotation = new Vector3(-45f, 0f, 0f);
                break;
            case (ItemType.Wand):
                ItemRotation = new Vector3(50f, 0f, 0f);
                break;
        }
	}

    /// <summary>
    /// Moves an item in a parabola. Used during drop and chest opening routines.
    /// </summary>
    /// <param name="direction"></param>
    /// <returns></returns>
    IEnumerator MoveParabola(Vector3 direction) {
        float timeInParabola = 0.75f;
        Vector3 positionStart, positionTop, positionEnd;

        //Debug.Log(direction);

        positionStart = parabolaPoint1 = transform.position;
        positionTop = parabolaPoint3 = transform.position + Vector3.up * 4 + direction * 2;
        positionEnd = parabolaPoint2 = transform.position + direction * 4;


        // lerp from start to top
        for (float t = 0.0f; t < 1.0f; t += Time.unscaledDeltaTime / (timeInParabola / 2)) {
            transform.position = Vector3.Lerp(positionStart, positionTop, t);
            yield return null;
        }

        // lerp from top to end
        for (float t = 0.0f; t < 1.0f; t += Time.unscaledDeltaTime / (timeInParabola / 2)) {
            transform.position = Vector3.Lerp(positionTop, positionEnd, t);
            yield return null;
        }
    }

    void OnDrawGizmos() {
        Gizmos.color = Color.blue;
        Gizmos.DrawSphere(parabolaPoint1, 0.2f);
        Gizmos.color = Color.red;
        Gizmos.DrawSphere(parabolaPoint2, 0.2f);
        Gizmos.color = Color.green;
        Gizmos.DrawSphere(parabolaPoint3, 0.2f);
    }

    /// <summary>
    /// Handles behavior while on the ground rather than in the inventory
    /// </summary>
	private void HandleOnGround ()
    {
        // Get the radius of our sphere collider
        float _radius = GetComponent<SphereCollider>().radius;
        
        // Create a temporary raycast hit value
        RaycastHit hit;

        // If we hit something...
        if (Physics.Raycast(transform.position + (Vector3.up * _radius), Vector3.down, out hit, _radius * 2, layerMask))
        {
            transform.position = hit.point + Vector3.up * (_radius * 0.95f);
            rBody.velocity = Vector3.zero;
        }
        else
        {
            rBody.AddForce(0f, -12f, 0f);
        }

        // Set the scale of the item
        ScaleByType();

        // Update the rotation of the item
        UpdateRotation(Time.deltaTime);
    }

    /// <summary>
    /// Updates the object's rotation by a factor of 45 degrees
    /// </summary>
    void UpdateRotation(float _rotFactor)
    {
        // Increment the persistent rotation value
        ItemRotation += new Vector3(0f, 45f * _rotFactor, 0f);

        // Apply the rotation to the item
        transform.localEulerAngles = ItemRotation;
    }

    /// <summary>
    /// Scales the gameobject to a default size according to its type
    /// </summary>
    void ScaleByType()
    {
        // Scale according to the type of the item
        switch (itemType)
        {
            // If it's a potion, set the scale to 1
            case (ItemType.Potion):
                m_sizeMod = 1;
                break;
            // If it's a wand, set the scale to 2.25
            case (ItemType.Wand):
                m_sizeMod = 2.25f;
                break;
        }

        // If the global scale is not the proper size...
        if (transform.lossyScale != Vector3.one * m_sizeMod)
            // Set the local scale to the appropriate scale
            transform.localScale = (transform.localScale.x / transform.lossyScale.x) * (Vector3.one * m_sizeMod);
    }

    /// <summary>
    /// Moves the item in a parabolic motion
    /// </summary>
    /// <param name="_forceMagnitude"></param>
    /// <param name="_direction"></param>
    public void AddForceToItem(float _forceMagnitude, Vector3 _direction)
    {
        // If we have no rigidbody, try to acquire it first
		if (!rBody)
			rBody = GetComponent<Rigidbody> ();

        // Start the parabola motion coroutine
        StartCoroutine(MoveParabola(_direction));
    }

    /// <summary>
    /// Sets the equipped state of the item
    /// </summary>
    /// <param name="_equipped"></param>
    public void SetEquipped(bool _equipped)
    {
        isEquipped = _equipped;
    }

    /// <summary>
    /// Handles updating out-of-inventory values and reenabling dropped behaviors
    /// </summary>
    public void HandleDrop()
    {
        inInventory = false;
        isEquipped = false;

        // Enable the itemscript behaviors and collider
        enabled = true;
        EnableSphereCollider();

		// Unparent from the player
        transform.parent = null;

        Debug.Log("No longer parent of player. Position at " + transform.position.ToString());

        // Is this a potion?
        bool _itemIsPotion = gameObject.GetComponent<ItemBase>().itemType == ItemType.Potion;

        // Set size and rotation appropriately
        m_sizeMod = _itemIsPotion ? 1 : 2.25f;
        transform.localEulerAngles = _itemIsPotion ? new Vector3(-45f, 0f, 0f) : new Vector3(50f, 0f, 0f);

        // For potions, set each part of the potion to active again
        if (_itemIsPotion)
            foreach (GameObject part in GetComponent<PotionScript>().potionParts)
                part.SetActive(true);

        // Null the owner field
        myOwner = null;

        // Enable
        gameObject.SetActive(true);
    }

    /// <summary>
    /// Calls remote instances of items to drop from their respective player instances
    /// </summary>
    /// <param name="ownerID"></param>
    /// <param name="itemID"></param>
    /// <param name="forceMagnitude"></param>
    /// <param name="direction"></param>
    [PunRPC] public void DropItem(int ownerID, int itemID, float forceMagnitude, Vector3 direction)
    {
        if (photonView.ViewID != itemID) {
            return;
        }
        GameObject[] players = GameObject.FindGameObjectsWithTag("Player");
        foreach (GameObject player in players) {
            if (player.GetComponent<PlayerBase>().photonView.Owner.ActorNumber == ownerID) {
                player.GetComponent<PlayerBase>().DropItem(gameObject, forceMagnitude, direction);
                EnableSphereCollider();
            }
        }
    }

    /// <summary>
    /// Calls remote instances of item stacks to drop from their respective players
    /// </summary>
    /// <param name="ownerID"></param>
    /// <param name="itemID"></param>
    /// <param name="forceMagnitude"></param>
    /// <param name="direction"></param>
    /// <param name="stack"></param>
    [PunRPC] public void DropItemStack(int ownerID, int itemID, float forceMagnitude, Vector3 direction, int stack)
    {
        if (photonView.ViewID != itemID) {
            return;
        }
        GameObject[] players = GameObject.FindGameObjectsWithTag("Player");
        foreach (GameObject player in players) {
            if (player.GetComponent<PlayerBase>().photonView.Owner.ActorNumber == ownerID) {
                player.GetComponent<PlayerBase>().DropItem(gameObject, forceMagnitude, direction);
                stackNumber = stack;
				if (PhotonNetwork.IsMasterClient && stackNumber == 0) {
					PhotonNetwork.Destroy(photonView);
					return;
				}
                EnableSphereCollider();
                if (itemType == ItemType.Potion) {
                    foreach (GameObject part in gameObject.GetComponent<PotionScript>().potionParts) {
                        part.SetActive(true);
                    }
                }
            }
        }
    }
}
