using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using Assets.Scripts.Utils;
using UnityEngine;

namespace Assets.Scripts.MapGeneration
{
    internal class DefaultCellDelegates
    {
        public static bool DefaultCanGrow(Cell cell)
        {
            return true;
        }

        public static bool DefaultWillGrow(Cell cell)
        {
            return true;
        }
    }

    public class CellularAutomaton
    {
        #region Delegates

        public delegate IEnumerable<Cell> Filter(Map map);

        public delegate bool CanGrow(Cell cell);

        public delegate bool WillGrow(Cell cell);

        public delegate Cell Picktarget(Map map, Cell origin);

        #endregion

        #region Fields

        private Map _map;
        private List<Cell> _toProcess;

        public CanGrow CanGrowRule;
        public WillGrow WillGrowRule;
        public Picktarget PickTargetRule;

        private TileType _targetType;

        #endregion

        #region Ctors

        public CellularAutomaton(Map map, TileType targetType, uint startX = 0, uint startY = 0)
        {
            _map = map;

            _targetType = targetType;
            CanGrowRule = DefaultCellDelegates.DefaultCanGrow;
            WillGrowRule = DefaultCellDelegates.DefaultWillGrow;

            _toProcess = new List<Cell> {new Cell(startX, startY, targetType)};
            _map[startX, startY] = targetType;
            ApplyToMap(_toProcess);
        }

        #endregion

        #region Methods

        public void Step(float max)
        {
            if (!_toProcess.Any())
                return;
            var newCells = new List<Cell>();

            foreach (var cell in _toProcess)
            {
                if (!NeedToCompute(cell, max))
                {
                    newCells.Add(cell);
                    continue;
                }

                foreach (var target in GetNeighbors(_map, cell))
                {
                    if (!CanGrowRule(target) || !WillGrowRule(target))
                        continue;

                    Grow(target, _targetType);

                    newCells.Add(target);
                }
            }
            ApplyToMap(newCells);
            _toProcess = newCells;
        }

        private bool NeedToCompute(Cell cell, float max)
        {
            return cell.Position.x < max % _map.Columns;
        }

        public void ApplyToMap(List<Cell> cells)
        {
            foreach (var cell in cells)
            {
                _map[(uint) cell.Position.x, (uint) cell.Position.y] = cell.Type;
            }
        }

        public static IEnumerable<Cell> GetNeighbors(Map map, Cell origin)
        {
            var ret = new List<Cell>();

            for (var x = origin.Position.x - 1; x <= origin.Position.x + 1; x++)
            {
                for (var y = origin.Position.y - 1; y <= origin.Position.y + 1; y++)
                {
                    if ((Math.Abs(x - origin.Position.x) > .1f || Math.Abs(y - origin.Position.y) > .1f)
                        && IsInMapRange(map, (int) x, (int) y) && map[(uint) x, (uint) y] != origin.Type)
                    {
                        ret.Add(new Cell((uint) x, (uint) y, map[(uint) x, (uint) y]));
                    }
                }
            }

            return ret;
        }

        private void Grow(Cell targetCell, TileType targetType)
        {
            targetCell.Type = targetType;
        }

        private static bool IsInMapRange(Map map, int x, int y)
        {
            return (x >= 0 && x < map.Columns &&
                    y >= 0 && y < map.Rows);
        }

        #endregion
    }
}