// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using UnityEngine;
using UnityEngine.Rendering;
using UnityEditor.AnimatedValues;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Video;
using UnityEditor.Build;
using UnityEngineObject = UnityEngine.Object;

namespace UnityEditor
{
    [CustomEditor(typeof(VideoPlayer))]
    [CanEditMultipleObjects]
    internal class VideoPlayerEditor : Editor
    {
        class Styles
        {
            public readonly GUIContent dataSourceContent =
                EditorGUIUtility.TrTextContent("Source", "Type of source the movie will be read from.");
            public readonly GUIContent timeUpdateModeContent =
                EditorGUIUtility.TrTextContent("Update Mode", "The clock source to use to derive the current time.");
            public readonly GUIContent videoClipContent =
                EditorGUIUtility.TrTextContent("Video Clip", "VideoClips can be imported using the asset pipeline.");
            public readonly GUIContent urlContent =
                EditorGUIUtility.TrTextContent("URL", "URLs");// can be http:// or file://. File URLs can be relative [file://] or absolute [file:///].  For file URLs, the prefix is optional.");
            public readonly GUIContent browseContent = EditorGUIUtility.TrTextContent("Browse...", "Click to set a file:// URL.  http:// URLs have to be written or copy-pasted manually.");
            public readonly GUIContent playOnAwakeContent =
                EditorGUIUtility.TrTextContent("Play On Awake", "Start playback as soon as the game is started.");
            public readonly GUIContent waitForFirstFrameContent =
                EditorGUIUtility.TrTextContent("Wait For First Frame", "Wait for first frame to be ready before starting playback. When on, player time will only start increasing when the first image is ready.  When off, the first few frames may be skipped while clip preparation is ongoing.");
            public readonly GUIContent loopContent =
                EditorGUIUtility.TrTextContent("Loop", "Start playback at the beginning when end is reached.");
            public readonly GUIContent skipOnDropContent =
                EditorGUIUtility.TrTextContent("Skip On Drop", "Allow to skip frames to catch up with current time.");
            public readonly GUIContent playbackSpeedContent =
                EditorGUIUtility.TrTextContent("Playback Speed", "Increase or decrease the playback speed. 1.0 is the normal speed.");
            public readonly GUIContent renderModeContent =
                EditorGUIUtility.TrTextContent("Render Mode", "Type of object on which the played images will be drawn.");
            public readonly GUIContent cameraContent =
                EditorGUIUtility.TrTextContent("Camera", "Camera where the images will be drawn, behind (Back Plane) or in front of (Front Plane) of the scene.");
            public readonly GUIContent textureContent =
                EditorGUIUtility.TrTextContent("Target Texture", "RenderTexture where the images will be drawn. RenderTextures can be created under the Assets folder and then used on other objects.");
            public readonly GUIContent alphaContent =
                EditorGUIUtility.TrTextContent("Alpha", "A value less than 1.0 will reveal the content behind the video.");
            public readonly GUIContent camera3DLayout =
                EditorGUIUtility.TrTextContent("3D Layout", "Layout of 3D content in the source video. Only meaningful when stereoscopic render is used.");
            public readonly GUIContent audioOutputModeContent =
                EditorGUIUtility.TrTextContent("Audio Output Mode", "Where the audio in the movie will be output.");
            public readonly GUIContent audioSourceContent =
                EditorGUIUtility.TrTextContent("Audio Source", "AudioSource component that will receive this track's audio samples.");
            public readonly GUIContent aspectRatioLabel = EditorGUIUtility.TrTextContent("Aspect Ratio");
            public readonly GUIContent muteLabel = EditorGUIUtility.TrTextContent("Mute");
            public readonly GUIContent volumeLabel = EditorGUIUtility.TrTextContent("Volume");
            public readonly GUIContent controlledAudioTrackCountContent = EditorGUIUtility.TextContent(
                "Controlled Tracks|How many audio tracks will the player control.  The actual number of tracks is only known during playback when the source is a URL.");
            public readonly GUIContent materialRendererContent = EditorGUIUtility.TextContent(
                "Renderer|Renderer that will receive the images. Defaults to the first renderer on the game object.");
            public readonly GUIContent materialPropertyContent = EditorGUIUtility.TextContent(
                "Material Property|Texture property of the current Material that will receive the images.");
            public readonly GUIContent autoSelectMaterialPropertyContent = EditorGUIUtility.TextContent(
                "Auto-Select Property|Automatically select the main texture property of the current Material.");

