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

public class Player : MonoBehaviour
{
    private Item netItem;
    public bool ForceLocal;

    public bool IsLocal
    {
        get
        {
            return ForceLocal || (netItem != null && netItem.IsLocal);
        }
    }

    private UIHookup uiHookup;

    private CharacterController charControl;

    private PlayerFade playerFade;
    private Vector3 fadeSpot;
    private Camera playerFadeCam;
    private Transform fadeCamRoot;
    private Vector3 startCamShift;
    private Vector3 endCamShift;
    private float camHeight;
    private float camOffsetZ;

    public float DefaultMaxHealth = 1;
    private float maxHealth = 0;
    private float health = 0;

    private float cooldownBaseScale = 1;

    public GameObject MeshRoot;

    public enum PlayerState
    {
        Remote,
        Normal,
        Attacking,
        Dead
    }

    public PlayerState State { get; private set; }

    // Start is called before the first frame update
    void Awake()
    {
        if (shiftTween == null) shiftTween = new SimpleTween();
        shiftTween.Stop();
        netItem = GetComponentInChildren<Item>();
        castLight = transform.Find("CastLight").gameObject;

        if (attackTween == null) attackTween = new SimpleTween();

        uiHookup = FindObjectOfType<UIHookup>();

        viewRadius = DefaultViewRadius;

        State = PlayerState.Remote;

        if (ForceLocal)
        {
            StartLocal();
        }
    }

    public GameObject PlayerMesh;

    private Image attackCooldownImage;

    private GameObject castLight;
    private float bottomOffset;
    public float BottomOffset { get { return bottomOffset; } }
    void StartLocal()
    {
        State = PlayerState.Normal;

        maxHealth = DefaultMaxHealth;
        health = DefaultMaxHealth;
        castLight.SetActive(true);
        charControl = GetComponentInChildren<CharacterController>();
        charControl.enabled = true;

        bottomOffset = -charControl.center.y + charControl.height * 0.5f;

        playerFade = FindObjectOfType<PlayerFade>();
        if (playerFade != null)
        {
            playerFadeCam = playerFade.GetComponent<Camera>();

            fadeSpot = transform.position;

            if (playerFadeCam != null)
            {
                fadeCamRoot = playerFadeCam.transform.root;

                
                startCamShift = playerFadeCam.transform.localPosition;
                camHeight = startCamShift.y;
                camOffsetZ = startCamShift.z;

                ShiftCamera();

                // shift instantly in the XZ plane
                fadeCamRoot.position = SlowFollowXZ(transform.position, fadeCamRoot.position, 1);
            }
        }

        cooldownBaseScale = uiHookup.AttackCooldown.localScale.x;
        attackCooldownImage = uiHookup.AttackCooldown.GetComponentInChildren<Image>();

        // scale to 0;
        uiHookup.AttackCooldown.localScale = new Vector3(0, 0, 1);

        AddShadow addShadow = FindObjectOfType<AddShadow>();
        Light pointLight = GetComponentsInChildren<Light>().FirstOrDefault(l => l.type == LightType.Point);
        if (pointLight != null)
        {
            pointLight.enabled = false;
            addShadow.PointLight = pointLight;
        }

        //netItem.GetNetHost().SpawnItemLocal("test",
        //    i =>
        //    {
        //        i.transform.position = transform.position;
        //    }
        //);
    }

    private float attackBuildup = 0;
    private float attackCooldown = 0;

    void TakeDamage(object detailsObj)
    {
        Dictionary<string, object> details = detailsObj as Dictionary<string, object>;
        if (details != null && details.ContainsKey("damage"))
        {
            float damage = details.Key("damage", Convert.ToSingle);
            health -= damage;
            if (health < 0)
            {
                health = 0;
                // TODO die

                State = PlayerState.Dead;
                netItem.DestroyItem();
            }

            //Rect hRect = uiHookup.Health.rect;
            //hRect.width = uiHookup.HealthWidth * 
            Vector3 scale = uiHookup.Health.localScale;
            scale.x = (health / maxHealth); ;
            uiHookup.Health.localScale = scale;
        }
    }

    private SimpleTween shiftTween;
    private void ShiftCamera()
    {
        startCamShift = playerFadeCam.transform.localPosition;

        endCamShift = new Vector3(UnityEngine.Random.Range(-0.5f, 0.5f), camHeight + UnityEngine.Random.Range(-0.4f, 0.4f), UnityEngine.Random.Range(camOffsetZ - 0.4f, camOffsetZ + 0.4f));

        shiftTween.Init(v => playerFadeCam.transform.localPosition = startCamShift * (1 - v) + endCamShift * v, SimpleTween.QuadEaseInOut, 0, 1, UnityEngine.Random.Range(5f, 10f));
        shiftTween.OnFinish = ShiftCamera;
    }

