using UnityEngine;
using System.Collections;
using System.Linq;
using System.Collections.Generic;

public class PlayerController : MonoBehaviour
{

    public float JumpForce = 1200.0f;

    private Rigidbody2D m_myBody;

    private float fDeltaFromCenter;

    MasterControllerScript mcs;

    private int iJumpCounter = 0;
    private float fHackAssNoJumpDelay = 0.0f;
    public int iCrushedBuildingsCombo = 0;

    public GameObject[] collisionFeet;

    public LayerMask m_whatIsGround;
    private bool bOnGround;

    private float fDamageImmunityTimer = 0.0f;

    private SpriteRenderer m_mySpriteRenderer;

    public Sprite[] idleSprites;
    public Sprite[] injuredSprites;

    private Sprite[] currentSpriteCollection;
    private int iSpriteIndex;
    private float fAnimTimer;

    public GameObject baseProjectile;

    public List<AllyController> listAttachedAllies;

    public ShotInfo Attack;

    private float fTimeFromStartOfJump;
    private float fPeachFloatTimeRemaining;

    private GameObject[] alliesStackedOnMyHead;
    private float fTimerToShuffleStack;

    public GameObject DoubleJumpParticle;
    public GameObject PeachFloatParticle;

    private GameObject boundPeachFloatParticle;

    public float MaxHealth;
    private float fHealthRemaining;

    public Texture texHeartIcon;

    public GUIStyle fontStyle;

    private bool bDead;

    public AudioSource Speakerbox;
    public AudioClip wavGainAlly;
    public AudioClip wavLoseAlly;
    public AudioClip wavGetHurt;
    public AudioClip wavCrushBuilding;
    public AudioClip wavPlayerFire;
    public AudioClip wavShotHitAThing;
    public AudioClip wavPurpleRay;
    public AudioClip wavBossCrushed;


	// Use this for initialization
	void Start ()
	{
	    m_myBody = GetComponent<Rigidbody2D>();
        m_mySpriteRenderer = GetComponent<SpriteRenderer>();

	    currentSpriteCollection = idleSprites;
	    iSpriteIndex = 0;

	    mcs = GameObject.Find("MasterController").GetComponent<MasterControllerScript>();

        listAttachedAllies = new List<AllyController>();

        Attack.Start(false);

        alliesStackedOnMyHead = new GameObject[10];

	    fHealthRemaining = MaxHealth;

	    bDead = false;
	}

    void EndPeachFloat()
    {
        if (boundPeachFloatParticle != null)
        {
            ParticleSystem ps = boundPeachFloatParticle.GetComponent<ParticleSystem>();
            ps.Stop();
            Destroy(boundPeachFloatParticle,2.0f);
            boundPeachFloatParticle = null;
        }

        if( !IsDead())
           m_myBody.isKinematic = false;
    }

    public int GetNextSpotForStackingOnHead()
    {
        for (int t = 0; t < alliesStackedOnMyHead.Count(); t++)
        {
            if (alliesStackedOnMyHead[t] == null)
                return t;

        }

        return -1;
    }

    public void CollapseHeadStack()
    {
        bool bStackNotOrganized = true;
        while( bStackNotOrganized )
        {
            bStackNotOrganized = false;
            for (int t = 0; t < alliesStackedOnMyHead.Count(); t++)
            {
                //if we have an empty spot, move everything down.
                if (alliesStackedOnMyHead[t] == null)
                {
                    for (int idx = t+1; idx < alliesStackedOnMyHead.Count(); idx++)
                    {
                        //if we are actually moving an object, then we're not shuffled yet.
                        if (alliesStackedOnMyHead[idx] != null)
                        {
                            bStackNotOrganized = true;
                            alliesStackedOnMyHead[idx - 1] = alliesStackedOnMyHead[idx];
                            alliesStackedOnMyHead[idx] = null;
                        }
                    }
                }
            }
        }

        PointAllStackedAlliesTowardSpot();

    }

    public void PointAllStackedAlliesTowardSpot()
    {
        Vector3 vBasePos = transform.position;
        vBasePos.y += 1.25f; 

        for (int t = 0; t < alliesStackedOnMyHead.Count(); t++)
        {
            if (alliesStackedOnMyHead[t] != null)
            {
                AllyController ac = alliesStackedOnMyHead[t].GetComponent<AllyController>();
                ac.vStackOnHeadPosition = vBasePos;
                ac.vStackOnHeadPosition.x += (-0.5f + Random.value*1.0f);
                ac.vStackOnHeadPosition.y += (-0.2f + Random.value * 0.4f);
            }

            vBasePos.y += 1.25f;
        }
    }