            public readonly string selectUniformVideoSourceHelp =
                "Select a uniform video source type before a video clip or URL can be selected.";
            public readonly string rendererMaterialsHaveNoTexPropsHelp =
                "Renderer materials have no texture properties.";
            public readonly string someRendererMaterialsHaveNoTexPropsHelp =
                "Some selected renderers have materials with no texture properties.";
            public readonly string invalidTexPropSelectionHelp =
                "Invalid texture property selection.";
            public readonly string oneInvalidTexPropSelectionHelp =
                "1 selected object has an invalid texture property selection.";
            public readonly string someInvalidTexPropSelectionsHelp =
                "{0} selected objects have invalid texture property selections.";
            public readonly string texPropInAllMaterialsHelp =
                "Texture property appears in all renderer materials.";
            public readonly string texPropInSomeMaterialsHelp =
                "Texture property appears in {0} out of {1} renderer materials.";
            public readonly string selectUniformVideoRenderModeHelp =
                "Select a uniform video render mode type before a target camera, render texture or material parameter can be selected.";
            public readonly string selectUniformAudioOutputModeHelp =
                "Select a uniform audio target before audio settings can be edited.";
            public readonly string selectUniformAudioTracksHelp =
                "Only sources with the same number of audio tracks can be edited during multi-selection.";
            public readonly string selectMovieFile = "Select movie file.";
            // FIXME: Array should come from the player.
            public readonly string[] selectMovieFileFilter =
            {
                L10n.Tr("Movie files"), "asf,avi,dv,m4v,mp4,mov,mpg,mpeg,m4v,ogv,vp8,webm,wmv",
                L10n.Tr("All files"), "*"
            };
            public readonly string selectMovieFileRecentPath = "VideoPlayerSelectMovieFileRecentPath";
            public readonly string audioControlsNotEditableHelp =
                "Audio controls not editable when using muliple selection.";
            public readonly string enableDecodingTooltip =
                "Enable decoding for this track.  Only effective when not playing.  When playing from a URL, track details are shown only while playing back.";
            public static readonly int ObjectFieldControlID = "VideoPlayerAudioSourceObjectFieldHash".GetHashCode();
        }

        internal class AudioTrackInfo
        {
            public AudioTrackInfo()
            {
                language = "";
                channelCount = 0;
            }

            public string language;
            public ushort channelCount;
            public GUIContent content;
        }

        static Styles s_Styles;

        SerializedProperty m_DataSource;
        SerializedProperty m_TimeUpdateMode;
        SerializedProperty m_VideoClip;
        SerializedProperty m_Url;
        SerializedProperty m_PlayOnAwake;
        SerializedProperty m_WaitForFirstFrame;
        SerializedProperty m_Looping;
        SerializedProperty m_SkipOnDrop;
        SerializedProperty m_PlaybackSpeed;
        SerializedProperty m_RenderMode;
        SerializedProperty m_TargetTexture;
        SerializedProperty m_TargetCamera;
        SerializedProperty m_TargetMaterialRenderer;
        SerializedProperty m_TargetMaterialProperty;
        SerializedProperty m_AspectRatio;
        SerializedProperty m_TargetCameraAlpha;
        SerializedProperty m_TargetCamera3DLayout;
        SerializedProperty m_AudioOutputMode;
        SerializedProperty m_ControlledAudioTrackCount;
        SerializedProperty m_EnabledAudioTracks;
        SerializedProperty m_TargetAudioSources;
        SerializedProperty m_DirectAudioVolumes;
        SerializedProperty m_DirectAudioMutes;

        readonly AnimBool m_ShowRenderTexture = new AnimBool();
        readonly AnimBool m_ShowTargetCamera = new AnimBool();
        readonly AnimBool m_ShowRenderer = new AnimBool();
        readonly AnimBool m_DataSourceIsClip = new AnimBool();
        readonly AnimBool m_DataSourceIsUrl = new AnimBool();
        readonly AnimBool m_ShowAspectRatio = new AnimBool();
        readonly AnimBool m_ShowAudioControls = new AnimBool();

        ushort m_AudioTrackCountCached = 0;
        GUIContent m_ControlledAudioTrackCountContent;
        List<AudioTrackInfo> m_AudioTrackInfos;

        int m_MaterialPropertyPopupContentHash;
        GUIContent[] m_MaterialPropertyPopupContent;
        int m_MaterialPropertyPopupSelection, m_MaterialPropertyPopupInvalidSelections;
        string m_MultiMaterialInfo = null;