    private void OnDestroy()
    {
        shiftTween.Stop();
        attackTween.Stop();
    }

    void StartRemote()
    {
        if (PlayerMesh != null)
        {
            foreach (MeshRenderer mr in PlayerMesh.GetComponentsInChildren<MeshRenderer>())
            {
                if (mr.sharedMaterial.name == "Player")
                {
                    Material m = mr.material;

                    m.DisableKeyword("_EMISSION");
                    m.globalIlluminationFlags = MaterialGlobalIlluminationFlags.EmissiveIsBlack;
                    m.SetColor("_EmissionColor", Color.black);
                }
            }
        }
    }

    public float Speed;

    private List<Touch> touches = new List<Touch>();

    public float MoveTouchFullZone = 0.7f;
    public float MoveTouchRadiusSize = 1.45f;

    public float AttackTouchRadiusSize = 1.45f;

    public float DefaultViewRadius = 5.5f;
    private float viewRadius;

    public float CamFollowAmount = 0.9f;
    public float SpotFollowAmount = 0.95f;

    private Vector3 SlowFollowXZ(Vector3 target, Vector3 current, float factor)
    {
        Vector2 currentXZ = new Vector2(current.x, current.z);

        Vector2 targetXZ = new Vector2(target.x, target.z);

        float followThisFrame = 1 - Mathf.Pow(1 - factor, Time.deltaTime);

        Vector2 newCurrentXZ = targetXZ * followThisFrame + currentXZ * (1 - followThisFrame);

        current.x = newCurrentXZ.x;
        current.z = newCurrentXZ.y;

        return current;
    }

    private bool touchInAttack = false;

    // Update is called once per frame
    void Update()
    {
        if (IsLocal && charControl != null)
        {
            RaycastHit hit;
            if (Physics.Raycast(new Ray(transform.position, Vector3.down), out hit, 10f))
            {
                Vector3 pos = transform.position;
                pos.y = hit.point.y + (bottomOffset + 0.01f);
                transform.position = pos;
            }

            Vector3 wantMove = new Vector3();

            if (Input.GetKey(KeyCode.UpArrow))
            {
                wantMove.z += 1;
            }
            if (Input.GetKey(KeyCode.DownArrow))
            {
                wantMove.z -= 1;
            }
            if (Input.GetKey(KeyCode.RightArrow))
            {
                wantMove.x += 1;
            }
            if (Input.GetKey(KeyCode.LeftArrow))
            {
                wantMove.x -= 1;
            }

            wantMove.Normalize();

            if (uiHookup != null && uiHookup.MainCanvas != null)
            {
                

                touches.Clear();
                touches.AddRange(Input.touches);

                if (Input.GetMouseButton(0))
                {
                    touches.Add(new Touch { position = Input.mousePosition });
                }

                bool wasInAttackTouch = touchInAttack;
                touchInAttack = false;

                foreach (Touch touch in touches)
                {
                    // movement touch
                    float radius = uiHookup.MoveTouch.rect.width * 0.5f * MoveTouchRadiusSize;
                    Vector2 diff = new Vector2();
                    RectTransformUtility.ScreenPointToLocalPointInRectangle(uiHookup.MoveTouch, touch.position, null, out diff);
                    if (diff.magnitude < radius)
                    {
                        wantMove = new Vector3(diff.x, 0, diff.y) / (radius * MoveTouchFullZone);
                        if (wantMove.magnitude > 1) wantMove.Normalize();

                        break;
                    }
                }

                foreach (Touch touch in touches)
                {
                    float radius = uiHookup.AttackTouch.rect.width * 0.5f * AttackTouchRadiusSize;
                    Vector2 diff = new Vector2();
                    RectTransformUtility.ScreenPointToLocalPointInRectangle(uiHookup.AttackTouch, touch.position, null, out diff);
                    if (diff.magnitude < radius && diff.magnitude > 0.2f)
                    {
                        if (!wasInAttackTouch && State == PlayerState.Normal && attackCooldown == 0)
                        {
                            Attack(new Vector3(diff.x, 0, diff.y));
                        }

                        touchInAttack = true;

                        break;
                    }
                }
            }

            if (playerFade != null && playerFadeCam != null)
            {
                Vector3 playerScreen3 = playerFadeCam.WorldToScreenPoint(fadeSpot);
                Vector3 edgeScreen3 = playerFadeCam.WorldToScreenPoint(fadeSpot + playerFadeCam.transform.right * viewRadius);

                Vector2 playerScreen2 = new Vector2(playerScreen3.x / playerFadeCam.pixelWidth, playerScreen3.y / playerFadeCam.pixelHeight);
                Vector2 edgeScreen2 = new Vector2(edgeScreen3.x / playerFadeCam.pixelWidth, edgeScreen3.y / playerFadeCam.pixelHeight);

                float radius = (edgeScreen2 - playerScreen2).magnitude;

                playerFade.SetFadeParams(radius, playerScreen2);

                fadeCamRoot.position = SlowFollowXZ(transform.position, fadeCamRoot.position, SpotFollowAmount);
                //playerFadeCam.transform.LookAt(fadeCamRoot, Vector3.up);
                fadeSpot = SlowFollowXZ(transform.position, fadeSpot, CamFollowAmount);
            }


            if (wantMove.sqrMagnitude > 0 && State == PlayerState.Normal)
            {
                wantMove *= Speed * Time.deltaTime;
                wantMove.y = -0.1f * Time.deltaTime;
                CollisionFlags flags = charControl.Move(wantMove);


            }

            if (attackCooldown > 0)
            {
                attackCooldownImage.color = new Color(1, 0, 0, 0.5f);
                attackCooldown = Math.Max(0, attackCooldown - Time.deltaTime * AttackCooldownRate);
                uiHookup.AttackCooldown.localScale = new Vector3(attackCooldown, attackCooldown, 1) * cooldownBaseScale;
            }
            else
            {
                attackCooldownImage.color = new Color(0, 0, 1, 0.5f);
                attackBuildup = Math.Max(0, attackBuildup - Time.deltaTime * AttackBuildupRate);
                uiHookup.AttackCooldown.localScale = new Vector3(attackBuildup, attackBuildup, 1) * cooldownBaseScale;
            }

            
        }
    }

