using System;
using System.Collections;
using System.Collections.Generic;
using PlayerInput;
using UnityEngine;
using UnityEngine.SceneManagement;

[SelectionBase]
public class PlayerController : MonoBehaviour {
    public float Speed = 5;
    public float Gravity = 7;

    public enum State
    {
        Walking,
        Raking,
        MakingPile
    }

    private State state = State.Walking;

    private int leafLayer = 0;

    private CharacterController charC;
    private AnimationHandler anim;
	// Use this for initialization
	void Start () {
        charC = GetComponent<CharacterController>();
        anim = GetComponent<AnimationHandler>();
        rakeTimer = new SimpleTimer();
        rakeBackTween = new SimpleTween();
        oldPos = transform.position;

        leafLayer = LayerMask.GetMask("leaf");

    }

    private float velY = 0;

    public float RunCycleTime = 0.8f;
    public float RunFrameTime = 0.3f;

    public float RunFrameSpeedBoost = 1.4f;

    private float runCycle = 0.0f;

    public float PushRadius = 0.6f;
    public float PushForce = 300.0f;
    public float PushPower = 2.0f;

    private float rakeTime = 0.0f;
    public float TimeToPushRake = 0.3f;

    private SimpleTimer rakeTimer;

    private Vector3 rakeDir = new Vector2();

    private Vector3 oldPos = new Vector3();

    private Vector3 playerDir = new Vector3();

    public int[] LeafsForPile;

    private AnimationHandler.Direction rakeAnimDir = AnimationHandler.Direction.UpRight;