        void OnEnable()
        {
            m_ShowRenderTexture.valueChanged.AddListener(Repaint);
            m_ShowTargetCamera.valueChanged.AddListener(Repaint);
            m_ShowRenderer.valueChanged.AddListener(Repaint);
            m_DataSourceIsClip.valueChanged.AddListener(Repaint);
            m_DataSourceIsUrl.valueChanged.AddListener(Repaint);
            m_ShowAspectRatio.valueChanged.AddListener(Repaint);
            m_ShowAudioControls.valueChanged.AddListener(Repaint);

            m_DataSource = serializedObject.FindProperty("m_DataSource");
            m_TimeUpdateMode = serializedObject.FindProperty("m_TimeUpdateMode");
            m_VideoClip = serializedObject.FindProperty("m_VideoClip");
            m_Url = serializedObject.FindProperty("m_Url");
            m_PlayOnAwake = serializedObject.FindProperty("m_PlayOnAwake");
            m_WaitForFirstFrame = serializedObject.FindProperty("m_WaitForFirstFrame");
            m_Looping = serializedObject.FindProperty("m_Looping");
            m_SkipOnDrop = serializedObject.FindProperty("m_SkipOnDrop");
            m_PlaybackSpeed = serializedObject.FindProperty("m_PlaybackSpeed");
            m_RenderMode = serializedObject.FindProperty("m_RenderMode");
            m_TargetTexture = serializedObject.FindProperty("m_TargetTexture");
            m_TargetCamera = serializedObject.FindProperty("m_TargetCamera");
            m_TargetMaterialRenderer = serializedObject.FindProperty("m_TargetMaterialRenderer");
            m_TargetMaterialProperty = serializedObject.FindProperty("m_TargetMaterialProperty");
            m_AspectRatio = serializedObject.FindProperty("m_AspectRatio");
            m_TargetCameraAlpha = serializedObject.FindProperty("m_TargetCameraAlpha");
            m_TargetCamera3DLayout = serializedObject.FindProperty("m_TargetCamera3DLayout");
            m_AudioOutputMode = serializedObject.FindProperty("m_AudioOutputMode");
            m_ControlledAudioTrackCount = serializedObject.FindProperty("m_ControlledAudioTrackCount");
            m_EnabledAudioTracks = serializedObject.FindProperty("m_EnabledAudioTracks");
            m_TargetAudioSources = serializedObject.FindProperty("m_TargetAudioSources");
            m_DirectAudioVolumes = serializedObject.FindProperty("m_DirectAudioVolumes");
            m_DirectAudioMutes = serializedObject.FindProperty("m_DirectAudioMutes");

            m_ShowRenderTexture.value = m_RenderMode.intValue == (int)VideoRenderMode.RenderTexture;
            m_ShowTargetCamera.value =
                m_RenderMode.intValue == (int)VideoRenderMode.CameraFarPlane ||
                m_RenderMode.intValue == (int)VideoRenderMode.CameraNearPlane;

            m_ShowRenderer.value = m_RenderMode.intValue == (int)VideoRenderMode.MaterialOverride;
            m_MaterialPropertyPopupContent = BuildPopupEntries(targets, GetMaterialPropertyNames, out m_MaterialPropertyPopupSelection, out m_MaterialPropertyPopupInvalidSelections);
            m_MaterialPropertyPopupContentHash = GetMaterialPropertyPopupHash(targets);

            m_DataSourceIsClip.value = m_DataSource.intValue == (int)VideoSource.VideoClip;
            m_DataSourceIsUrl.value = m_DataSource.intValue == (int)VideoSource.Url;
            m_ShowAspectRatio.value = (m_RenderMode.intValue != (int)VideoRenderMode.MaterialOverride) &&
                (m_RenderMode.intValue != (int)VideoRenderMode.APIOnly);
            m_ShowAudioControls.value = m_AudioOutputMode.intValue != (int)VideoAudioOutputMode.None;
            VideoPlayer vp = target as VideoPlayer;
            vp.prepareCompleted += PrepareCompleted;

            m_AudioTrackInfos = new List<AudioTrackInfo>();
        }

        void OnDisable()
        {
            m_ShowRenderTexture.valueChanged.RemoveListener(Repaint);
            m_ShowTargetCamera.valueChanged.RemoveListener(Repaint);
            m_ShowRenderer.valueChanged.RemoveListener(Repaint);
            m_DataSourceIsClip.valueChanged.RemoveListener(Repaint);
            m_DataSourceIsUrl.valueChanged.RemoveListener(Repaint);
            m_ShowAspectRatio.valueChanged.RemoveListener(Repaint);
            m_ShowAudioControls.valueChanged.RemoveListener(Repaint);
        }

