using System;
using System.Collections.Generic;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;
using UnityEngine.Serialization;
using UnityEngine.U2D;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace UnityEngine.AddressableAssets
{
    /// <summary>
    /// Generic version of AssetReference class.  This should not be used directly as CustomPropertyDrawers do not support generic types.  Instead use the concrete derived classes such as AssetReferenceGameObject.
    /// </summary>
    /// <typeparam name="TObject"></typeparam>
    [Serializable]
    public class AssetReferenceT<TObject> : AssetReference where TObject : Object
    {
        /// <summary>
        /// Construct a new AssetReference object.
        /// </summary>
        /// <param name="guid">The guid of the asset.</param>
        public AssetReferenceT(string guid)
            : base(guid)
        {
        }

#if UNITY_EDITOR
        protected override internal Type DerivedClassType => typeof(TObject);
#endif

        /// <summary>
        /// Load the referenced asset as type TObject.
        /// This cannot be used a second time until the first load is released. If you wish to call load multiple times
        /// on an AssetReference, use <see cref="Addressables.LoadAssetAsync{TObject}(object)"/> and pass your AssetReference in as the key.
        ///
        /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details.
        /// </summary>
        /// <returns>The load operation.</returns>
        //[Obsolete("We have added Async to the name of all asynchronous methods (UnityUpgradable) -> LoadAssetAsync(*)", true)]
        [Obsolete]
        public AsyncOperationHandle<TObject> LoadAsset()
        {
            return LoadAssetAsync();
        }

        /// <summary>
        /// Load the referenced asset as type TObject.
        /// This cannot be used a second time until the first load is released. If you wish to call load multiple times
        /// on an AssetReference, use <see cref="Addressables.LoadAssetAsync{TObject}(object)"/> and pass your AssetReference in as the key.
        /// on an AssetReference, use Addressables.LoadAssetAsync&lt;&gt;() and pass your AssetReference in as the key.
        ///
        /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details.
        /// </summary>
        /// <returns>The load operation.</returns>
        public virtual AsyncOperationHandle<TObject> LoadAssetAsync()
        {
            return LoadAssetAsync<TObject>();
        }

        /// <inheritdoc/>
        public override bool ValidateAsset(Object obj)
        {
            var type = obj.GetType();
            return typeof(TObject).IsAssignableFrom(type);
        }

        /// <summary>
        /// Validates that the asset located at a path is allowable for this asset reference. An asset is allowable if
        /// it is of the correct type or if one of its sub-asset is.
        /// </summary>
        /// <param name="mainAssetPath">The path to the asset in question.</param>
        /// <returns>Whether the referenced asset is valid.</returns>
        public override bool ValidateAsset(string mainAssetPath)
        {
#if UNITY_EDITOR
            Type objType = typeof(TObject);
            if (objType.IsAssignableFrom(AssetDatabase.GetMainAssetTypeAtPath(mainAssetPath)))
                return true;

            var types = AssetPathToTypes.GetTypesForAssetPath(mainAssetPath);
            return types.Contains(objType);
#else
            return false;
#endif
        }

#if UNITY_EDITOR
        /// <summary>
        /// Type-specific override of parent editorAsset.  Used by the editor to represent the main asset referenced.
        /// </summary>
        /// <returns>Editor Asset as type TObject, else null</returns>
        public new TObject editorAsset
        {
            get => base.editorAsset as TObject;
        }
#endif
    }

    /// <summary>
    /// GameObject only asset reference.
    /// </summary>
    [Serializable]
    public class AssetReferenceGameObject : AssetReferenceT<GameObject>
    {
        /// <summary>
        /// Constructs a new reference to a GameObject.
        /// </summary>
        /// <param name="guid">The object guid.</param>
        public AssetReferenceGameObject(string guid) : base(guid)
        {
        }
    }

    /// <summary>
    /// Texture only asset reference.
    /// </summary>
    [Serializable]
    public class AssetReferenceTexture : AssetReferenceT<Texture>
    {
        /// <summary>
        /// Constructs a new reference to a Texture.
        /// </summary>
        /// <param name="guid">The object guid.</param>
        public AssetReferenceTexture(string guid) : base(guid)
        {
        }
    }

    /// <summary>
    /// Texture2D only asset reference.
    /// </summary>
    [Serializable]
    public class AssetReferenceTexture2D : AssetReferenceT<Texture2D>
    {
        /// <summary>
        /// Constructs a new reference to a Texture2D.
        /// </summary>
        /// <param name="guid">The object guid.</param>
        public AssetReferenceTexture2D(string guid) : base(guid)
        {
        }
    }

    /// <summary>
    /// Texture3D only asset reference
    /// </summary>
    [Serializable]
    public class AssetReferenceTexture3D : AssetReferenceT<Texture3D>
    {
        /// <summary>
        /// Constructs a new reference to a Texture3D.
        /// </summary>
        /// <param name="guid">The object guid.</param>
        public AssetReferenceTexture3D(string guid) : base(guid)
        {
        }
    }

    /// <summary>
    /// Sprite only asset reference.
    /// </summary>
    [Serializable]
    public class AssetReferenceSprite : AssetReferenceT<Sprite>
    {
        /// <summary>
        /// Constructs a new reference to a AssetReferenceSprite.
        /// </summary>
        /// <param name="guid">The object guid.</param>
        public AssetReferenceSprite(string guid) : base(guid)
        {
        }

        /// <inheritdoc/>
        public override bool ValidateAsset(string path)
        {
#if UNITY_EDITOR
            if (AssetDatabase.GetMainAssetTypeAtPath(path) == typeof(SpriteAtlas))
                return true;

            var type = AssetDatabase.GetMainAssetTypeAtPath(path);
            bool isTexture = typeof(Texture2D).IsAssignableFrom(type);
            if (isTexture)
            {
                var importer = AssetImporter.GetAtPath(path) as TextureImporter;
                return (importer != null) && (importer.spriteImportMode != SpriteImportMode.None);
            }
#endif
            return false;
        }

#if UNITY_EDITOR
        /// <summary>
        /// Typeless override of parent editorAsset. Used by the editor to represent the main asset referenced.
        /// </summary>
        public new Object editorAsset
        {
            get => GetEditorAssetInternal();
        }

        internal override Object GetEditorAssetInternal()
        {
            if (CachedAsset != null || string.IsNullOrEmpty(m_AssetGUID))
                return CachedAsset;

            var assetPath = AssetDatabase.GUIDToAssetPath(m_AssetGUID);
            Type mainAssetType = AssetDatabase.GetMainAssetTypeAtPath(assetPath);
            Object asset = mainAssetType == typeof(SpriteAtlas) ? AssetDatabase.LoadAssetAtPath(assetPath, typeof(SpriteAtlas)) : AssetDatabase.LoadAssetAtPath(assetPath, DerivedClassType);

            if (DerivedClassType == null)
                return CachedAsset = asset;

            if (asset == null)
                Debug.LogWarning($"Assigned editorAsset does not match type {typeof(SpriteAtlas)} or {DerivedClassType}. EditorAsset will be null.");
            return CachedAsset = asset;
        }

        internal override bool SetEditorAssetInternal(Object value)
        {
            if (value is SpriteAtlas)
                return OnSetEditorAsset(value, typeof(SpriteAtlas));
            return base.SetEditorAssetInternal(value);
        }
#endif
    }

    /// <summary>
    /// Assetreference that only allows atlassed sprites.
    /// </summary>
    [Serializable]
    public class AssetReferenceAtlasedSprite : AssetReferenceT<Sprite>
    {
        /// <summary>
        /// Constructs a new reference to a AssetReferenceAtlasedSprite.
        /// </summary>
        /// <param name="guid">The object guid.</param>
        public AssetReferenceAtlasedSprite(string guid) : base(guid)
        {
        }

#if UNITY_EDITOR
        internal override Object GetEditorAssetInternal()
        {
            if (CachedAsset != null || string.IsNullOrEmpty(m_AssetGUID))
                return CachedAsset;

            var assetPath = AssetDatabase.GUIDToAssetPath(m_AssetGUID);
            Object asset = AssetDatabase.LoadAssetAtPath(assetPath, typeof(SpriteAtlas));

            if (asset == null)
                Debug.LogWarning($"Assigned editorAsset does not match type {typeof(SpriteAtlas)}. EditorAsset will be null.");
            return CachedAsset = asset;
        }

        internal override bool SetEditorAssetInternal(Object value)
        {
            return OnSetEditorAsset(value, typeof(SpriteAtlas));
        }
#endif

        /// <inheritdoc/>
        public override bool ValidateAsset(Object obj)
        {
            return obj is SpriteAtlas;
        }

        /// <inheritdoc/>
        public override bool ValidateAsset(string path)
        {
#if UNITY_EDITOR
            return AssetDatabase.GetMainAssetTypeAtPath(path) == typeof(SpriteAtlas);
#else
            return false;
#endif
        }

#if UNITY_EDITOR
        /// <summary>
        /// SpriteAtlas Type-specific override of parent editorAsset. Used by the editor to represent the main asset referenced.
        /// </summary>
        public new SpriteAtlas editorAsset
        {
            get
            {
                if (CachedAsset != null || string.IsNullOrEmpty(AssetGUID))
                    return CachedAsset as SpriteAtlas;

                var assetPath = AssetDatabase.GUIDToAssetPath(AssetGUID);
                var main = AssetDatabase.LoadMainAssetAtPath(assetPath) as SpriteAtlas;
                if (main != null)
                    CachedAsset = main;
                return main;
            }
        }
#endif
    }

    /// <summary>
    /// Reference to an addressable asset.  This can be used in script to provide fields that can be easily set in the editor and loaded dynamically at runtime.
    /// To determine if the reference is set, use RuntimeKeyIsValid().
    /// </summary>
    [Serializable]
    public class AssetReference : IKeyEvaluator
    {
        [FormerlySerializedAs("m_assetGUID")]
        [SerializeField]
        protected internal string m_AssetGUID = "";

        [SerializeField]
        string m_SubObjectName;

        [SerializeField]
        string m_SubObjectType = null;

        AsyncOperationHandle m_Operation;
#if UNITY_EDITOR
        virtual protected internal Type DerivedClassType { get; }
#endif
        /// <summary>
        /// The AsyncOperationHandle currently being used by the AssetReference.
        /// For example, if you call AssetReference.LoadAssetAsync, this property will return a handle to that operation.
        /// </summary>
        public AsyncOperationHandle OperationHandle
        {
            get { return m_Operation; }
            internal set
            {
                m_Operation = value;
#if UNITY_EDITOR
                if (m_Operation.Status != AsyncOperationStatus.Failed)
                    m_ActiveAssetReferences.Add(this);
#endif
            }
        }

        /// <summary>
        /// The actual key used to request the asset at runtime. RuntimeKeyIsValid() can be used to determine if this reference was set.
        /// </summary>
        public virtual object RuntimeKey
        {
            get
            {
                if (m_AssetGUID == null)
                    m_AssetGUID = string.Empty;
                if (!string.IsNullOrEmpty(m_SubObjectName))
                    return string.Format("{0}[{1}]", m_AssetGUID, m_SubObjectName);
                return m_AssetGUID;
            }
        }

        /// <summary>
        /// Stores the guid of the asset.
        /// </summary>
        public virtual string AssetGUID
        {
            get { return m_AssetGUID; }
        }

        /// <summary>
        /// Stores the name of the sub object.
        /// </summary>
        public virtual string SubObjectName
        {
            get { return m_SubObjectName; }
            set { m_SubObjectName = value; }
        }

        internal virtual Type SubOjbectType
        {
            get
            {
                if (!string.IsNullOrEmpty(m_SubObjectName) && m_SubObjectType != null)
                    return Type.GetType(m_SubObjectType);
                return null;
            }
        }

        /// <summary>
        /// Returns the state of the internal operation.
        /// </summary>
        /// <returns>True if the operation is valid.</returns>
        public bool IsValid()
        {
            return m_Operation.IsValid();
        }

        /// <summary>
        /// Get the loading status of the internal operation.
        /// </summary>
        public bool IsDone
        {
            get { return m_Operation.IsDone; }
        }

#if UNITY_EDITOR
        [InitializeOnLoadMethod]
        static void RegisterForPlaymodeChange()
        {
            EditorApplication.playModeStateChanged -= EditorApplicationOnplayModeStateChanged;
            EditorApplication.playModeStateChanged += EditorApplicationOnplayModeStateChanged;
        }

        static HashSet<AssetReference> m_ActiveAssetReferences = new HashSet<AssetReference>();

        static void EditorApplicationOnplayModeStateChanged(PlayModeStateChange state)
        {
            if (EditorSettings.enterPlayModeOptionsEnabled && Addressables.reinitializeAddressables)
            {
                foreach (AssetReference reference in m_ActiveAssetReferences)
                {
                    reference.ReleaseHandleWhenPlaymodeStateChanged(state);
                }
            }
        }

        void ReleaseHandleWhenPlaymodeStateChanged(PlayModeStateChange state)
        {
            if (m_Operation.IsValid())
                m_Operation.Release();
        }
#endif

        /// <summary>
        /// Construct a new AssetReference object.
        /// </summary>
        public AssetReference()
        {
        }

#if UNITY_EDITOR
        ~AssetReference()
        {
            m_ActiveAssetReferences.Remove(this);
        }
#endif

        /// <summary>
        /// Construct a new AssetReference object.
        /// </summary>
        /// <param name="guid">The guid of the asset.</param>
        public AssetReference(string guid)
        {
            m_AssetGUID = guid;
        }

        /// <summary>
        /// The loaded asset.  This value is only set after the AsyncOperationHandle returned from LoadAssetAsync completes.
        /// It will not be set if only InstantiateAsync is called.  It will be set to null if release is called.
        /// </summary>
        public virtual Object Asset
        {
            get
            {
                if (!m_Operation.IsValid())
                    return null;

                return m_Operation.Result as Object;
            }
        }

#if UNITY_EDITOR
        Object m_CachedAsset;
        string m_CachedGUID = "";

        /// <summary>
        /// Cached Editor Asset.
        /// </summary>
        protected internal Object CachedAsset
        {
            get
            {
                if (m_CachedGUID != m_AssetGUID)
                {
                    m_CachedAsset = null;
                    m_CachedGUID = "";
                }

                return m_CachedAsset;
            }
            set
            {
                m_CachedAsset = value;
                m_CachedGUID = m_AssetGUID;
            }
        }
#endif
        /// <summary>
        /// String representation of asset reference.
        /// </summary>
        /// <returns>The asset guid as a string.</returns>
        public override string ToString()
        {
#if UNITY_EDITOR
            return "[" + m_AssetGUID + "]" + CachedAsset;
#else
            return "[" + m_AssetGUID + "]";
#endif
        }

        static AsyncOperationHandle<T> CreateFailedOperation<T>()
        {
            //this needs to be set in order for ResourceManager.ExceptionHandler to get hooked up to AddressablesImpl.LogException.
            Addressables.InitializeAsync();
            return Addressables.ResourceManager.CreateCompletedOperation(default(T), new Exception("Attempting to load an asset reference that has no asset assigned to it.").Message);
        }

        /// <summary>
        /// Load the referenced asset as type TObject.
        /// This cannot be used a second time until the first load is released. If you wish to call load multiple times
        /// on an AssetReference, use <see cref="Addressables.LoadAssetAsync{TObject}(object)"/> and pass your AssetReference in as the key.
        ///
        /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details.
        /// </summary>
        /// <typeparam name="TObject">The object type.</typeparam>
        /// <returns>The load operation.</returns>
        //[Obsolete("We have added Async to the name of all asynchronous methods (UnityUpgradable) -> LoadAssetAsync(*)", true)]
        [Obsolete]
        public AsyncOperationHandle<TObject> LoadAsset<TObject>()
        {
            return LoadAssetAsync<TObject>();
        }

        /// <summary>
        /// Loads the reference as a scene.
        /// This cannot be used a second time until the first load is released. If you wish to call load multiple times
        /// on an AssetReference, use Addressables.LoadSceneAsync() and pass your AssetReference in as the key.
        /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details.
        /// </summary>
        /// <returns>The operation handle for the scene load.</returns>
        //[Obsolete("We have added Async to the name of all asynchronous methods (UnityUpgradable) -> LoadSceneAsync(*)", true)]
        [Obsolete]
        public AsyncOperationHandle<SceneInstance> LoadScene()
        {
            return LoadSceneAsync();
        }

        /// <summary>
        /// InstantiateAsync the referenced asset as type TObject.
        /// This cannot be used a second time until the first load is released. If you wish to call load multiple times
        /// on an AssetReference, use Addressables.InstantiateAsync() and pass your AssetReference in as the key.
        /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details.
        /// </summary>
        /// <param name="position">Position of the instantiated object.</param>
        /// <param name="rotation">Rotation of the instantiated object.</param>
        /// <param name="parent">The parent of the instantiated object.</param>
        /// <returns>Returns the instantiation operation.</returns>
        //[Obsolete("We have added Async to the name of all asynchronous methods (UnityUpgradable) -> InstantiateAsync(*)", true)]
        [Obsolete]
        public AsyncOperationHandle<GameObject> Instantiate(Vector3 position, Quaternion rotation, Transform parent = null)
        {
            return InstantiateAsync(position, rotation, parent);
        }

        /// <summary>
        /// InstantiateAsync the referenced asset as type TObject.
        /// This cannot be used a second time until the first load is released. If you wish to call load multiple times
        /// on an AssetReference, use Addressables.InstantiateAsync() and pass your AssetReference in as the key.
        /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details.
        /// </summary>
        /// <param name="parent">The parent of the instantiated object.</param>
        /// <param name="instantiateInWorldSpace">Option to retain world space when instantiated with a parent.</param>
        /// <returns>Returns the instantiation operation.</returns>
        //[Obsolete("We have added Async to the name of all asynchronous methods (UnityUpgradable) -> InstantiateAsync(*)", true)]
        [Obsolete]
        public AsyncOperationHandle<GameObject> Instantiate(Transform parent = null, bool instantiateInWorldSpace = false)
        {
            return InstantiateAsync(parent, instantiateInWorldSpace);
        }

        /// <summary>
        /// Load the referenced asset as type TObject.
        /// This cannot be used a second time until the first load is released. If you wish to call load multiple times
        /// on an AssetReference, use <see cref="Addressables.LoadAssetAsync{TObject}(object)"/> and pass your AssetReference in as the key.
        ///
        /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details.
        /// </summary>
        /// <typeparam name="TObject">The object type.</typeparam>
        /// <returns>The load operation if there is not a valid cached operation, otherwise return default operation.</returns>
        public virtual AsyncOperationHandle<TObject> LoadAssetAsync<TObject>()
        {
            AsyncOperationHandle<TObject> result = default(AsyncOperationHandle<TObject>);
            if (m_Operation.IsValid())
                Debug.LogError("Attempting to load AssetReference that has already been loaded. Handle is exposed through getter OperationHandle");
            else
            {
                result = Addressables.LoadAssetAsync<TObject>(RuntimeKey);
                OperationHandle = result;
            }

            return result;
        }

        /// <summary>
        /// Loads the reference as a scene.
        /// This cannot be used a second time until the first load is unloaded. If you wish to call load multiple times
        /// on an AssetReference, use Addressables.LoadSceneAsync() and pass your AssetReference in as the key.
        /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details.
        /// </summary>
        /// <param name="loadMode">Scene load mode.</param>
        /// <param name="activateOnLoad">If false, the scene will load but not activate (for background loading).  The SceneInstance returned has an Activate() method that can be called to do this at a later point.</param>
        /// <param name="priority">Async operation priority for scene loading.</param>
        /// <returns>The operation handle for the request if there is not a valid cached operation, otherwise return default operation</returns>
        public virtual AsyncOperationHandle<SceneInstance> LoadSceneAsync(LoadSceneMode loadMode = LoadSceneMode.Single, bool activateOnLoad = true, int priority = 100)
        {
            AsyncOperationHandle<SceneInstance> result = default(AsyncOperationHandle<SceneInstance>);
            if (m_Operation.IsValid())
                Debug.LogError("Attempting to load AssetReference Scene that has already been loaded. Handle is exposed through getter OperationHandle");
            else
            {
                result = Addressables.LoadSceneAsync(RuntimeKey, loadMode, activateOnLoad, priority);
                OperationHandle = result;
            }

            return result;
        }

        /// <summary>
        /// Unloads the reference as a scene.
        /// </summary>
        /// <returns>The operation handle for the scene load.</returns>
        public virtual AsyncOperationHandle<SceneInstance> UnLoadScene()
        {
            return Addressables.UnloadSceneAsync(m_Operation, true);
        }

        /// <summary>
        /// InstantiateAsync the referenced asset as type TObject.
        /// This cannot be used a second time until the first load is released. If you wish to call load multiple times
        /// on an AssetReference, use Addressables.InstantiateAsync() and pass your AssetReference in as the key.
        /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details.
        /// </summary>
        /// <param name="position">Position of the instantiated object.</param>
        /// <param name="rotation">Rotation of the instantiated object.</param>
        /// <param name="parent">The parent of the instantiated object.</param>
        /// <returns></returns>
        public virtual AsyncOperationHandle<GameObject> InstantiateAsync(Vector3 position, Quaternion rotation, Transform parent = null)
        {
            return Addressables.InstantiateAsync(RuntimeKey, position, rotation, parent, true);
        }

        /// <summary>
        /// InstantiateAsync the referenced asset as type TObject.
        /// This cannot be used a second time until the first load is released. If you wish to call load multiple times
        /// on an AssetReference, use Addressables.InstantiateAsync() and pass your AssetReference in as the key.
        /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details.
        /// </summary>
        /// <param name="parent">The parent of the instantiated object.</param>
        /// <param name="instantiateInWorldSpace">Option to retain world space when instantiated with a parent.</param>
        /// <returns></returns>
        public virtual AsyncOperationHandle<GameObject> InstantiateAsync(Transform parent = null, bool instantiateInWorldSpace = false)
        {
            return Addressables.InstantiateAsync(RuntimeKey, parent, instantiateInWorldSpace, true);
        }

        /// <inheritdoc/>
        public virtual bool RuntimeKeyIsValid()
        {
            Guid result;
            string guid = RuntimeKey.ToString();
            int subObjectIndex = guid.IndexOf('[');
            if (subObjectIndex != -1) //This means we're dealing with a sub-object and need to convert the runtime key.
                guid = guid.Substring(0, subObjectIndex);
            return Guid.TryParse(guid, out result);
        }

        /// <summary>
        /// Release the internal operation handle.
        /// </summary>
        public virtual void ReleaseAsset()
        {
            if (!m_Operation.IsValid())
            {
                Debug.LogWarning("Cannot release a null or unloaded asset.");
                return;
            }

            Addressables.Release(m_Operation);
            m_Operation = default(AsyncOperationHandle);
        }

        /// <summary>
        /// Release an instantiated object.
        /// </summary>
        /// <param name="obj">The object to release.</param>
        public virtual void ReleaseInstance(GameObject obj)
        {
            Addressables.ReleaseInstance(obj);
        }

        /// <summary>
        /// Validates that the referenced asset allowable for this asset reference.
        /// </summary>
        /// <param name="obj">The Object to validate.</param>
        /// <returns>Whether the referenced asset is valid.</returns>
        public virtual bool ValidateAsset(Object obj)
        {
            return true;
        }

        /// <summary>
        /// Validates that the referenced asset allowable for this asset reference.
        /// </summary>
        /// <param name="path">The path to the asset in question.</param>
        /// <returns>Whether the referenced asset is valid.</returns>
        public virtual bool ValidateAsset(string path)
        {
            return true;
        }

#if UNITY_EDITOR

        [SerializeField]
#pragma warning disable CS0414
        bool m_EditorAssetChanged;

#pragma warning restore CS0414

        /// <summary>
        /// Used by the editor to represent the main asset referenced.
        /// </summary>
        public virtual Object editorAsset
        {
            get { return GetEditorAssetInternal(); }
        }

        /// <summary>
        /// Helper function that can be used to override the base class editorAsset accessor.
        /// </summary>
        /// <returns>Returns the main asset referenced used in the editor.</returns>
        internal virtual Object GetEditorAssetInternal()
        {
            if (CachedAsset != null || string.IsNullOrEmpty(m_AssetGUID))
                return CachedAsset;

            var asset = FetchEditorAsset();

            if (DerivedClassType == null)
                return CachedAsset = asset;

            if (asset == null)
                Debug.LogWarning("Assigned editorAsset does not match type " + DerivedClassType + ". EditorAsset will be null.");
            return CachedAsset = asset;
        }

        internal Object FetchEditorAsset()
        {
            var assetPath = AssetDatabase.GUIDToAssetPath(m_AssetGUID);
            var asset = AssetDatabase.LoadAssetAtPath(assetPath, DerivedClassType ?? AssetDatabase.GetMainAssetTypeAtPath(assetPath));
            return asset;
        }

        /// <summary>
        /// Sets the main asset on the AssetReference.  Only valid in the editor, this sets both the editorAsset attribute,
        ///   and the internal asset GUID, which drives the RuntimeKey attribute. If the reference uses a sub object,
        ///   then it will load the editor asset during edit mode and load the sub object during runtime. For example, if
        ///   the AssetReference is set to a sprite within a sprite atlas, the editorAsset is the atlas (loaded during edit mode)
        ///   and the sub object is the sprite (loaded during runtime). If called by AssetReferenceT, will set the editorAsset
        ///   to the requested object if the object is of type T, and null otherwise.
        /// <param name="value">Object to reference</param>
        /// </summary>
        public virtual bool SetEditorAsset(Object value)
        {
            return SetEditorAssetInternal(value);
        }

        internal virtual bool SetEditorAssetInternal(Object value)
        {
            return OnSetEditorAsset(value, DerivedClassType);
        }

        internal bool OnSetEditorAsset(Object value, Type derivedType)
        {
            if (value == null)
            {
                CachedAsset = null;
                m_AssetGUID = string.Empty;
                m_SubObjectName = null;
                m_EditorAssetChanged = true;
                return true;
            }

            if (CachedAsset != value)
            {
                m_SubObjectName = null;
                var path = AssetDatabase.GetAssetOrScenePath(value);
                if (string.IsNullOrEmpty(path))
                {
                    Addressables.LogWarningFormat("Invalid object for AssetReference {0}.", value);
                    return false;
                }

                if (!ValidateAsset(path))
                {
                    Addressables.LogWarningFormat("Invalid asset for AssetReference path = '{0}'.", path);
                    return false;
                }
                else
                {
                    m_AssetGUID = AssetDatabase.AssetPathToGUID(path);
                    Object mainAsset;
                    if (derivedType != null)
                        mainAsset = LocateEditorAssetForTypedAssetReference(value, path, derivedType);
                    else
                    {
                        mainAsset = AssetDatabase.LoadMainAssetAtPath(path);
                        if (value != mainAsset)
                            SetEditorSubObject(value);
                    }

                    CachedAsset = mainAsset;
                }
            }

            m_EditorAssetChanged = true;
            return true;
        }

        internal Object LocateEditorAssetForTypedAssetReference(Object value, string path, Type type)
        {
            Object mainAsset;
            if (value.GetType() != type)
            {
                mainAsset = null;
            }
            else
            {
                mainAsset = AssetDatabase.LoadAssetAtPath(path, type);
                if (mainAsset != value)
                {
                    mainAsset = null;
                    var subAssets = AssetDatabase.LoadAllAssetRepresentationsAtPath(path);
                    foreach (var asset in subAssets)
                    {
                        if (asset.GetType() == type && value == asset)
                        {
                            mainAsset = asset;
                            break;
                        }
                    }
                }
            }

            if (mainAsset == null)
                Debug.LogWarning("Assigned editorAsset does not match type " + type + ". EditorAsset will be null.");

            return mainAsset;
        }


        /// <summary>
        /// Sets the sub object for this asset reference.
        /// </summary>
        /// <param name="value">The sub object.</param>
        /// <returns>True if set correctly.</returns>
        public virtual bool SetEditorSubObject(Object value)
        {
            if (value == null)
            {
                m_SubObjectName = null;
                m_SubObjectType = null;
                m_EditorAssetChanged = true;
                return true;
            }

            if (editorAsset == null)
                return false;
            if (editorAsset.GetType() == typeof(SpriteAtlas))
            {
                var spriteName = value.name;
                if (spriteName.EndsWith("(Clone)", StringComparison.Ordinal))
                    spriteName = spriteName.Replace("(Clone)", "");
                if ((editorAsset as SpriteAtlas).GetSprite(spriteName) == null)
                {
                    Debug.LogWarningFormat("Unable to find sprite {0} in atlas {1}.", spriteName, editorAsset.name);
                    return false;
                }

                m_SubObjectName = spriteName;
                m_SubObjectType = typeof(Sprite).AssemblyQualifiedName;
                m_EditorAssetChanged = true;
                return true;
            }

            var subAssets = AssetDatabase.LoadAllAssetRepresentationsAtPath(AssetDatabase.GUIDToAssetPath(m_AssetGUID));
            foreach (var s in subAssets)
            {
                if (s.name == value.name && s.GetType() == value.GetType())
                {
                    m_SubObjectName = s.name;
                    m_SubObjectType = s.GetType().AssemblyQualifiedName;
                    m_EditorAssetChanged = true;
                    return true;
                }
            }

            return false;
        }

#endif
    }

#if UNITY_EDITOR
    class AssetPathToTypes : AssetPostprocessor
    {
        private static Dictionary<string, HashSet<Type>> s_PathToTypes = new Dictionary<string, HashSet<Type>>();

        public static HashSet<Type> GetTypesForAssetPath(string path)
        {
#if UNITY_2020_1_OR_NEWER
            AssetDatabase.SaveAssetIfDirty(AssetDatabase.GUIDFromAssetPath(path));
#endif
            if (s_PathToTypes.TryGetValue(path, out HashSet<Type> value))
                return value;

            var objectsForAsset = AssetDatabase.LoadAllAssetRepresentationsAtPath(path);
            value = new HashSet<Type>();
            foreach (Object o in objectsForAsset)
                value.Add(o.GetType());
            s_PathToTypes.Add(path, value);
            return value;
        }

        static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
        {
            foreach (string str in importedAssets)
                s_PathToTypes.Remove(str);
            foreach (string str in deletedAssets)
                s_PathToTypes.Remove(str);

            for (int i = 0; i < movedFromAssetPaths.Length; ++i)
            {
                if (s_PathToTypes.TryGetValue(movedFromAssetPaths[i], out var values))
                {
                    s_PathToTypes.Remove(movedFromAssetPaths[i]);
                    s_PathToTypes.Add(movedAssets[i], values);
                }
            }
        }
    }
#endif
}