using System;
using Nan.Scripts.Stage;
using UniRx;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace Nan.Scripts
{
    public class PlayerControl : MonoBehaviour
    {
        [SerializeField] private GameObject _playerStatusUiPrefab;
        [SerializeField] private GameObject _playerBullet;
        [SerializeField] private GameObject _playerGuard;

        [SerializeField] private AudioSource _shotAudioSource;

        [SerializeField] private Camera _camera;

        private float _speedFactor = 20f;
        private float _statusChangeFactor = 2f;

        public bool HasKey { get; private set; }

        public PlayerStatus Status { get; private set; }

        private float _waitTime;

        private bool _isCleared;

        private Vector3 cameraVelocity;

        // Use this for initialization
        private void Start()
        {
            Status = new PlayerStatus();
            var statusUi = Instantiate(_playerStatusUiPrefab).GetComponent<PlayerStatusUI>();
            statusUi.Initialize(Status);
            var playerGuard = Instantiate(_playerGuard, this.transform).GetComponent<PlayerGuard>();
            playerGuard.transform.localPosition = Vector3.zero;
            playerGuard.Initialize(Status);
        }

        // Update is called once per frame
        private void Update()
        {
            if (_isCleared) return;

            //Player movement control
            var move = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0);
            var body = GetComponent<Rigidbody2D>();
            body.velocity = move * Status.Speed * _speedFactor;

            //Player Status Alternation Controls
            Status.Attack = CalcPerlin(Time.time, 30);
            Status.Defense = CalcPerlin(Time.time, 40);
            Status.Speed = CalcPerlin(Time.time, 50);

            var dir = new Vector2(Input.GetAxis("RightHorizontal"), Input.GetAxis("RightVertical"));
            if (_waitTime < 0 && dir.magnitude > 0.1f)
            {
                _shotAudioSource.transform.position = transform.position + (Vector3) dir;
                _shotAudioSource.Play();
                _waitTime = (1 - Status.Attack) * 0.2f;

                ShootBullet(dir * 30);
            }
            _waitTime -= Time.deltaTime;
            Status.Health += Time.deltaTime * 0.02f * Status.Defense;

            _camera.transform.localPosition = Vector3.SmoothDamp(_camera.transform.localPosition, (Vector3)dir * 2 + Vector3.back * 9.0f, ref cameraVelocity, 0.1f);
        }

        private float CalcPerlin(float time, int seed)
        {
            var value = 0.0f;
            for (var i = 0; i < 4; ++i)
            {
                value += Mathf.PerlinNoise(Time.time * Mathf.Pow(2, -i) / _statusChangeFactor, (seed * 1235668 + 10 * i) / _statusChangeFactor);
            }
            return Mathf.Clamp01((value / 4 - 0.5f) * 3 + 0.5f);
        }

        private void ShootBullet(Vector3 velocity)
        {
            var obj = Instantiate(_playerBullet);
            obj.GetComponent<PlayerBullet>().Velocity = velocity;
            obj.transform.position = transform.position;
        }

        private void OnTriggerEnter2D(Collider2D other)
        {
            if (other.GetComponent<Key>() != null)
            {
                HasKey = true;
            }
            if (other.GetComponent<Exit>() != null && HasKey)
            {
                ScoreManager.Instance.CurrentLayer++;
                _isCleared = true;
                GetComponent<Rigidbody2D>().velocity = Vector2.zero;

                Observable.Return(Unit.Default).Delay(TimeSpan.FromSeconds(0.5f)).Subscribe(_ =>
                {
                    SceneManager.LoadScene(SceneNames.GameMain);
                });
            }
            if (other.GetComponent<RecoverItem>() != null)
            {
                var recoverItem = other.GetComponent<RecoverItem>();
                Status.Health += recoverItem.RecoverHealth;
            }
        }

        private void OnCollisionStay2D(Collision2D other)
        {
            if (_isCleared) return;

            if (other.gameObject.GetComponent<Enemy>() == null) return;
            Status.Health -= Time.deltaTime * (1 - Status.Defense) * 0.4f;
            if (Status.Health < 0)
            {
                GetComponentInChildren<PlayerPresenter>().ShowDeathAnimation();
                GetComponent<Rigidbody2D>().constraints = RigidbodyConstraints2D.FreezeAll;
                Observable.Return(Unit.Default).Delay(TimeSpan.FromSeconds(0.5f)).Subscribe(_ =>
                {
                    SceneManager.LoadScene(SceneNames.Result);
                });
                Destroy(this);
            }
        }

        private void OnCollisionEnter2D(Collision2D other)
        {

            if (other.gameObject.GetComponent<EnemyBullet>() == null) return;
            Status.Health -= 0.1f;
        }
    }
}