        public override void OnInspectorGUI()
        {
            if (s_Styles == null)
                s_Styles = new Styles();

            serializedObject.Update();

            EditorGUILayout.PropertyField(m_DataSource, s_Styles.dataSourceContent);
            HandleDataSourceField();
            EditorGUILayout.PropertyField(m_TimeUpdateMode, s_Styles.timeUpdateModeContent);
            EditorGUILayout.PropertyField(m_PlayOnAwake, s_Styles.playOnAwakeContent);
            EditorGUILayout.PropertyField(m_WaitForFirstFrame, s_Styles.waitForFirstFrameContent);
            EditorGUILayout.PropertyField(m_Looping, s_Styles.loopContent);
            EditorGUILayout.PropertyField(m_SkipOnDrop, s_Styles.skipOnDropContent);
            EditorGUILayout.Slider(m_PlaybackSpeed, 0.0f, 10.0f, s_Styles.playbackSpeedContent);
            EditorGUILayout.Space();

            EditorGUILayout.PropertyField(m_RenderMode, s_Styles.renderModeContent);
            if (m_RenderMode.hasMultipleDifferentValues)
            {
                EditorGUILayout.HelpBox(s_Styles.selectUniformVideoRenderModeHelp, MessageType.Warning, false);
            }
            else
            {
                VideoRenderMode currentRenderMode = (VideoRenderMode)m_RenderMode.intValue;
                HandleTargetField(currentRenderMode);
            }
            HandleAudio();

            serializedObject.ApplyModifiedProperties();
        }

        private void HandleDataSourceField()
        {
            m_DataSourceIsClip.target = m_DataSource.intValue == (int)VideoSource.VideoClip;
            m_DataSourceIsUrl.target = m_DataSource.intValue == (int)VideoSource.Url;

            if (m_DataSource.hasMultipleDifferentValues)
                EditorGUILayout.HelpBox(s_Styles.selectUniformVideoSourceHelp, MessageType.Warning, false);
            else
            {
                if (EditorGUILayout.BeginFadeGroup(m_DataSourceIsClip.faded))
                    EditorGUILayout.PropertyField(m_VideoClip, s_Styles.videoClipContent);
                EditorGUILayout.EndFadeGroup();

                if (EditorGUILayout.BeginFadeGroup(m_DataSourceIsUrl.faded))
                {
                    EditorGUILayout.PropertyField(m_Url, s_Styles.urlContent);
                    Rect browseRect = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight);
                    browseRect.xMin += EditorGUIUtility.labelWidth;
                    browseRect.xMax = browseRect.xMin + GUI.skin.label.CalcSize(s_Styles.browseContent).x + 10;
                    if (EditorGUI.DropdownButton(
                        browseRect, s_Styles.browseContent, FocusType.Passive, GUISkin.current.button))
                    {
                        string path = EditorUtility.OpenFilePanelWithFilters(
                            s_Styles.selectMovieFile,
                            EditorPrefs.GetString(s_Styles.selectMovieFileRecentPath),
                            s_Styles.selectMovieFileFilter);
                        if (!string.IsNullOrEmpty(path))
                        {
                            m_Url.stringValue = "file://" + path;
                            serializedObject.ApplyModifiedProperties();
                            EditorPrefs.SetString(s_Styles.selectMovieFileRecentPath, path);
                        }
                        EditorGUIUtility.ExitGUI();
                    }
                }
                EditorGUILayout.EndFadeGroup();
            }
        }

        private static int GetMaterialPropertyPopupHash(UnityEngine.Object[] objects)
        {
            int hash = 0;
            foreach (VideoPlayer vp in objects)
            {
                if (!vp)
                    continue;
                Renderer renderer = GetTargetRenderer(vp);
                if (!renderer)
                    continue;

                var prop = vp.effectiveTargetMaterialProperty;
                hash ^= prop.GetHashCode();
                foreach (Material material in renderer.sharedMaterials)
                {
                    if (!material)
                        continue;
                    hash ^= material.name.GetHashCode();
                    hash ^= material.shader.name.GetHashCode();
                    for (int i = 0, e = material.shader.GetPropertyCount(); i < e; ++i)
                    {
                        if (material.shader.GetPropertyType(i) == ShaderPropertyType.Texture)
                            hash ^= material.shader.GetPropertyName(i).GetHashCode();
                    }
                }
            }
            return hash;
        }