    public void StackAllyOnHead(GameObject ally)
    {
        AllyController ac = ally.GetComponent<AllyController>();
        int iSpot = GetNextSpotForStackingOnHead();
        if (iSpot == -1)
        {
            ac.AttachToPlayerStyle = AllyController.AttachStyle.orbit_player;
            return;
        }

        alliesStackedOnMyHead[iSpot] = ally;
        PointAllStackedAlliesTowardSpot();
    }

    protected virtual bool UpdateGrounded()
    {
        bOnGround = false;
        foreach (GameObject go in collisionFeet)
        {
            Collider2D[] colliders = Physics2D.OverlapCircleAll(new Vector2(go.transform.position.x, go.transform.position.y), 0.1f, m_whatIsGround); // (groundCollisionCircle.position, fGroundCheckRadius, m_WhatIsGround);
            for (int i = 0; i < colliders.Length; i++)
            {
                if (colliders[i].gameObject != gameObject)
                {
                    //If we crushed a building!
                    CrushableBuilding cb = colliders[i].GetComponent<CrushableBuilding>();
                    if (cb != null )
                    {
                        bool bWasCrushed = m_myBody.velocity.y < 0;

                        cb.Died(bWasCrushed);

                        if (bWasCrushed)
                        {
                            iCrushedBuildingsCombo++;

                            if (iCrushedBuildingsCombo > 1)
                            {
                                GameObject newText = Instantiate(Resources.Load("CrushComboText")) as GameObject;
                                TextMesh tm = newText.GetComponent<TextMesh>();
                                tm.text = "x" + iCrushedBuildingsCombo;
                                Vector3 textPos = transform.position;
                                textPos.y += 2.0f;
                                newText.transform.position = textPos;
                            }

                            float fBoostForce = 400.0f;
                            if (Input.GetKey(KeyCode.UpArrow))
                                fBoostForce *= 1.5f;

                            Vector2 vCurrentVelocity = m_myBody.velocity;
                            vCurrentVelocity.y = 0;
                            m_myBody.velocity = vCurrentVelocity;

                            m_myBody.AddForce(new Vector2(0, fBoostForce));

                            //reset the jump counter
                            fTimeFromStartOfJump = 0.0f;
                            iJumpCounter = 0;
                        }
                    }
                    else if (fHackAssNoJumpDelay <= 0.0f)
                    {
                        bOnGround = true;
                        iJumpCounter = 0;
                    }

                }

            }
        }

        if (bOnGround)
        {
            fTimeFromStartOfJump = 0.0f;
            iCrushedBuildingsCombo = 0;
            //m_myBody.isKinematic = false;
            EndPeachFloat();
        }

        return bOnGround;
    }

    void UpdateSprites()
    {
        if( CantBeDamaged() )
            currentSpriteCollection = injuredSprites;
        else
            currentSpriteCollection = idleSprites;

        if (fAnimTimer > 0.0f)
            return;

        if (currentSpriteCollection == null)
            return;

        if (iSpriteIndex >= currentSpriteCollection.Count())
            iSpriteIndex = 0;

        m_mySpriteRenderer.sprite = currentSpriteCollection[iSpriteIndex];
        iSpriteIndex++;
        fAnimTimer = 0.1f - (0.06f * fDeltaFromCenter);

    }

    public bool CantBeDamaged()
    {
        return fDamageImmunityTimer > 0.0f;
    }

    public void TakeDamage(float fDamageAmount)
    {
        if (CantBeDamaged())
            return;

        fDamageImmunityTimer = 1.0f;

        if (listAttachedAllies.Count > 0)
        {
            int iRandomAlly = Random.Range(0, listAttachedAllies.Count);

            AllyController ac = listAttachedAllies[iRandomAlly];
            GameObject deadAllyGO = ac.gameObject;

            //remove from stack if on a stack
            for(int t=0; t < alliesStackedOnMyHead.Length; t++)
                if (alliesStackedOnMyHead[t] == deadAllyGO)
                    alliesStackedOnMyHead[t] = null;

            ac.Died();

            listAttachedAllies.Remove(ac);

            Speakerbox.PlayOneShot(wavLoseAlly);
        }
        else
        {
            fHealthRemaining -= fDamageAmount;
        }
    }