    // Update is called once per frame
    void Update () {
        if (Input.GetKeyDown(KeyCode.F5))
        {
            Scene scene = SceneManager.GetActiveScene();
            SceneManager.LoadScene(scene.name);
        }

        Vector3 dir = new Vector3();
        if (input != null)
        {
            if (charC.isGrounded && state == State.Walking)
            {

                dir.x = input.Axis(InputSource<PlayerController>.InputAxis.MoveX);
                dir.z = input.Axis(InputSource<PlayerController>.InputAxis.MoveY);                

                if (dir.magnitude > 0.3f)
                {
                    dir.Normalize();

                    playerDir.x = dir.x;
                    playerDir.z = dir.z;

                    float speed = Speed;
                    if (runCycle < RunFrameTime)
                    {
                        speed *= RunFrameSpeedBoost;
                    }
                    dir *= speed;

                    float angle = Mathf.Atan2(dir.z, dir.x);

                    if (angle < 0) angle += Mathf.PI * 2;

                    if (angle < Mathf.PI / 2.0f) anim.SetDir(AnimationHandler.Direction.UpRight);
                    if (angle < Mathf.PI && angle >= Mathf.PI / 2.0f) anim.SetDir(AnimationHandler.Direction.UpLeft);
                    if (angle < Mathf.PI * 3.0f / 2.0f && angle >= Mathf.PI) anim.SetDir(AnimationHandler.Direction.DownLeft);
                    if (angle >= Mathf.PI * 3.0f / 2.0f) anim.SetDir(AnimationHandler.Direction.DownRight);

                    //Debug.Log("ANGLE: " + angle);
                    runCycle += Time.deltaTime;
                    while (runCycle >= RunCycleTime)
                    {
                        runCycle -= RunCycleTime;
                    }
                    int frame = ((int)Mathf.Floor(runCycle / RunCycleTime * 4.0f) + 1) % 4; //< RunFrameTime ? 1 : 0;
                    anim.SetFrame(frame);
                }
                else
                {
                    dir.x = 0;
                    dir.z = 0;

                    anim.SetFrame(0);
                    runCycle = 0.0f;
                }
            }
        }

        if (state == State.Walking)
        {
            dir.y = velY;

            velY -= Gravity * Time.deltaTime;

            gameObject.layer = LayerMask.NameToLayer("player_controller_no_collide");
            charC.Move(dir * Time.deltaTime);
            gameObject.layer = LayerMask.NameToLayer("player_controller");

            if (charC.isGrounded)
            {
                velY = 0;
            }

            Vector3 moved = transform.position - oldPos;
            if (moved.magnitude > 0.7f * Time.deltaTime)
            {                
                foreach (Collider c in Physics.OverlapSphere(transform.position, PushRadius, leafLayer))
                {
                    LeafPhysics leaf = c.GetComponentInParent<LeafPhysics>();
                    if (leaf != null)
                    {
                        Vector3 toLeaf = leaf.transform.position - transform.position;
                        float dist = toLeaf.magnitude;
                        float power = Mathf.Pow(1.0f - dist / PushRadius, PushPower) * Time.deltaTime * PushForce;
                        //toLeaf.Normalize();
                        toLeaf.y += UnityEngine.Random.Range(0.4f, 1.0f);
                        leaf.Body.AddForce(toLeaf * (9.0f + UnityEngine.Random.Range(0, 9.0f)) * power * Time.deltaTime * 60);
                    }
                }
            }

            Vector3 localRakeDir = new Vector3(input.Axis(InputSource<PlayerController>.InputAxis.RakeX), 0, input.Axis(InputSource<PlayerController>.InputAxis.RakeY));
            if (localRakeDir.magnitude < 0.2f)
            {
                rakeTime = 0;
            }
            else
            {
                if (rakeTime >= 0)
                {
                    rakeTime += Time.deltaTime;
                }
            }

            if (rakeTime >= 0 && localRakeDir.magnitude > 0.7f && rakeTime < TimeToPushRake)
            {
                playerDir = -localRakeDir;
                rakeDir = localRakeDir;
                state = State.Raking;

                float angle = Mathf.Atan2(-rakeDir.z, -rakeDir.x);

                if (angle < 0) angle += Mathf.PI * 2;

                rakeAnimDir = AnimationHandler.Direction.UpRight;

                if (angle < Mathf.PI / 2.0f) rakeAnimDir = AnimationHandler.Direction.UpRight;
                if (angle < Mathf.PI && angle >= Mathf.PI / 2.0f) rakeAnimDir = AnimationHandler.Direction.UpLeft;
                if (angle < Mathf.PI * 3.0f / 2.0f && angle >= Mathf.PI) rakeAnimDir = AnimationHandler.Direction.DownLeft;
                if (angle >= Mathf.PI * 3.0f / 2.0f) rakeAnimDir = AnimationHandler.Direction.DownRight;

                rakeTime = -1;

                anim.SetState("Rake", rakeAnimDir, 0);

                rakeTimer.Start(0.15f, Rake);
            }

            if (state == State.Walking && input.ButtonPressedThisFrame(InputSource<PlayerController>.InputButton.MakePile))
            {
                rakeDir = playerDir;
                state = State.MakingPile;

                float angle = Mathf.Atan2(rakeDir.z, rakeDir.x);

                if (angle < 0) angle += Mathf.PI * 2;

                rakeAnimDir = AnimationHandler.Direction.UpRight;

                if (angle < Mathf.PI / 2.0f) rakeAnimDir = AnimationHandler.Direction.UpRight;
                if (angle < Mathf.PI && angle >= Mathf.PI / 2.0f) rakeAnimDir = AnimationHandler.Direction.UpLeft;
                if (angle < Mathf.PI * 3.0f / 2.0f && angle >= Mathf.PI) rakeAnimDir = AnimationHandler.Direction.DownLeft;
                if (angle >= Mathf.PI * 3.0f / 2.0f) rakeAnimDir = AnimationHandler.Direction.DownRight;

                rakeTime = -1;

                anim.SetState("Rake", rakeAnimDir, 0);

                rakeTimer.Start(0.175f, () =>
                {
                    anim.SetState("Rake", rakeAnimDir, 1);
                    rakeTimer.Start(0.175f, MakePile);
                });
            }
        }

        oldPos = transform.position;

    }

    public float RakeBackDist = 0.8f;
    public float RakeBackTime = 0.2f;

    private SimpleTween rakeBackTween;

    private Vector3 rakeBackStart = new Vector3();
    private Vector3 rakeBackEnd = new Vector3();

    public float RakeReachDist = 0.6f;
    public float RakeReachRadius = 0.6f;

    public float RakeFlyForce = 100;