        private static List<string> GetMaterialPropertyNames(UnityEngine.Object obj, bool multiSelect, out int selection, out bool invalidSelection)
        {
            selection = -1;
            invalidSelection = true;
            List<string> properties = new List<string>();
            VideoPlayer vp = obj as VideoPlayer;
            if (!vp)
                return properties;

            Renderer renderer = GetTargetRenderer(vp);
            if (!renderer)
                return properties;

            var targetMaterialProperty = vp.effectiveTargetMaterialProperty;
            foreach (Material material in renderer.sharedMaterials)
            {
                if (material)
                {
                    for (int i = 0, e = material.shader.GetPropertyCount(); i < e; ++i)
                    {
                        if (material.shader.GetPropertyType(i) == ShaderPropertyType.Texture)
                        {
                            string propertyName = material.shader.GetPropertyName(i);
                            if (!properties.Contains(propertyName))
                                properties.Add(propertyName);
                        }
                    }
                    selection = properties.IndexOf(targetMaterialProperty);
                    invalidSelection = selection < 0 && properties.Count > 0;
                    if (invalidSelection && !multiSelect)
                    {
                        selection = properties.Count;
                        properties.Add(targetMaterialProperty);
                    }
                }
            }
            return properties;
        }

        private delegate List<string> EntryGenerator(UnityEngine.Object obj, bool multiSelect, out int selection, out bool invalidSelection);
        private static GUIContent[] BuildPopupEntries(UnityEngine.Object[] objects, EntryGenerator func, out int selection, out int invalidSelections)
        {
            selection = -1;
            invalidSelections = 0;
            List<string> entries = null;
            foreach (UnityEngine.Object o in objects)
            {
                int newSelection;
                bool invalidSelection;
                List<string> newEntries = func(o, objects.Length > 1, out newSelection, out invalidSelection);
                if (newEntries != null)
                {
                    if (invalidSelection)
                        ++invalidSelections;
                    List<string> mergedEntries =
                        entries == null ? newEntries : new List<string>(entries.Intersect(newEntries));
                    selection = entries == null ? newSelection : selection < 0 || newSelection < 0 || entries[selection] != newEntries[newSelection] ? -1 : mergedEntries.IndexOf(entries[selection]);
                    entries = mergedEntries;
                }
            }
            if (entries == null)
                entries = new List<string>();
            return entries.Select(x => new GUIContent(x)).ToArray();
        }

        private static bool HandleAutoSelect(UnityEngine.Object[] objects, SerializedProperty property)
        {
            var currentValueStr = property.stringValue;
            bool curAutoSelect = currentValueStr == "" || currentValueStr == "<noninit>";
            var newAutoSelect =
                EditorGUILayout.Toggle(s_Styles.autoSelectMaterialPropertyContent, curAutoSelect);

            if (curAutoSelect && !newAutoSelect)
            {
                // Disabling auto-selection means every VideoPlayer now takes the current value of
                // its effective target material. They may not all have the same value, so we have
                // to handle them separately.
                foreach (VideoPlayer vp in objects)
                    if (vp)
                        vp.targetMaterialProperty = vp.effectiveTargetMaterialProperty;
            }
            else if (!curAutoSelect && newAutoSelect)
                property.stringValue = default;

            return newAutoSelect;
        }

