using System.Collections.Generic;
using UnityEngine.Tilemaps;
using UnityEngine;
using System;
using System.Collections;
using System.Linq;
using Newtonsoft.Json;

namespace GatherAndDefend.LevelEditor
{
    [Serializable]
    public class TilemapData : IEnumerable<TileData>
    {
        public const int INVISIBLE_LAYER = 6;
        [SerializeField]
        private string _key;
        [SerializeField]
        private List<TileData> _tiles;
        [SerializeField]
        private bool _isInvisible;
        [SerializeField]
        private bool _isCollidable;
        [SerializeField]
        private bool _isTrigger;
        [SerializeField]
        private int _renderOrder;
        [SerializeField]
        private string _renderLayer;

        public string Key => _key;

        public void LoadToTilemap(Tilemap reference)
        {
            var rend = reference.GetComponent<TilemapRenderer>();
            rend.sortingOrder = _renderOrder;
            rend.sortingLayerName = _renderLayer;
            if (_isInvisible) rend.gameObject.layer = INVISIBLE_LAYER;
            if (_isCollidable)
            {
                var collision = rend.gameObject.AddComponent<TilemapCollider2D>();
                collision.isTrigger = _isTrigger;
            }


            foreach (TileData data in _tiles)
            {
                reference.SetTile(data.Position, data.Tile);
            }
        }
        /// <summary>
        /// saves a tilemap into the level object
        /// </summary>
        /// <param name="reference"></param>
        /// <returns>the bounds of the tilemap</returns>
        public void SaveFromTilemap(Tilemap reference)
        {
            _key = reference.name;
            if (_isCollidable = reference.GetComponent<TilemapCollider2D>())
            {
                _isTrigger = reference.GetComponent<TilemapCollider2D>().isTrigger;
            }
            
            _isInvisible = reference.gameObject.layer == INVISIBLE_LAYER;
            _renderLayer = reference.GetComponent<TilemapRenderer>().sortingLayerName;
            _renderOrder = reference.GetComponent<TilemapRenderer>().sortingOrder;


            _tiles = new List<TileData>();

            BoundsInt bounds = reference.cellBounds;
            for (int i = bounds.xMin; i <= bounds.xMax; i++)
            {
                for (int j = bounds.yMin; j <= bounds.yMax; j++)
                {
                    Vector3Int position = new Vector3Int(i, j);
                    TileBase tile = reference.GetTile(position);
                    if (!tile) continue;
                    var tileData = new TileData(position, tile);
                    _tiles.Add(tileData);
                }
            }
        }

        /// <summary>
        /// returns a dictionary representation of the tilemap
        /// </summary>
        /// <returns></returns>
        public Dictionary<string, object> ToDictionary()
        {
            return new Dictionary<string, object>()
            {
                {nameof(_key), _key },
                {nameof(_isInvisible), _isInvisible },
                {nameof(_isCollidable), _isCollidable },
                {nameof(_isTrigger), _isTrigger },
                {nameof(_renderOrder), _renderOrder },
                {nameof(_renderLayer), _renderLayer },
                {nameof(_tiles), _tiles.FindAll(x => !(x.Tile is LevelTile))
                                       .Select(x => new Dictionary<string, object>() {
                                            {nameof(x.Position), new float[] { x.Position.x, x.Position.y, x.Position.z } }, 
                                           {nameof(x.Tile), x.Tile.name } }).ToArray() }
            };
        }

        /// <summary>
        /// builds a tilemap from a dictionary representation (from the save file)
        /// </summary>
        /// <param name="dict"></param>
        /// <returns></returns>
        public static Tilemap FromDictionary(Dictionary<string, object> dict)
        {
            //get all tilemap data
            var key = dict[nameof(_key)].ToString();
            var invisible = dict[nameof(_isInvisible)].ToBool();
            var collidable = dict[nameof(_isCollidable)].ToBool();
            var trigger = dict[nameof(_isTrigger)].ToBool();
            var renderOrder = dict[nameof(_renderOrder)].ToInt();
            var renderLayer = dict[nameof(_renderLayer)].ToString();
            var tiles = dict[nameof(_tiles)];

            //get grid
            var grid = GameObject.FindObjectOfType<Grid>();
            if (!grid) grid = new GameObject("Grid").AddComponent<Grid>();

            //get tilemap by name
            var tilemap = grid.GetComponentInChildren<Tilemap>(key);
            TilemapRenderer renderer;
            if (!tilemap)
            {
                tilemap = new GameObject(key).AddComponent<Tilemap>();
                tilemap.tileAnchor = Vector3.zero;
                tilemap.gameObject.AddComponent<TilemapRenderer>();
                tilemap.transform.SetParent(grid.transform);
            }
            renderer = tilemap.GetComponent<TilemapRenderer>();

            //populate tilemap according to specs
            renderer.sortingOrder = renderOrder;
            renderer.sortingLayerName = renderLayer;

            if (invisible) tilemap.gameObject.layer = INVISIBLE_LAYER;
            if (collidable)
            {
                tilemap.gameObject.AddComponent<TilemapCollider2D>().isTrigger = trigger;
            }

            //populate tilemaps with non-LevelTile tiles
            foreach (var tileObj in tiles as IEnumerable)
            {
                var tileDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(tileObj.ToString());
                var tileName = tileDict["Tile"].ToString();
                var tile = Database.Instance.ScriptableObjects[tileName];
                
                var tilePos = tileDict["Position"].ToVector3();

                tilemap.SetTile(Vector3Int.RoundToInt(tilePos), tile as TileBase);
            }

            return tilemap;
        }

        public IEnumerator<TileData> GetEnumerator()
        {
            return _tiles.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return _tiles.GetEnumerator();
        }

        public TilemapData()
        {
            _tiles = new List<TileData>();
        }
    }
}