using System.Collections.Generic;
using System.Linq;
using InfinityPBR.ModularCharacterHelper;
using Sirenix.OdinInspector;
using Sirenix.Utilities;
using UnityEngine;
using VOID.Generic.Dict;
using VOID.Generic.Objects.Unit;
using VOID.Generic.Util;
using Random = UnityEngine.Random;

namespace VOID.ScriptableObjects
{
    [CreateAssetMenu(menuName = "GAME DATA/Generator/Generator Jednostki")]
    public class UnitGenerator : ScriptableObject
    {
        [Title("Unit References")]
        [SerializeField] private UnitObject[] _unitPrefabs;
        [SerializeField] private Material[] _skinMaterials;
        [SerializeField] private Material[] _hairMaterials;

        [Space(100)]
        [Title("Model")]
        [InfoBox("If prefab part can't be attached to unit prefab then it'll be skipped!")]
        [SerializeField] private bool _generateHair;
        [SerializeField, ShowIf(nameof(_generateHair)), Required] private ModularPart[] _hairPrefabs;

        [Title("Colors")]
        [InfoBox("If colors list is empty then randomize new one!")]
        [SerializeField] private bool _generateSkinColor;
        [SerializeField, ShowIf(nameof(_generateSkinColor))] private Color[] _skinColors;
        [SerializeField] private bool _generateHairColor;
        [SerializeField, ShowIf(nameof(_generateHairColor))] private Color[] _hairColors;

        [Title("Main Attributes")]
        [SerializeField] private bool _generateMainAttributes;
        [SerializeField, ShowIf(nameof(_generateMainAttributes)), MinMaxSlider(1, 50, true)] private Vector2Int _strength = new(4, 7);
        [SerializeField, ShowIf(nameof(_generateMainAttributes)), MinMaxSlider(1, 50, true)] private Vector2Int _dexterity = new(4, 7);
        [SerializeField, ShowIf(nameof(_generateMainAttributes)), MinMaxSlider(1, 50, true)] private Vector2Int _intelligence = new(4, 7);
        [SerializeField, ShowIf(nameof(_generateMainAttributes)), MinMaxSlider(1, 50, true)] private Vector2Int _constitution = new(4, 7);
        [SerializeField, ShowIf(nameof(_generateMainAttributes)), MinMaxSlider(1, 50, true)] private Vector2Int _speed = new(4, 7);
        [SerializeField, ShowIf(nameof(_generateMainAttributes)), MinMaxSlider(1, 50, true)] private Vector2Int _perception = new(4, 7);

        private void OnValidate()
        {
            if (!_generateHair)
            {
                _hairPrefabs = null;
            }
            
            if (!_generateSkinColor)
            {
                _skinColors = null;
            }
            
            if (!_generateHairColor)
            {
                _hairColors = null;
            }

            if (!_generateMainAttributes)
            {
                _strength = new Vector2Int(4, 7);
                _dexterity = new Vector2Int(4, 7);
                _intelligence = new Vector2Int(4, 7);
                _constitution = new Vector2Int(4, 7);
                _speed = new Vector2Int(4, 7);
                _perception = new Vector2Int(4, 7);
            }
        }

        /// <summary>
        /// Generate new <see cref="UnitObject"/>:<br/>
        /// 1. Selects random <see cref="UnitObject"/> prefab<br/>
        /// 2. Wait for full initialization of <see cref="UnitObject"/><br/>
        /// 3. Updates <see cref="UnitObject"/> with random things defined via inspector
        /// </summary>
        /// <returns>Instantiated random <see cref="UnitObject"/></returns>
        public UnitObject Generate(int? seed = null)
        {
            if (seed.HasValue) RandomUtil.InitState(seed.Value);
            var unit = Instantiate(RandomUtil.RandomElement(_unitPrefabs));
            Randomize(unit);
            return unit;
        }

        private void Randomize(UnitObject unit)
        {
            RandomizeHair(unit);
            RandomizeSkinColor(unit);
            RandomizeHairColor(unit);
            RandomizeMainAttributes(unit);
        }

        private void RandomizeHair(UnitObject unit)
        {
            if (!_generateHair) return;

            var modularCharacter = unit.unitState.modularCharacter;
            var validHairPrefabs = _hairPrefabs.Where(hair => hair.avatar == modularCharacter.avatar).ToList();

            if (validHairPrefabs.IsNullOrEmpty()) return;

            modularCharacter.AttachPart(Instantiate(RandomUtil.RandomElement(validHairPrefabs), unit.transform));
        }

        private void RandomizeSkinColor(UnitObject unit)
        {
            if (!_generateSkinColor) return;
            if (_skinMaterials.IsNullOrEmpty()) return;

            ApplyTintToMaterials(unit, _skinColors, _skinMaterials);
        }

        private void RandomizeHairColor(UnitObject unit)
        {
            if (!_generateHairColor) return;
            if (_hairMaterials.IsNullOrEmpty()) return;
            
            ApplyTintToMaterials(unit, _hairColors, _hairMaterials);
        }

        private void ApplyTintToMaterials(UnitObject unit, Color[] colors, Material[] forMaterials)
        {
            // Find renderers with this material to add tint color
            var foundRenderers = unit.GetComponentsInChildren<Renderer>()
                .Where(renderer => forMaterials.Any(material => renderer.sharedMaterials.Contains(material)))
                .ToList();

            if (foundRenderers.IsNullOrEmpty()) return;

            // Get random defined color if present OR randomize new one
            var color = colors.IsNullOrEmpty()
                ? Random.ColorHSV(0, 1, 0, 1, 0, 1, 0, 1)
                : RandomUtil.RandomElement(colors);

            // Normally renderers here will use shared materials, but we want to create colored "variants" thats why
            // we need to instantiate material and add tint color if this is matching material. Other materials will
            // instantiate too anyway even without our help so lets do all of them by ourselfs!
            foreach (var foundRenderer in foundRenderers)
            {
                var materials = new List<Material>();
                foreach (var sharedMaterial in foundRenderer.sharedMaterials)
                {
                    if (forMaterials.Contains(sharedMaterial))
                    {
                        var material = Instantiate(sharedMaterial);
                        material.color = color;
                        materials.Add(material);
                    }
                    else
                    {
                        materials.Add(sharedMaterial);
                    }
                }
                foundRenderer.sharedMaterials = materials.ToArray();
            }
        }

        private void RandomizeMainAttributes(UnitObject unit)
        {
            if (!_generateMainAttributes) return;

            unit.unitData.baseMainAttributes.Set(MainAttributeType.Strength,
                RandomUtil.RandomInteger(_strength.x, _strength.y));
            unit.unitData.baseMainAttributes.Set(MainAttributeType.Dexterity,
                RandomUtil.RandomInteger(_dexterity.x, _dexterity.y));
            unit.unitData.baseMainAttributes.Set(MainAttributeType.Intelligence,
                RandomUtil.RandomInteger(_intelligence.x, _intelligence.y));
            unit.unitData.baseMainAttributes.Set(MainAttributeType.Constitution,
                RandomUtil.RandomInteger(_constitution.x, _constitution.y));
            unit.unitData.baseMainAttributes.Set(MainAttributeType.Speed,
                RandomUtil.RandomInteger(_speed.x, _speed.y));
            unit.unitData.baseMainAttributes.Set(MainAttributeType.Perception,
                RandomUtil.RandomInteger(_perception.x, _perception.y));
        }
    }
}