        private static void HandlePopup(
            GUIContent content, SerializedProperty property, GUIContent[] entries, int selection, bool editable)
        {
            GUILayout.BeginHorizontal();
            Rect pos = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight);
            GUIContent label = EditorGUI.BeginProperty(pos, content, property);
            EditorGUI.BeginChangeCheck();
            EditorGUI.BeginDisabledGroup(!editable || entries.Length == 0);
            selection = EditorGUI.Popup(pos, label, selection, entries);
            EditorGUI.EndDisabledGroup();
            if (EditorGUI.EndChangeCheck())
                property.stringValue = entries[selection].text;
            EditorGUI.EndProperty();
            GUILayout.EndHorizontal();
        }

        private void HandleTargetField(VideoRenderMode currentRenderMode)
        {
            m_ShowRenderTexture.target = currentRenderMode == VideoRenderMode.RenderTexture;
            if (EditorGUILayout.BeginFadeGroup(m_ShowRenderTexture.faded))
                EditorGUILayout.PropertyField(m_TargetTexture, s_Styles.textureContent);
            EditorGUILayout.EndFadeGroup();

            m_ShowTargetCamera.target = (currentRenderMode == VideoRenderMode.CameraFarPlane) ||
                (currentRenderMode == VideoRenderMode.CameraNearPlane);
            if (EditorGUILayout.BeginFadeGroup(m_ShowTargetCamera.faded))
            {
                EditorGUILayout.PropertyField(m_TargetCamera, s_Styles.cameraContent);
                EditorGUILayout.Slider(m_TargetCameraAlpha, 0.0f, 1.0f, s_Styles.alphaContent);
                EditorGUILayout.PropertyField(m_TargetCamera3DLayout, s_Styles.camera3DLayout);
            }
            EditorGUILayout.EndFadeGroup();

            m_ShowRenderer.target = currentRenderMode == VideoRenderMode.MaterialOverride;
            if (EditorGUILayout.BeginFadeGroup(m_ShowRenderer.faded))
            {
                bool hasMultipleSelection = targets.Length > 1;
                if (hasMultipleSelection)
                    EditorGUILayout.PropertyField(m_TargetMaterialRenderer, s_Styles.materialRendererContent);
                else
                {
                    Rect rect = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight);
                    GUIContent label = EditorGUI.BeginProperty(
                        rect, s_Styles.materialRendererContent, m_TargetMaterialRenderer);
                    EditorGUI.BeginChangeCheck();
                    var newRenderer = EditorGUI.ObjectField(
                        rect, label, GetTargetRenderer((VideoPlayer)target), typeof(Renderer), true);
                    if (EditorGUI.EndChangeCheck())
                        m_TargetMaterialRenderer.objectReferenceValue = newRenderer;
                    EditorGUI.EndProperty();
                }

                var autoSelectEnabled = HandleAutoSelect(targets, m_TargetMaterialProperty);

                int curHash = GetMaterialPropertyPopupHash(targets);
                if (m_MaterialPropertyPopupContentHash != curHash)
                    m_MaterialPropertyPopupContent = BuildPopupEntries(
                        targets, GetMaterialPropertyNames, out m_MaterialPropertyPopupSelection,
                        out m_MaterialPropertyPopupInvalidSelections);

                EditorGUI.indentLevel++;
                HandlePopup(s_Styles.materialPropertyContent, m_TargetMaterialProperty,
                    m_MaterialPropertyPopupContent, m_MaterialPropertyPopupSelection, !autoSelectEnabled);
                EditorGUI.indentLevel--;

                if (m_MaterialPropertyPopupInvalidSelections > 0 || m_MaterialPropertyPopupContent.Length == 0)
                {
                    GUILayout.BeginHorizontal();
                    GUILayout.Space(EditorGUIUtility.labelWidth);
                    if (m_MaterialPropertyPopupContent.Length == 0)
                    {
                        if (!hasMultipleSelection)
                            EditorGUILayout.HelpBox(s_Styles.rendererMaterialsHaveNoTexPropsHelp, MessageType.Warning);
                        else
                            EditorGUILayout.HelpBox(s_Styles.someRendererMaterialsHaveNoTexPropsHelp, MessageType.Warning);
                    }
                    else if (!hasMultipleSelection)
                        EditorGUILayout.HelpBox(s_Styles.invalidTexPropSelectionHelp, MessageType.Warning);
                    else if (m_MaterialPropertyPopupInvalidSelections == 1)
                        EditorGUILayout.HelpBox(s_Styles.oneInvalidTexPropSelectionHelp, MessageType.Warning);
                    else
                        EditorGUILayout.HelpBox(
                            string.Format(s_Styles.someInvalidTexPropSelectionsHelp, m_MaterialPropertyPopupInvalidSelections),
                            MessageType.Warning);
                    GUILayout.EndHorizontal();
                }
                else
                    DisplayMultiMaterialInformation(m_MaterialPropertyPopupContentHash != curHash);

                m_MaterialPropertyPopupContentHash = curHash;
            }
            EditorGUILayout.EndFadeGroup();

            m_ShowAspectRatio.target =
                currentRenderMode != VideoRenderMode.MaterialOverride &&
                currentRenderMode != VideoRenderMode.APIOnly;
            if (EditorGUILayout.BeginFadeGroup(m_ShowAspectRatio.faded))
                EditorGUILayout.PropertyField(m_AspectRatio, s_Styles.aspectRatioLabel);
            EditorGUILayout.EndFadeGroup();
        }

        private void DisplayMultiMaterialInformation(bool refreshInfo)
        {
            if (refreshInfo || m_MultiMaterialInfo == null)
                m_MultiMaterialInfo = GenerateMultiMaterialinformation();

            if (string.IsNullOrEmpty(m_MultiMaterialInfo))
                return;

            GUILayout.BeginHorizontal();
            GUILayout.Space(EditorGUIUtility.labelWidth);
            EditorGUILayout.HelpBox(m_MultiMaterialInfo, MessageType.Info);
            GUILayout.EndHorizontal();
        }

        private string GenerateMultiMaterialinformation()
        {
            if (targets.Length > 1)
                return "";

            VideoPlayer vp = target as VideoPlayer;
            if (!vp)
                return "";

            Renderer renderer = GetTargetRenderer(vp);
            if (!renderer)
                return "";

            var sharedMaterials = renderer.sharedMaterials;
            if (sharedMaterials == null || sharedMaterials.Length <= 1)
                return "";

            var targetMaterials = new List<string>();

            foreach (Material material in sharedMaterials)
            {
                if (!material)
                    continue;
                for (int i = 0, e = material.shader.GetPropertyCount(); i < e; ++i)
                {
                    if ((material.shader.GetPropertyType(i) == ShaderPropertyType.Texture)
                        && (material.shader.GetPropertyName(i) == m_TargetMaterialProperty.stringValue))
                    {
                        targetMaterials.Add(material.name);
                        break;
                    }
                }
            }

            if (targetMaterials.Count == sharedMaterials.Length)
                return s_Styles.texPropInAllMaterialsHelp;

            return string.Format(
                s_Styles.texPropInSomeMaterialsHelp,
                targetMaterials.Count, sharedMaterials.Length) + ": " +
                string.Join(", ", targetMaterials.ToArray());
        }

        private void HandleAudio()
        {
            EditorGUILayout.Space();
            EditorGUILayout.PropertyField(m_AudioOutputMode, s_Styles.audioOutputModeContent);

            m_ShowAudioControls.target = (VideoAudioOutputMode)m_AudioOutputMode.intValue != VideoAudioOutputMode.None;
            if (EditorGUILayout.BeginFadeGroup(m_ShowAudioControls.faded))
            {
                // FIXME: Due to a bug in the behaviour of the widgets used in
                // this multi-selection-capable code, we are disabling
                // multi-select editing for now.  The array of widgets being
                // constructed ends up being garbled (no crash, just incorrect
                // content).  After discussing with @shawn, it was agreed to
                // handle this bug separately and disable multi-editing here for
                // the time being.
                if (serializedObject.isEditingMultipleObjects)
                    EditorGUILayout.HelpBox(s_Styles.audioControlsNotEditableHelp, MessageType.Warning, false);
                else if (m_AudioOutputMode.hasMultipleDifferentValues)
                    EditorGUILayout.HelpBox(s_Styles.selectUniformAudioOutputModeHelp, MessageType.Warning, false);
                else
                {
                    ushort trackCountBefore = (ushort)m_ControlledAudioTrackCount.intValue;
                    bool useControlledAudioTrackCount = HandleControlledAudioTrackCount();
                    if (useControlledAudioTrackCount && m_ControlledAudioTrackCount.hasMultipleDifferentValues)
                        EditorGUILayout.HelpBox(s_Styles.selectUniformAudioTracksHelp, MessageType.Warning, false);
                    else
                    {
                        VideoAudioOutputMode audioOutputMode = (VideoAudioOutputMode)m_AudioOutputMode.intValue;

                        // VideoPlayer::CheckConsistency keeps the array sizes in
                        // sync with the (possible) change done in
                        // HandleControlledAudioTrackCount().  But this adjustment is
                        // only done later so we conservatively only iterate over the
                        // smallest known number of tracks we know are initialized.
                        ushort trackCount = 0;
                        if (useControlledAudioTrackCount)
                        {
                            trackCount = (ushort)Math.Min(
                                (ushort)m_ControlledAudioTrackCount.intValue, trackCountBefore);
                            trackCount = (ushort)Math.Min(trackCount, m_EnabledAudioTracks.arraySize);
                        }
                        else
                        {
                            var clip = ((VideoPlayer)target).clip;
                            if (clip != null)
                                trackCount = clip.audioTrackCount;
                        }

                        for (ushort trackIdx = 0; trackIdx < trackCount; ++trackIdx)
                        {
                            EditorGUILayout.PropertyField(
                                m_EnabledAudioTracks.GetArrayElementAtIndex(trackIdx),
                                GetAudioTrackEnabledContent(trackIdx));

                            EditorGUI.indentLevel++;
                            if (audioOutputMode == VideoAudioOutputMode.AudioSource)
                            {
                                var property = m_TargetAudioSources.GetArrayElementAtIndex(trackIdx);
                                Rect rect = EditorGUILayout.GetControlRect();
                                int id = GUIUtility.GetControlID(Styles.ObjectFieldControlID, FocusType.Keyboard, rect);
                                var label = EditorGUI.BeginProperty(rect, s_Styles.audioSourceContent, property);
                                rect = EditorGUI.PrefixLabel(rect, id, label);
                                EditorGUI.BeginChangeCheck();

                                var result = EditorGUI.DoObjectField(rect, rect, id, property.objectReferenceValue, property.serializedObject.targetObject, typeof(AudioSource), ValidateAudioSource, true);
                                if (EditorGUI.EndChangeCheck())
                                {
                                    if (!EditorUtility.IsPersistent(result))
                                        property.objectReferenceValue = result;
                                    else
                                        Debug.LogWarning("Invalid AudioSource for VideoPlayer. Use an instance of an AudioSource in the scene.");
                                }
                            }
                            else if (audioOutputMode == VideoAudioOutputMode.Direct)
                            {
                                EditorGUILayout.PropertyField(
                                    m_DirectAudioMutes.GetArrayElementAtIndex(trackIdx),
                                    s_Styles.muteLabel);
                                EditorGUILayout.Slider(
                                    m_DirectAudioVolumes.GetArrayElementAtIndex(trackIdx), 0.0f, 1.0f,
                                    s_Styles.volumeLabel);
                            }
                            EditorGUI.indentLevel--;
                        }
                    }
                }
            }
            EditorGUILayout.EndFadeGroup();
        }

        private static UnityEngineObject ValidateAudioSource(UnityEngineObject[] references, Type objtype, SerializedProperty property, EditorGUI.ObjectFieldValidatorOptions options)
        {
            foreach (var reference in references)
            {
                if (reference == null || IsPersistent(reference))
                    continue;
                if (reference is AudioSource)
                    return reference;
                if (reference is GameObject gameObject && gameObject.TryGetComponent<AudioSource>(out _))
                    return gameObject;
            }

            return null;
        }

        GUIContent GetAudioTrackEnabledContent(ushort trackIdx)
        {
            while (m_AudioTrackInfos.Count <= trackIdx)
                m_AudioTrackInfos.Add(new AudioTrackInfo());

            AudioTrackInfo info = m_AudioTrackInfos[trackIdx];

            VideoPlayer player = null;
            if (!serializedObject.isEditingMultipleObjects)
                player = (VideoPlayer)target;

            // Only produce a decorated track label with single-selection.  No
            // point trying to come up with a label that makes the average of
            // the current track params...
            string language = player ? player.GetAudioLanguageCode(trackIdx) : "";
            ushort channelCount = player ? player.GetAudioChannelCount(trackIdx) : (ushort)0;

            if (language != info.language || channelCount != info.channelCount || info.content == null)
            {
                string trackDetails = "";
                if (language.Length > 0)
                    trackDetails += language;

                if (channelCount > 0)
                {
                    if (trackDetails.Length > 0)
                        trackDetails += ", ";
                    trackDetails += channelCount + " ch";
                }

                if (trackDetails.Length > 0)
                    trackDetails = " [" + trackDetails + "]";

                info.content = EditorGUIUtility.TextContent("Track " + trackIdx + trackDetails);
                info.content.tooltip = s_Styles.enableDecodingTooltip;
            }

            return info.content;
        }

        private bool HandleControlledAudioTrackCount()
        {
            // Won't show the widget for number of controlled tracks if we're
            // just using VideoClips (for which editing this property doesn't
            // make sense) or mixing VideoClips and URLs.
            if (m_DataSourceIsClip.value || m_DataSource.hasMultipleDifferentValues)
                return false;

            VideoPlayer player = (VideoPlayer)target;
            ushort audioTrackCount = serializedObject.isEditingMultipleObjects ? (ushort)0 : player.audioTrackCount;
            GUIContent controlledAudioTrackCountContent;
            if (audioTrackCount == 0)
            {
                // Use the simple undecorated label (without number of existing tracks)
                // when editing multiple objects so we don't need fancy logic
                // to explain that the number of discovered tracks is not uniform
                // across multi-selection.
                controlledAudioTrackCountContent = s_Styles.controlledAudioTrackCountContent;
            }
            else
            {
                // Manage a cached decorated GUIContent where we show how many
                // existing tracks there are in the URL being played.  Doing
                // this to avoid repeatedly construct this string.
                if (audioTrackCount != m_AudioTrackCountCached)
                {
                    m_AudioTrackCountCached = audioTrackCount;
                    m_ControlledAudioTrackCountContent = EditorGUIUtility.TextContent(
                        s_Styles.controlledAudioTrackCountContent.text + " [" + audioTrackCount + " found]");
                    m_ControlledAudioTrackCountContent.tooltip = s_Styles.controlledAudioTrackCountContent.tooltip;
                }
                controlledAudioTrackCountContent = m_ControlledAudioTrackCountContent;
            }

            EditorGUILayout.PropertyField(m_ControlledAudioTrackCount, controlledAudioTrackCountContent);
            return true;
        }

        private void PrepareCompleted(VideoPlayer vp)
        {
            Repaint();
        }

        static private Renderer GetTargetRenderer(VideoPlayer vp)
        {
            Renderer renderer = vp.targetMaterialRenderer;
            if (renderer)
                return renderer;
            return vp.gameObject.GetComponent<Renderer>();
        }
    }
}