    public void Died()
    {
        if (bDead)
            return;

        bDead = true;
        if (m_myBody != null)
        {
            m_myBody.isKinematic = false;
            m_myBody.fixedAngle = false;
            m_myBody.AddTorque(Random.value * 8000.0f);
            m_myBody.AddForce(Vector2.up * (600.0f + Random.value * 600.0f));
        }
        gameObject.layer = LayerMask.NameToLayer("NoCollide");
        Destroy(gameObject, 4.0f);

        mcs.OnPlayerDied();
    }

    public bool IsDead()
    {
        return fHealthRemaining <= 0.0f;
    }

    public void OnGUI()
    {
        //Keep mostly upright-- why is this still in OnGUI <3 ?
        Vector3 vEuler = transform.rotation.eulerAngles;
        float fAngleMaybe = vEuler.z;

        if (fAngleMaybe > 180)
        {
            if (fAngleMaybe < 340)
                fAngleMaybe = 340;
        }
        else
        {
            if (fAngleMaybe > 20)
                fAngleMaybe = 20;
        }
        vEuler.z = fAngleMaybe;

        transform.rotation = Quaternion.Euler(vEuler);

        //draw health
        Rect healthRect = new Rect(16,16,32,32);
        for (int t = 0; t < fHealthRemaining; t++)
        {
            GUI.DrawTexture(healthRect, texHeartIcon );
            healthRect.x += 48.0f;
        }

        GUI.Label(new Rect(250, 16, 140, 32), "Score: " + mcs.iScore, fontStyle);
        GUI.Label(new Rect(450, 16, 140, 32), "Crush Chain: x" + iCrushedBuildingsCombo, fontStyle);
        GUI.Label(new Rect(700, 16, 140, 32), "Wave: " + mcs.iWhatWaveIsIt, fontStyle);

        

        /*
        GUI.Label( new Rect(200,64,400,32), "Wave is " + mcs.iWhatWaveIsIt + " | Block Count: " + mcs.iBlockCountThisWave );
        */

    }

    void UpdateAllies()
    {
        foreach (AllyController ac in listAttachedAllies)
        {
            ac.CheckBurstFire();
        }
    }

	// Update is called once per frame
	void Update ()
	{
        if( fHealthRemaining <= 0.0f )
            Died();

	    if (!IsDead())
	    {
	        if (transform.position.y < -5.0f)
	        {
	            Vector3 vFixMe = transform.position;
	            vFixMe.y = 2.0f;
	            transform.position = vFixMe;
	            bOnGround = false;
	        }
	    }

        UpdateSprites();

	    if (!IsDead())
	    {
	        UpdateGrounded();
	        UpdateAllies();
	        Attack.Update();
	    }
	    //tickdown

	    if (fAnimTimer > 0.0f)
	        fAnimTimer -= Time.deltaTime;

	    if (fHackAssNoJumpDelay > 0.0f)
	        fHackAssNoJumpDelay -= Time.deltaTime;

	    if (fDamageImmunityTimer > 0.0f)
	    {
	        fDamageImmunityTimer -= Time.deltaTime;
	        m_mySpriteRenderer.enabled = !m_mySpriteRenderer.enabled;
	    }

	    if (fDamageImmunityTimer <= 0.0f)
	        m_mySpriteRenderer.enabled = true;

	    if (fTimerToShuffleStack > 0.0f)
	        fTimerToShuffleStack -= Time.deltaTime;
        if (fTimerToShuffleStack <= 0.0f || !bOnGround)
        {
            fTimerToShuffleStack = Random.value*3.0f;
            PointAllStackedAlliesTowardSpot();
        }

        if (!bOnGround)
	        fTimeFromStartOfJump += Time.deltaTime;

	    float fControllerChangeRate = 1.0f;

	    if (mcs.bAttractScreen)
	    {
            Vector3 attractPos = transform.position;
            attractPos.x = -3.0f;
            transform.position = attractPos;
	    }

	    if (IsDead() || mcs.bAttractScreen)
	        return;


	    bool bNoInputThisFrame = true;

	    if (Input.GetKey(KeyCode.RightArrow))
	    {
            fDeltaFromCenter += fControllerChangeRate * Time.deltaTime;
	        bNoInputThisFrame = false;
	    }
        if (Input.GetKey(KeyCode.LeftArrow))
        {
            fDeltaFromCenter -= fControllerChangeRate * Time.deltaTime;
            bNoInputThisFrame = false;
        }

	    bool bJump = Input.GetKey(KeyCode.UpArrow);

	    if (mcs.bJimAFK && Random.value > 0.92f)
	        bJump = true;

        //peach floating?
        m_myBody.isKinematic = false;
	    if ( bJump && !bOnGround)
	    {
	        HandlePeachFloatLogic();
	    }
	    
        if ( bJump && iJumpCounter < 2 && fHackAssNoJumpDelay <= 0.0f )
	    {
            HandleJumpLogic();
	    }

	    if (bNoInputThisFrame)
	    {
            //decay delta if necessary 
            if (fDeltaFromCenter > 0.0f)
            {
                fDeltaFromCenter -= Time.deltaTime * fControllerChangeRate * 0.5f;
                if (fDeltaFromCenter < 0.0f)
                    fDeltaFromCenter = 0.0f;
            }
            if (fDeltaFromCenter < 0.0f)
            {
                fDeltaFromCenter += Time.deltaTime * fControllerChangeRate * 0.5f;
                if (fDeltaFromCenter > 0.0f)
                    fDeltaFromCenter = 0.0f;
            }

	    }

	    fDeltaFromCenter = Mathf.Clamp(fDeltaFromCenter, -1.0f, 1.0f);

	    Vector3 pos = transform.position;
	    pos.x = -3.0f + fDeltaFromCenter;
	    transform.position = pos;

        mcs.SetPlayerDeltaFromRootPosition(fDeltaFromCenter);

        //shootings
	    bool bShoot = Input.GetKey(KeyCode.Space);
        if (mcs.bJimAFK && Random.value > 0.94f)
            bShoot = true;

	    if (bShoot)
	    {
            if( Attack.TryFire( transform.position, new Vector2(1,0),12.0f + (5.0f * fDeltaFromCenter) ))
                Speakerbox.PlayOneShot(wavPlayerFire);

	        foreach (AllyController ac in listAttachedAllies)
	            ac.TryAttack();
	    }
    }