    public float AttackBuildupStep = 0.2f;

    public float AttackBuildupRate = 1;
    public float AttackCooldownRate = 0.5f;

    private SimpleTween attackTween;

    public float AttackTime = 0.2f;
    public float AttackDist = 2f;

    public float Damage = 1f;

    

    private Vector3 attackDelta;

    private Vector3 lastAttackPos;

    private void Attack(Vector3 attackDir)
    {
        attackBuildup += AttackBuildupStep;
        if (attackBuildup > 1)
        {
            attackBuildup = 0;
            attackCooldown = 1;
        }


        State = PlayerState.Attacking;

        attackDelta = attackDir.normalized * AttackDist;

        Vector3 startPos = transform.position;
        lastAttackPos = startPos;
        attackTween.Init(
            v =>
            {
                lastAttackPos = transform.position;
                transform.position = startPos + attackDelta * v;
                MeshRoot.transform.localScale = new Vector3(1, 1, 1) * (v * 0.3f + 1.0f);
            }, SimpleTween.QuadEaseOut, 0, 1, AttackTime);

        attackTween.OnFinish = FinishedAttack;
    }

    public float AttackWait = 0.8f;

    void OnTriggerEnter(Collider other)
    {
        if (State == PlayerState.Attacking)
        {
            Virus v = other.GetComponentInParent<Virus>();
            if (v == null)
            {
                transform.position = lastAttackPos;
                FinishedAttack();
            }
            else
            {
                Item virusItem = v.GetComponentInChildren<Item>();
                if (virusItem != null && netItem.GetNetHost() != null)
                {
                    netItem.GetNetHost().SendItemCommand(virusItem.Key, "TakeDamage", new Dictionary<string, object> { { "damage", Damage } });
                }
            }
        }
    }

    private void FinishedAttack()
    {
        MeshRoot.transform.localScale = new Vector3(1, 1, 1);
        State = PlayerState.Normal;
        attackTween.Stop();
        /*
        if (UnityEngine.Random.Range(0, 1) < 0.4f)
        {
            targetPlayer = null;
            PathToPlayer();
            return;
        }


        if (targetPlayer != null && DistToTarget <= AttackMaxDist)
        {
            attackTimer.Start(AttackWait, CheckAttackTarget);
        }
        else
        {
            targetPlayer = null;
            PathToPlayer();
        }
        */
    }
}
