using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using System.Collections;
using TMPro;
using UnityEditor;

/// <summary>
/// <see cref="Fact.Id"/>/ <c>MonoBehaviour</c> wrapper to be attached to <see cref="Fact.Representation"/>
/// </summary>
[DisallowMultipleComponent]
public class FactObject : FactWrapper, ISerializationCallbackReceiver
{
    public enum FactMaterials
    {
        Default = 0,
        Selected = 1,
        Hint = 2,
        Solution = 3,
    }


    [SerializeField] protected List<TMP_Text> FactText;
    [SerializeField] protected List<string> StringLabelFormats;

    [NonSerialized]
    public Material[] materials;
    public new Renderer[] renderer;
    [NonSerialized]
    protected List<FactObject> AllChildren;

    #region Unity Serialization
    public Material Default;
    public Material Selected;
    public Material Hint;
    public Material Solution;

    void ISerializationCallbackReceiver.OnBeforeSerialize()
    {
        if (FactText == null || FactText.Count() == 0)
        {
            FactText = transform
                .GetComponentsInChildren<TMP_Text>(includeInactive: true)
                .ToList();
        }

        {
            StringLabelFormats ??= new();

            int i = StringLabelFormats.Count();
            int end = FactText.Count();

            for (; i < end; i++)
                StringLabelFormats.Add("{" + (i + 1) + "}");
        }

        if (materials != null)
        {
            Default = materials[(int)FactMaterials.Default];
            Selected = materials[(int)FactMaterials.Selected];
            Hint = materials[(int)FactMaterials.Hint];
            Solution = materials[(int)FactMaterials.Solution];
        }

#if UNITY_EDITOR // not working
        if (EditorApplication.isPlaying)
            return;

        foreach (Collider collider in transform.GetComponentsInChildren<Collider>(includeInactive: true))
        {
            if (!collider.gameObject.GetComponent<FactObject>())
                try
                {
                    collider.gameObject.AddComponent<FactObject>();
                }
                catch(Exception) { }
        }
#endif
    }

    void ISerializationCallbackReceiver.OnAfterDeserialize()
    {
        {
            materials = new Material[4];
            materials[(int)FactMaterials.Default] = Default;
            materials[(int)FactMaterials.Selected] = Selected;
            materials[(int)FactMaterials.Hint] = Hint;
            materials[(int)FactMaterials.Solution] = Solution;
        }
    }
    #endregion

    private void Awake()
    {
        AllChildren = transform.GetComponentsInChildren<FactObject>(includeInactive: true).ToList();
        AllChildren.Remove(this);
    }

    protected override void FactUpdated()
    {
        Awake();

        ReLabel();

        foreach (FactObject childObject in AllChildren)
        {
            childObject._URI = _URI;
            childObject._Fact = null;
            childObject.ReLabel();
        }
    }

    public void ReLabel()
    {
        string[] mother_child_labels =
            new[] { URI }.ShallowCloneAppend(Fact.DependentFactIds)
            .Select(fid =>
                FactOrganizer.AllFacts.TryGetValue(fid, out Fact fact)
                ? fact.Label
                : "N/A")
            .ToArray();

        string indexoverflow = "{" + mother_child_labels.Length.ToString() + "}";

        for (int i = 0; i < Math.Min(FactText.Count(), StringLabelFormats.Count()); i++)
        {
            if (StringLabelFormats[i].Contains(indexoverflow))
            {
                Debug.LogWarning("Format contains illegal Argument and will be ignored: " + indexoverflow);
                continue;
            }
            FactText[i].text = string.Format(StringLabelFormats[i], mother_child_labels);
        }

        switch (Fact) // for highly customized labels
        {
            case TestFact testFact:
                //FactText[0].text = testFact.Label;
                break;
            default:
                break;
        }
    }

    public void CascadeForMeAndChildren(Action<FactObject> func)
    {
        func(this);
        foreach (FactObject fo in AllChildren)
            func(fo);
    }

    public void ForAllRenderer(Action<Renderer> func)
    {
        foreach (Renderer ren in renderer)
            func(ren);
    }

    public void CoroutineCascadeForMeAndChildrenAllRenderer(Func<FactObject, Renderer, IEnumerator> func)
    {
        this.StopAllCoroutines();

        CascadeForMeAndChildren((FactObject fo) =>
            fo.ForAllRenderer((Renderer ren) =>
                StartCoroutine(func(fo, ren))
        ));
    }
}