    void HandleJumpLogic()
    {
        //can double jump?
        int iJumpAssistCount = 0;
        float fTotalPeachTimeFromAllies = 0.0f;

        foreach( AllyController ac in listAttachedAllies)
        {
            if (ac.AddsJumpPower)
                iJumpAssistCount++;
            if (ac.AddPeachFloatTime)
            {
                if (fTotalPeachTimeFromAllies == 0.0f)
                    fTotalPeachTimeFromAllies = 1.0f;
                else
                {
                    fTotalPeachTimeFromAllies += 0.5f;
                }
            }
        }

        //only double jump if we have assist
        if (iJumpCounter == 1 && iJumpAssistCount < 1)
            return;

        iJumpCounter++;

        float fModifiedJumpForce = JumpForce;
        for (int t = 0; t < iJumpAssistCount; t++)
            fModifiedJumpForce *= 1.1f;

        //First jump
        if (iJumpCounter == 1)
        {
            m_myBody.AddForce(new Vector2(0, fModifiedJumpForce));
            fTimeFromStartOfJump = 0.0f;
            fPeachFloatTimeRemaining = fTotalPeachTimeFromAllies;
        }
        //extra jump
        else
        {
            GameObject djp = Instantiate(DoubleJumpParticle);
            djp.transform.position = transform.position;
            Destroy(djp,1.0f);
            m_myBody.AddForce(new Vector2(0, fModifiedJumpForce * 0.6f));
        }

        fHackAssNoJumpDelay = 0.2f;
    }

    void HandlePeachFloatLogic()
    {
        //if we've been in the air for X seconds,
        //and we're holding jump,
        //counteract the force of gravity... somehow :D
        if (fTimeFromStartOfJump > 0.25f && m_myBody.velocity.y <= 0.0f && fPeachFloatTimeRemaining > 0.0f)
        {
            m_myBody.isKinematic = true;
            fPeachFloatTimeRemaining -= Time.deltaTime;
            if (boundPeachFloatParticle == null)
            {
                boundPeachFloatParticle = Instantiate(PeachFloatParticle);
                boundPeachFloatParticle.transform.position = transform.position;
                boundPeachFloatParticle.transform.parent = transform;
            }

        }
        else
        {
            EndPeachFloat();
        }
    }
}