    private void Rake()
    {
        anim.SetState("Rake", rakeAnimDir, 1);

        Vector3 rakeMove = rakeDir * RakeBackDist;
        rakeBackStart = transform.position;
        charC.Move(rakeMove);
        rakeBackEnd = transform.position;
        transform.position = rakeBackStart;

        rakeBackTween.Init(UpdateRakeBack, SimpleTween.QuadEaseInOut, 0, 1, RakeBackTime);
        rakeBackTween.OnFinish = FinishedRake;

        List<LeafPhysics> leafs = new List<LeafPhysics>();

        int layer = LayerMask.GetMask("leaf");
        foreach (Collider col in Physics.OverlapSphere(rakeBackStart - rakeDir * RakeReachDist, RakeReachRadius, layer))
        {
            LeafPhysics leaf = col.GetComponentInParent<LeafPhysics>();
            if (leaf != null)
            {
                leafs.Add(leaf);
            }
        }

        foreach (LeafPhysics leaf in leafs)
        {
            float rand = UnityEngine.Random.Range(0, 1.0f);
            Vector3 startPos = leaf.transform.position;
            Vector3 toLeaf = startPos - transform.position;
            float dist = Vector3.Dot(toLeaf, -rakeDir);
            dist += UnityEngine.Random.Range(-0.1f, 0.1f);
            Vector3 pull = rakeDir * dist;

            if (rand < 0.65f)
            {
                
                // pull the leaf back, and then destroy
                leaf.Body.isKinematic = true;
                
                pull.x += UnityEngine.Random.Range(-0.05f, 0.05f);
                pull.z += UnityEngine.Random.Range(-0.05f, 0.05f);

                pull = pull * 0.5f + toLeaf.normalized * -0.5f * dist;

                float pullTime = RakeBackTime + UnityEngine.Random.Range(-0.04f, 0.04f);

                SimpleTween tween = new SimpleTween(v =>
                {
                    if (leaf != null)
                    {
                        leaf.transform.position = startPos + pull * v;
                    }
                }, SimpleTween.TweenType.EaseLinear, 0, 1, pullTime);
                tween.OnFinish = () =>
                {
                    if (leaf != null)
                    {
                        leaf.Body.isKinematic = false;
                    }
                    //GameObject.Destroy(leaf.gameObject);
                };
            }
            else if (rand < 0.83f)
            {
                pull.Normalize();
                pull = Quaternion.Euler(UnityEngine.Random.Range(-30.0f, 30.0f), UnityEngine.Random.Range(-30.0f, 30.0f), UnityEngine.Random.Range(-30.0f, 30.0f)) * pull;
                pull.y += 0.3f;

                leaf.Body.AddForce(pull * RakeFlyForce, ForceMode.Impulse);
            }
            else
            {
            }
        }
    }

    public Pile PilePrefab;

    public float PileSearchRadius = 0.7f;

    private List<LeafPhysics> potentialPile = new List<LeafPhysics>();
    private void MakePile()
    {
        anim.SetState("Stand", anim.Dir, 0);
        potentialPile.Clear();
        foreach (Collider c in Physics.OverlapSphere(transform.position + rakeDir * RakeReachDist, PileSearchRadius, leafLayer))
        {
            LeafPhysics leaf = c.GetComponentInParent<LeafPhysics>();
            if (leaf != null)
            {
                potentialPile.Add(leaf);
            }
        }

        int numLeaves = potentialPile.Count;

        bool madePile = false;
        for (int i = LeafsForPile.Length - 1; i >= 0; i--)
        {
            if (numLeaves >= LeafsForPile[i])
            {
                if (UnityEngine.Random.Range(0, 1.0f) < 0.15f)
                {
                    i--;
                    if (i < 0) break;
                    numLeaves = LeafsForPile[i] - 1;
                }

                float pileScale = (float)i / (LeafsForPile.Length - 1) + 0.4f;

                Pile pile = Instantiate<Pile>(PilePrefab);

                pile.NumLeaves = numLeaves;

                pile.InitialPop = pileScale;

                pile.transform.position = transform.position + rakeDir * RakeReachDist + new Vector3(UnityEngine.Random.Range(-0.1f, 0.1f), 0, UnityEngine.Random.Range(-0.1f, 0.1f));

                madePile = true;

                break;
            }
        }

        if (madePile)
        {
            foreach (LeafPhysics leaf in potentialPile)
            {
                if (UnityEngine.Random.Range(0, 1f) < 0.8f)
                {
                    leaf.FadeOut(0.3f);
                }
            }
        }
        else
        {
            foreach (LeafPhysics leaf in potentialPile)
            {
                leaf.Body.AddForce(new Vector3(UnityEngine.Random.Range(-2, 2), 0, UnityEngine.Random.Range(-2, 2)), ForceMode.Impulse);
            }
        }

        FinishedRake();
    }

    private void FinishedRake()
    {
        state = State.Walking;
        anim.SetState("Stand", anim.Dir, 0);
    }

    private void UpdateRakeBack(float val)
    {
        Vector3 pos = transform.position;
        pos.x = rakeBackStart.x * (1 - val) + rakeBackEnd.x * val;
        pos.z = rakeBackStart.z * (1 - val) + rakeBackEnd.z * val;
        transform.position = pos;
    }

    private InputSource<PlayerController> input = null;
    private InputManager.PlayerInfo playerInfo;
    public void SetInput(InputSource<PlayerController> newPlayerInput, InputManager.PlayerInfo playerInfo)
    {
        this.input = newPlayerInput;
        this.playerInfo = playerInfo;
    }
}
