using System;
using System.Linq;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEngine;

namespace PebkacFinLabels
{
    class ModulePebkacFinLabel : PartModule
    {
        private static string _myModTag = "[PEBKAC FinLabels]";

        private const string FIN_A = "finA";
        private const string FIN_B = "finB";
        private const string FIN_C = "finC";
        private const string FIN_D = "finD";

        /// <summary>
        /// The folder in which textures are located. 
        /// Relative to GameData folder.
        /// </summary>
        [KSPField(isPersistant = true)]
        public string RootFolder = string.Empty;

        /// <summary>
        /// The names of the game objects which main texture should be replaced.
        /// Divided by commas. May be regular expressions.
        /// </summary>
        [KSPField(isPersistant = true)]
        public string AffectedObjects = string.Empty;
        List<Regex> affected_objects;

        readonly List<Renderer> renderers = new List<Renderer>();

        /// <summary>
        /// Names of the textures to choose from. 
        /// </summary>
        [KSPField(isPersistant = true)]
        public string Textures = string.Empty;
        protected readonly List<string> textures = new List<string>();

        /// <summary>
        /// The texture currently in use.
        /// </summary>
        [KSPField(isPersistant = true)]
        public string CurrentTexture = string.Empty;

        /// <summary>
        /// The texture currently in use. Part menu display.
        /// </summary>
        [KSPField(guiActiveEditor = true, guiName = "Fin Marking")]
        [UI_ChooseOption(scene = UI_Scene.Editor)]
        public string CurrentTextureDisplay = string.Empty;

        public void NextTexture()
        {
            if (textures.Count < 2) return;
            CurrentTexture = textures.Next(CurrentTexture);
            set_texture();
        }

        public void PrevTexture()
        {
            if (textures.Count < 2) return;
            CurrentTexture = textures.Prev(CurrentTexture);
            set_texture();
        }

        public void SetTexture(string texture)
        {
            var prev_texture = CurrentTexture;
            CurrentTexture = texture;
            if (!set_texture())
                CurrentTexture = prev_texture;
        }

        public void SetTexture(int index)
        {
            if (textures.Count == 0) return;
            index = index % textures.Count;
            CurrentTexture = textures[index];
            set_texture();
        }
        
        public override void OnStart(StartState state)
        {
            // hook up to the part attach callback
            if (state == StartState.Editor)
            {
                part.OnEditorAttach += OnEditorAttach;
            }

            //prepare root folder path
            if (!string.IsNullOrEmpty(RootFolder))
                RootFolder = RootFolder.TrimEnd('/') + "/";
            setup_material();
            setup_textures();
            set_texture();

        }

        protected void setup_material()
        {

            renderers.Clear();

            if (string.IsNullOrEmpty(AffectedObjects)) return;

            affected_objects = UtilitiesAndExtensions.ParseLine(AffectedObjects, UtilitiesAndExtensions.Comma).Select(s => new Regex(s)).ToList();

            foreach (var r in part.FindModelComponents<Renderer>())
            {
                if (r == null || !r.enabled) continue;
                if (affected_objects.Any(exp => exp.IsMatch(r.name.Replace("(Instance)", "").Trim())))
                    renderers.Add(r);
            }

            if (renderers.Count == 0)
                Debug.Log(string.Format("None of the following objects were found in the model: {}", AffectedObjects));

        }

        protected void setup_textures()
        {
            textures.Clear();
            if (renderers.Count == 0 || string.IsNullOrEmpty(Textures)) return;
            //parse textures
            foreach (var t in UtilitiesAndExtensions.ParseLine(Textures, UtilitiesAndExtensions.Comma))
            {
                var tex = RootFolder + t;
                if (GameDatabase.Instance.ExistsTexture(tex))
                {
                    try { textures.Add(t); }
                    catch { Debug.Log(string.Format("Duplicate texture in the replacement list: {}", t)); }
                }
                else Debug.Log(string.Format("No such texture: {}", tex));
            }
            if (textures.Count > 0 &&
               (CurrentTexture == string.Empty ||
                !textures.Contains(CurrentTexture)))
                CurrentTexture = textures[0];
        }

        protected bool set_texture()
        {
            var texture = GameDatabase.Instance.GetTexture(RootFolder + CurrentTexture, false);
            if (texture == null) return false;
            renderers.ForEach(r => r.material.mainTexture = texture);
            return true;
        }

        public void OnEditorAttach()
        {
            var parent = part.parent;

            if (parent.FindAttachNodeByPart(part).id == FIN_A)
            {
                //Debug.Log(string.Format("{0} This fin is attached to FIN_A", _myModTag));
                CurrentTexture = "Marking_A";
            }

            if (parent.FindAttachNodeByPart(part).id == FIN_B)
            {
                //Debug.Log(string.Format("{0} This fin is attached to FIN_B", _myModTag));
                CurrentTexture = "Marking_B";
            }

            if (parent.FindAttachNodeByPart(part).id == FIN_C)
            {
                //Debug.Log(string.Format("{0} This fin is attached to FIN_C", _myModTag));
                CurrentTexture = "Marking_C";
            }

            if (parent.FindAttachNodeByPart(part).id == FIN_D)
            {
                //Debug.Log(string.Format("{0} This fin is attached to FIN_D", _myModTag));
                CurrentTexture = "Marking_D";
            }

            set_texture();

        }
        
    }

    public static class UtilitiesAndExtensions
    {

        public static T Next<T>(this IList<T> list, T key)
        {
            try
            {
                var i = list.IndexOf(key);
                var ni = (i + 1) % list.Count;
                return list[ni];
            }
            catch { return default(T); }
        }

        public static T Prev<T>(this IList<T> list, T key)
        {
            try
            {
                var i = list.IndexOf(key);
                var ni = i > 0 ? i - 1 : list.Count - 1;
                return list[ni];
            }
            catch { return default(T); }
        }

        public static readonly char[] Comma = { ',' };

        public static string[] ParseLine(string line, char[] delims, bool trim = true)
        {
            if (string.IsNullOrEmpty(line)) return null;

            var array = line.Split(delims, StringSplitOptions.RemoveEmptyEntries);

            if (trim) { for (int i = 0, len = array.Length; i < len; i++) array[i] = array[i].Trim(); }

            return array;
        }

        public static void SetupChooser(string[] names, string[] values, BaseField field)
        {
            setup_chooser_control(names, values, field.uiControlEditor);
            setup_chooser_control(names, values, field.uiControlFlight);
        }

        static void setup_chooser_control(string[] names, string[] values, UI_Control control)
        {
            var current_editor = control as UI_ChooseOption;
            if (current_editor == null) return;
            current_editor.display = names;
            current_editor.options = values;
        }

        public static void EnableField(BaseField field, bool enable = true, UI_Scene scene = UI_Scene.All)
        {
            if ((scene & UI_Scene.Flight) == UI_Scene.Flight) field.guiActive = enable;
            if ((scene & UI_Scene.Editor) == UI_Scene.Editor) field.guiActiveEditor = enable;
            if (field.uiControlEditor != null) field.uiControlEditor.controlEnabled = enable;
            if (field.uiControlFlight != null) field.uiControlFlight.controlEnabled = enable;
        }


    }

}
