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

namespace Nan.Scripts.Stage
{
    public class StageLoader : MonoBehaviour
    {
        [SerializeField] private GameObject _wallPrefab;
        [SerializeField] private GameObject _floorPrefab;
        [SerializeField] private GameObject _doorPrefab;
        [SerializeField] private GameObject _keyPrefab;
        [SerializeField] private GameObject _enemy1Prefab;
        [SerializeField] private GameObject _enemy2Prefab;
        [SerializeField] private GameObject _fallPrefab;
        [SerializeField] private GameObject _recoverItemPrefab;

        [SerializeField] private Transform _stageRoot;
        [SerializeField] private PlayerControl _player;

        // あとで外から設定できるように
        private const int StageWidth = 40;
        private const int StageHeight = 40;
        private const int FallCount = 160;
        private const int Enemy1Count = 40;
        private const int Enemy2Count = 10;
        private const int RecoverItemCount = 10;
        
        public static StageLoader Instance { get; private set; }

        private void Start()
        {
            Instance = this;
            LoadStage();
        }

        public void LoadStage()
        {
            for (var i = 0; i < _stageRoot.childCount; i++)
            {
                Destroy(_stageRoot.GetChild(i).gameObject);
            }

            var stage = Load();

            // Walls
            foreach (var position in stage.WallPositions)
            {
                CreateObject(_wallPrefab, position);
            }
            // Floors
            foreach (var position in stage.FloorPositions)
            {
                CreateObject(_floorPrefab, position);
            }

            // Falls
            foreach (var position in stage.FallPositions)
            {
                CreateObject(_fallPrefab, position);
            }

            // Exit
            var exit = CreateObject(_doorPrefab, stage.DoorPosition).GetComponent<Exit>();
            exit.PlayerControl = _player;

            // Key
            CreateObject(_keyPrefab, stage.KeyPosition).GetComponent<Key>();

            // Enemies
            foreach (var position in stage.Enemy1Positions)
            {
                var enemy = CreateObject(_enemy1Prefab, position);
                enemy.GetComponent<Enemy1Move>().Player = _player;
            }
            foreach (var position in stage.Enemy2Positions)
            {
                var enemy = CreateObject(_enemy2Prefab, position);
                enemy.GetComponent<Enemy2Move>().Player = _player;
            }

            // RecoverItem
            foreach (var position in stage.RecoverItemPositions)
            {
                CreateObject(_recoverItemPrefab, position);
            }

            _player.transform.position = stage.PlayerPosition;

        }

        private global::Stage Load()
        {
            // 座標は仮

            // Wall Floor Fall
            var wallPositions = new Vector2[StageWidth * StageHeight];
            var floorPositions = new Vector2[StageWidth * StageHeight];
            var wallCount = 0;
            var floorCount = 0;
            for (var i = 0; i < StageWidth; i++)
            {
                for (var j = 0; j < StageHeight; j++)
                {
                    var position = new Vector2(i, j);
                    // 端は壁
                    if (i == 0 || i == StageHeight - 1 || j == 0 || j == StageWidth - 1)
                    {
                        wallPositions[wallCount++] = position;
                    }
                    else
                    {
                        floorPositions[floorCount++] = position;
                    }
                }
            }

            InitRandomPositions();

            // Player
            var playerPosition = GetRandomPosition();

            // Door
            var doorPosition = GetRandomPosition();
            // Key
            var keyPosition = GetRandomPosition();
            // Enemies
            var enemy1Positions = new Vector2[Enemy1Count];
            for (var i = 0; i < Enemy1Count; i++)
            {
                enemy1Positions[i] = GetRandomPosition();
            }
            var enemy2Positions = new Vector2[Enemy2Count];
            for (var i = 0; i < Enemy2Count; i++)
            {
                enemy2Positions[i] = GetRandomPosition();
            }
            // Fall
            var fallPositions = new Vector2[FallCount];
            for (var i = 0; i < FallCount; i++)
            {
                fallPositions[i] = GetRandomPosition();
                if ((fallPositions[i] - playerPosition).magnitude < 1.5f)
                {
                    fallPositions[i] = GetRandomPosition();
                }
            }
            // RecoverItem
            var recoverItemPositions = new Vector2[RecoverItemCount];
            for (var i = 0; i < RecoverItemCount; i++)
            {
                recoverItemPositions[i] = GetRandomPosition();
            }
            return new global::Stage(wallPositions, floorPositions, fallPositions, doorPosition, keyPosition, enemy1Positions, enemy2Positions,
                recoverItemPositions, playerPosition);
        }

        private GameObject CreateObject(GameObject prefab, Vector2 position)
        {
            var go = Instantiate(prefab);
            go.transform.position = position;
            go.transform.parent = _stageRoot;
            return go;
        }

        private List<Vector2> randomPositions;

        private void InitRandomPositions()
        {
            randomPositions = new List<Vector2>();
            for (var i = 1; i < StageWidth - 1; i++)
            {
                for (var j = 1; j < StageHeight - 1; j++)
                {
                    var position = new Vector2(i, j);
                    randomPositions.Add(position);
                }
            }
        }

        private Vector2 GetRandomPosition()
        {
            if (randomPositions == null || !randomPositions.Any())
                return new Vector2();

            var randomIndex = Random.Range(0, randomPositions.Count);
            var position = randomPositions [randomIndex];
            randomPositions.RemoveAt(randomIndex);
            return position;
        }

    }
}
