// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

namespace HoloToolkit.Unity
{
    /// <summary>
    /// By applying the [AudioEvent] attribute to a string field, this 
    /// PropertyDrawer is used instead of a standard string field entry.
    /// </summary>
    [CustomPropertyDrawer(typeof(AudioEventAttribute))]
    public class AudioEventPropertyDrawer : PropertyDrawer
    {
        /// <summary>
        /// Stored list of event names as GUIContent for display
        /// </summary>
        private static GUIContent[] AudioEventNames;

        /// <summary>
        /// For selecting no event
        /// </summary>
        private static readonly string NoEventName = "-- None --";

        /// <summary>
        /// Enable / Disable bank names
        /// </summary>
        private static bool ShowBankNames;

        /// <summary>
        /// Simple case insensitive comparer for GUIContent that contains text
        /// </summary>
        private class EventNameComparer : IComparer<GUIContent>
        {
            public int Compare(GUIContent x, GUIContent y)
            {
                return x.text.ToLower().CompareTo( y.text.ToLower() );
            }
        }

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            // Right click menu handler to enable / disable bank names in the list
            // Having bank names does not affect the data stored in the property
            if (Event.current.type == EventType.ContextClick)
            {
                GenericMenu menu = new GenericMenu();
                menu.AddItem(new GUIContent("Show Banks", "Show bank names in the dropdown"), ShowBankNames, () => ShowBankNames = !ShowBankNames);
                menu.ShowAsContext();
                Event.current.Use();
            }

            GetAllEventNames();

            var currentId = OptionToId(property.stringValue);
            EditorGUI.BeginProperty(position, new GUIContent(property.name), property);
            var newId = EditorGUI.Popup(position, label, currentId, AudioEventNames);

            // Do the necessary modification to property value
            if (newId != currentId)
            {
                if (AudioEventNames[newId].text == NoEventName)
                {
                    property.stringValue = string.Empty;
                }
                else
                {
                    if (ShowBankNames)
                    {
                        // Remove the bank name before storing the string
                        int skip = AudioEventNames[newId].text.IndexOf('/') + 1;
                        property.stringValue = AudioEventNames[newId].text.Substring(skip);
                    }
                    else
                    {
                        property.stringValue = AudioEventNames[newId].text;
                    }
                }
            }

            EditorGUI.EndProperty();
        }

        /// <summary>
        /// Extract all the audio event names from all the audio banks in the project
        /// </summary>
        private static void GetAllEventNames()
        {
            List<GUIContent> eventNames = new List<GUIContent>(200);

            var assets = AssetDatabase.FindAssets("t:AudioEventBank");

            var audioBanks = Resources.FindObjectsOfTypeAll<AudioEventBank>();

            // Check all the banks are loaded and load them if they are not
            if (audioBanks.Length != assets.Length)
            {
                List<AudioEventBank> tmpBanks = new List<AudioEventBank>(assets.Length);
                for(int i=0; i<assets.Length; i++)
                {
                    tmpBanks.Add(AssetDatabase.LoadAssetAtPath<AudioEventBank>(AssetDatabase.GUIDToAssetPath(assets[i])));
                }
                audioBanks = tmpBanks.ToArray();
            }

            for (int bankIndex = 0; bankIndex < audioBanks.Length; bankIndex++)
            {
                var bank = audioBanks[bankIndex];
                for (int eventIndex = 0; eventIndex < bank.Events.Length; eventIndex++)
                {
                    if (ShowBankNames)
                    {
                        // Prepend the bank name to the string, "/" causes a sub-menu to appear
                        // Pro-Tip, place a "/" in your event name to further sub-divide the list of events
                        eventNames.Add(new GUIContent(bank.name + "/" + bank.Events[eventIndex].Name));
                    }
                    else
                    {
                        eventNames.Add(new GUIContent(bank.Events[eventIndex].Name));
                    }
                }
            }

            eventNames.Sort(new EventNameComparer());

            // Make sure the NoEventName is first in the list
            eventNames.Insert(0, new GUIContent(NoEventName));

            AudioEventNames = eventNames.ToArray();
        }

        /// <summary>
        /// Convert a string entry to an option Id
        /// </summary>
        /// <param name="option">Name of string entry to find</param>
        /// <returns>Index in the list, or 0 if not found</returns>
        private static int OptionToId(string option)
        {
            int optionIndex = 0;

            if (AudioEventNames != null)
            {
                for(int i=0; i<AudioEventNames.Length; i++)
                {
                    if (ShowBankNames)
                    {
                        if (AudioEventNames[i].text.EndsWith(option))
                        {
                            optionIndex = i;
                            break;
                        }
                    }
                    else
                    {
                        if (AudioEventNames[i].text == option)
                        {
                            optionIndex = i;
                            break;
                        }
                    }
                }
            }

            return optionIndex;
        }
    }
}