using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEditor.Build.Content;
using UnityEngine;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.ResourceManagement.Util;
using UnityEngine.Serialization;
using UnityEngine.U2D;

namespace UnityEditor.AddressableAssets.Settings
{
    interface IReferenceEntryData
    {
        string AssetPath { get; }
        string address { get; set; }
        bool IsInResources { get; set; }
        HashSet<string> labels { get; }
    }

    internal struct ImplicitAssetEntry : IReferenceEntryData
    {
        public string AssetPath { get; set; }
        public string address { get; set; }
        public bool IsInResources { get; set; }
        public HashSet<string> labels { get; set; }
    }

    /// <summary>
    /// Contains data for an addressable asset entry.
    /// </summary>
    [Serializable]
    public class AddressableAssetEntry : ISerializationCallbackReceiver, IReferenceEntryData
    {
#pragma warning disable 0618
        static Type typeof_AddressableAssetEntryCollection = typeof(AddressableAssetEntryCollection);
#pragma warning restore 0618

        internal const string EditorSceneListName = "EditorSceneList";
        internal const string EditorSceneListPath = "Scenes In Build";

        internal const string ResourcesName = "Resources";
        internal const string ResourcesPath = "*/Resources/";

        [FormerlySerializedAs("m_guid")]
        [SerializeField]
        string m_GUID;

        [FormerlySerializedAs("m_address")]
        [SerializeField]
        string m_Address;

        [FormerlySerializedAs("m_readOnly")]
        [SerializeField]
        bool m_ReadOnly;

        [FormerlySerializedAs("m_serializedLabels")]
        [SerializeField]
        List<string> m_SerializedLabels;

        HashSet<string> m_Labels = new HashSet<string>();

        /// <summary>
        /// If true, this asset was changed after being built into an Addressable Group marked 'Cannot Change Post Release'.
        /// </summary>
        [SerializeField]
        public bool FlaggedDuringContentUpdateRestriction = false;

        internal virtual bool HasSettings()
        {
            return false;
        }

        /// <summary>
        /// Flag indicating if an AssetEntry is a folder or not.
        /// </summary>
        public bool IsFolder { get; set; }

        /// <summary>
        /// List of AddressableAssetEntries that are considered sub-assets of a main Asset.  Typically used for Folder entires.
        /// </summary>
        [NonSerialized]
        public List<AddressableAssetEntry> SubAssets = new List<AddressableAssetEntry>();

        [NonSerialized]
        AddressableAssetGroup m_ParentGroup;

        /// <summary>
        /// The asset group that this entry belongs to.  An entry can only belong to a single group at a time.
        /// </summary>
        public AddressableAssetGroup parentGroup
        {
            get { return m_ParentGroup; }
            set { m_ParentGroup = value; }
        }

        /// <summary>
        /// The id for the bundle file.
        /// </summary>
        public string BundleFileId { get; set; }

        /// <summary>
        /// The asset guid.
        /// </summary>
        public string guid
        {
            get { return m_GUID; }
        }

        /// <summary>
        /// The address of the entry.  This is treated as the primary key in the ResourceManager system.
        /// </summary>
        public string address
        {
            get { return m_Address; }
            set { SetAddress(value); }
        }

        /// <summary>
        /// Set the address of the entry.
        /// </summary>
        /// <param name="addr">The address.</param>
        /// <param name="postEvent">Post modification event.</param>
        public void SetAddress(string addr, bool postEvent = true)
        {
            if (m_Address != addr)
            {
                m_Address = addr;
                if (string.IsNullOrEmpty(m_Address))
                    m_Address = AssetPath;
                if (m_GUID.Length > 0 && m_Address.Contains('[') && m_Address.Contains(']'))
                    Debug.LogErrorFormat("Address '{0}' cannot contain '[ ]'.", m_Address);
                SetDirty(AddressableAssetSettings.ModificationEvent.EntryModified, this, postEvent);
            }
        }

        /// <summary>
        /// Read only state of the entry.
        /// </summary>
        public bool ReadOnly
        {
            get { return m_ReadOnly; }
            set
            {
                if (m_ReadOnly != value)
                {
                    m_ReadOnly = value;
                    SetDirty(AddressableAssetSettings.ModificationEvent.EntryModified, this, true);
                }
            }
        }

        /// <summary>
        /// Is the asset in a resource folder.
        /// </summary>
        public bool IsInResources { get; set; }

        /// <summary>
        /// Is scene in scene list.
        /// </summary>
        public bool IsInSceneList { get; set; }

        /// <summary>
        /// Is a sub asset.  For example an asset in an addressable folder.
        /// </summary>
        public bool IsSubAsset { get; set; }

        /// <summary>
        /// Stores a reference to the parent entry. Only used if the asset is a sub asset.
        /// </summary>
        public AddressableAssetEntry ParentEntry { get; set; }

        bool m_CheckedIsScene;
        bool m_IsScene;

        /// <summary>
        /// Is this entry for a scene.
        /// </summary>
        public bool IsScene
        {
            get
            {
                if (!m_CheckedIsScene)
                {
                    m_CheckedIsScene = true;
                    m_IsScene = AssetPath.EndsWith(".unity", StringComparison.OrdinalIgnoreCase);
                }

                return m_IsScene;
            }
        }

        /// <summary>
        /// The set of labels for this entry.  There is no inherent limit to the number of labels.
        /// </summary>
        public HashSet<string> labels
        {
            get { return m_Labels; }
        }

        internal Type m_cachedMainAssetType = null;

        /// <summary>
        /// The System.Type of the main Object referenced by an AddressableAssetEntry
        /// </summary>
        public Type MainAssetType
        {
            get
            {
                if (m_cachedMainAssetType == null)
                {
                    m_cachedMainAssetType = AssetDatabase.GetMainAssetTypeAtPath(AssetPath);
                    if (m_cachedMainAssetType == null)
                        return typeof(object); // do not cache a bad type lookup.
                }

                return m_cachedMainAssetType;
            }
        }

        /// <summary>
        /// Set or unset a label on this entry.
        /// </summary>
        /// <param name="label">The label name.</param>
        /// <param name="enable">Setting to true will add the label, false will remove it.</param>
        /// <param name="force">When enable is true, setting force to true will force the label to exist on the parent AddressableAssetSettings object if it does not already.</param>
        /// <param name="postEvent">Post modification event.</param>
        /// <returns></returns>
        public bool SetLabel(string label, bool enable, bool force = false, bool postEvent = true)
        {
            if (enable)
            {
                if (force)
                    parentGroup.Settings.AddLabel(label, postEvent);
                if (m_Labels.Add(label))
                {
                    SetDirty(AddressableAssetSettings.ModificationEvent.EntryModified, this, postEvent);
                    return true;
                }
            }
            else
            {
                if (m_Labels.Remove(label))
                {
                    SetDirty(AddressableAssetSettings.ModificationEvent.EntryModified, this, postEvent);
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Creates a list of keys that can be used to load this entry.
        /// </summary>
        /// <returns>The list of keys.  This will contain the address, the guid as a Hash128 if valid, all assigned labels, and the scene index if applicable.</returns>
        public List<object> CreateKeyList() => CreateKeyList(true, true, true);

        /// <summary>
        /// Creates a list of keys that can be used to load this entry.
        /// </summary>
        /// <returns>The list of keys.  This will contain the address, the guid as a Hash128 if valid, all assigned labels, and the scene index if applicable.</returns>
        internal List<object> CreateKeyList(bool includeAddress, bool includeGUID, bool includeLabels)
        {
            var keys = new List<object>();
            //the address must be the first key
            if (includeAddress)
                keys.Add(address);
            if (includeGUID && !string.IsNullOrEmpty(guid))
                keys.Add(guid);
            if (IsScene && IsInSceneList)
            {
                int index = BuiltinSceneCache.GetSceneIndex(new GUID(guid));
                if (index != -1)
                    keys.Add(index);
            }

            if (includeLabels && labels != null && labels.Count > 0)
            {
                var labelsToRemove = new HashSet<string>();
                var currentLabels = parentGroup.Settings.GetLabels();
                foreach (var l in labels)
                {
                    if (currentLabels.Contains(l))
                        keys.Add(l);
                    else
                        labelsToRemove.Add(l);
                }

                foreach (var l in labelsToRemove)
                    labels.Remove(l);
            }

            return keys;
        }

        internal AddressableAssetEntry(string guid, string address, AddressableAssetGroup parent, bool readOnly)
        {
            if (guid.Length > 0 && address.Contains('[') && address.Contains(']'))
                Debug.LogErrorFormat("Address '{0}' cannot contain '[ ]'.", address);
            m_GUID = guid;
            m_Address = address;
            m_ReadOnly = readOnly;
            parentGroup = parent;
            IsInResources = false;
            IsInSceneList = false;
        }

        Hash128 m_CurrentHash;
        internal Hash128 currentHash
        {
            get
            {
                if (!m_CurrentHash.isValid)
                {
                    m_CurrentHash.Append(m_GUID);
                    m_CurrentHash.Append(m_Address);
                    m_CurrentHash.Append(m_Labels.Count);
                    var flags = (m_ReadOnly ? 1 : 0) | (IsInResources ? 2 : 0) | (IsInSceneList ? 4 : 0) | (IsSubAsset ? 8 : 0);
                    m_CurrentHash.Append(flags);
                    foreach (var l in m_Labels)
                        m_CurrentHash.Append(l);
                }
                return m_CurrentHash;
            }
        }

        internal void SetDirty(AddressableAssetSettings.ModificationEvent e, object o, bool postEvent)
        {
            m_CurrentHash = default;
            if (parentGroup != null)
                parentGroup.SetDirty(e, o, postEvent, true);
        }

        internal void SetCachedPath(string newCachedPath)
        {
            if (newCachedPath != m_cachedAssetPath)
            {
                m_cachedAssetPath = newCachedPath;
                m_MainAsset = null;
                m_TargetAsset = null;
            }
        }

        internal void SetSubObjectType(Type type)
        {
            m_cachedMainAssetType = type;
        }

        internal string m_cachedAssetPath = null;

        /// <summary>
        /// The path of the asset.
        /// </summary>
        public string AssetPath
        {
            get
            {
                if (string.IsNullOrEmpty(m_cachedAssetPath))
                {
                    if (string.IsNullOrEmpty(guid))
                        SetCachedPath(string.Empty);
                    else if (guid == EditorSceneListName)
                        SetCachedPath(EditorSceneListPath);
                    else if (guid == ResourcesName)
                        SetCachedPath(ResourcesPath);
                    else
                        SetCachedPath(AssetDatabase.GUIDToAssetPath(guid));
                }

                return m_cachedAssetPath;
            }
        }

        private UnityEngine.Object m_MainAsset;

        /// <summary>
        /// The main asset object for this entry.
        /// </summary>
        public UnityEngine.Object MainAsset
        {
            get
            {
                if (m_MainAsset == null || !AssetDatabase.TryGetGUIDAndLocalFileIdentifier(m_MainAsset, out string guid, out long localId))
                {
                    AddressableAssetEntry e = this;
                    while (string.IsNullOrEmpty(e.AssetPath))
                    {
                        if (e.ParentEntry == null)
                            return null;
                        e = e.ParentEntry;
                    }

                    m_MainAsset = AssetDatabase.LoadMainAssetAtPath(e.AssetPath);
                }

                return m_MainAsset;
            }
        }

        private UnityEngine.Object m_TargetAsset;

        /// <summary>
        /// The asset object for this entry.
        /// </summary>
        public UnityEngine.Object TargetAsset
        {
            get
            {
                if (m_TargetAsset == null || !AssetDatabase.TryGetGUIDAndLocalFileIdentifier(m_TargetAsset, out string guid, out long localId))
                {
                    if (!string.IsNullOrEmpty(AssetPath) || !IsSubAsset)
                    {
                        m_TargetAsset = MainAsset;
                        return m_TargetAsset;
                    }

                    if (ParentEntry == null || !string.IsNullOrEmpty(AssetPath) || string.IsNullOrEmpty(ParentEntry.AssetPath))
                        return null;

                    var mainAsset = ParentEntry.MainAsset;
                    if (ResourceManagerConfig.ExtractKeyAndSubKey(address, out string mainKey, out string subObjectName))
                    {
                        if (mainAsset != null && mainAsset.GetType() == typeof(SpriteAtlas))
                        {
                            m_TargetAsset = (mainAsset as SpriteAtlas).GetSprite(subObjectName);
                            return m_TargetAsset;
                        }

                        var subObjects = AssetDatabase.LoadAllAssetRepresentationsAtPath(ParentEntry.AssetPath);
                        foreach (var s in subObjects)
                        {
                            if (s != null && s.name == subObjectName)
                            {
                                m_TargetAsset = s;
                                break;
                            }
                        }
                    }
                }

                return m_TargetAsset;
            }
        }

        /// <summary>
        /// The asset load path.  This is used to determine the internal id of resource locations.
        /// </summary>
        /// <param name="isBundled">True if the asset will be contained in an asset bundle.</param>
        /// <returns>Return the runtime path that should be used to load this entry.</returns>
        public string GetAssetLoadPath(bool isBundled)
        {
            return GetAssetLoadPath(isBundled, null);
        }

        /// <summary>
        /// The asset load path.  This is used to determine the internal id of resource locations.
        /// </summary>
        /// <param name="isBundled">True if the asset will be contained in an asset bundle.</param>
        /// <param name="otherLoadPaths">The internal ids of the asset, typically shortened versions of the asset's GUID.</param>
        /// <returns>Return the runtime path that should be used to load this entry.</returns>
        public string GetAssetLoadPath(bool isBundled, HashSet<string> otherLoadPaths)
        {
            if (!IsScene)
            {
                if (IsInResources)
                {
                    return GetResourcesPath(AssetPath);
                }
                else
                {
                    if (isBundled)
                        return parentGroup.GetSchema<GroupSchemas.BundledAssetGroupSchema>().GetAssetLoadPath(AssetPath, otherLoadPaths, p => guid);
                    return AssetPath;
                }
            }
            else
            {
                if (isBundled)
                    return parentGroup.GetSchema<GroupSchemas.BundledAssetGroupSchema>().GetAssetLoadPath(AssetPath, otherLoadPaths, p => guid);
                var path = AssetPath;
                int i = path.LastIndexOf(".unity", StringComparison.OrdinalIgnoreCase);
                if (i > 0)
                    path = path.Substring(0, i);
                i = path.IndexOf("assets/", StringComparison.OrdinalIgnoreCase);
                if (i == 0)
                    path = path.Substring("assets/".Length);
                return path;
            }
        }

        static string GetResourcesPath(string path)
        {
            path = path.Replace('\\', '/');
            int ri = path.LastIndexOf("/resources/", StringComparison.OrdinalIgnoreCase);
            if (ri >= 0)
                path = path.Substring(ri + "/resources/".Length);
            int i = path.LastIndexOf('.');
            if (i > 0)
                path = path.Substring(0, i);
            return path;
        }

        /// <summary>
        /// Gathers all asset entries.  Each explicit entry may contain multiple sub entries. For example, addressable folders create entries for each asset contained within.
        /// </summary>
        /// <param name="assets">The generated list of entries.  For simple entries, this will contain just the entry itself if specified.</param>
        /// <param name="includeSelf">Determines if the entry should be contained in the result list or just sub entries.</param>
        /// <param name="recurseAll">Determines if full recursion should be done when gathering entries.</param>
        /// <param name="includeSubObjects">Determines if sub objects such as sprites should be included.</param>
        /// <param name="entryFilter">Optional predicate to run against each entry, only returning those that pass.  A null filter will return all entries</param>
        public void GatherAllAssets(List<AddressableAssetEntry> assets, bool includeSelf, bool recurseAll, bool includeSubObjects, Func<AddressableAssetEntry, bool> entryFilter = null)
        {
            if (assets == null)
                assets = new List<AddressableAssetEntry>();

            if (guid == EditorSceneListName)
            {
                GatherEditorSceneEntries(assets, entryFilter);
            }
            else if (guid == ResourcesName)
            {
                GatherResourcesEntries(assets, recurseAll, entryFilter);
            }
            else
            {
                if (string.IsNullOrEmpty(AssetPath))
                    return;

                if (AssetDatabase.IsValidFolder(AssetPath))
                {
                    IsFolder = true;
                    List<AddressableAssetEntry> folderEntries = new List<AddressableAssetEntry>();
                    GatherFolderEntries(folderEntries, recurseAll, includeSubObjects, entryFilter);
                    SubAssets = folderEntries;
                    assets.AddRange(folderEntries);
                }
                else
                {
#pragma warning disable 0618
                    bool isEntryCollection = MainAssetType == typeof_AddressableAssetEntryCollection;
#pragma warning restore 0618
                    if (isEntryCollection)
                    {
                        GatherAssetEntryCollectionEntries(assets, entryFilter);
                    }
                    else
                    {
                        if (entryFilter == null || entryFilter(this))
                        {
                            if (includeSelf)
                                assets.Add(this);
                            if (includeSubObjects)
                            {
                                var mainType = AssetDatabase.GetMainAssetTypeAtPath(AssetPath);
                                if (mainType == typeof(SpriteAtlas))
                                    GatherSpriteAtlasEntries(assets);
                                else
                                    GatherSubObjectEntries(assets);
                            }
                        }
                    }
                }
            }
        }

        void GatherSubObjectEntries(List<AddressableAssetEntry> assets)
        {
            var objs = AssetDatabase.LoadAllAssetRepresentationsAtPath(AssetPath);
            for (int i = 0; i < objs.Length; i++)
            {
                var o = objs[i];
                var namedAddress = string.Format("{0}[{1}]", address, o == null ? "missing reference" : o.name);
                if (o == null)
                {
                    Debug.LogWarning(string.Format("NullReference in entry {0}\nAssetPath: {1}\nAddressableAssetGroup: {2}", address, AssetPath, parentGroup.Name));
                    assets.Add(new AddressableAssetEntry("", namedAddress, parentGroup, true));
                }
                else
                {
                    var newEntry = parentGroup.Settings.CreateEntry("", namedAddress, parentGroup, true);
                    newEntry.IsSubAsset = true;
                    newEntry.ParentEntry = this;
                    newEntry.IsInResources = IsInResources;
                    newEntry.SetSubObjectType(o.GetType());
                    assets.Add(newEntry);
                }
            }
        }

        void GatherSpriteAtlasEntries(List<AddressableAssetEntry> assets)
        {
            var settings = parentGroup.Settings;
            var atlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(AssetPath);
            var sprites = new Sprite[atlas.spriteCount];
            atlas.GetSprites(sprites);

            for (int i = 0; i < atlas.spriteCount; i++)
            {
                var spriteName = sprites[i] == null ? "missing reference" : sprites[i].name;
                if (sprites[i] == null)
                {
                    Debug.LogWarning(string.Format("NullReference in entry {0}\nAssetPath: {1}\nAddressableAssetGroup: {2}", address, AssetPath, parentGroup.Name));
                    assets.Add(new AddressableAssetEntry("", spriteName, parentGroup, true));
                }
                else
                {
                    if (spriteName.EndsWith("(Clone)", StringComparison.Ordinal))
                        spriteName = spriteName.Replace("(Clone)", "");

                    var namedAddress = string.Format("{0}[{1}]", address, spriteName);
                    var newEntry = settings.CreateEntry("", namedAddress, parentGroup, true);
                    newEntry.IsSubAsset = true;
                    newEntry.ParentEntry = this;
                    newEntry.IsInResources = IsInResources;
                    assets.Add(newEntry);
                }
            }
        }

#pragma warning disable 0618
        internal void GatherAssetEntryCollectionEntries(List<AddressableAssetEntry> assets, Func<AddressableAssetEntry, bool> entryFilter)
        {
            var settings = parentGroup.Settings;
            var col = AssetDatabase.LoadAssetAtPath<AddressableAssetEntryCollection>(AssetPath);
            if (col != null)
            {
                foreach (var e in col.Entries)
                {
                    var entry = settings.CreateSubEntryIfUnique(e.guid, e.address, this);
                    if (entry != null)
                    {
                        entry.IsInResources = e.IsInResources;
                        foreach (var l in e.labels)
                            entry.SetLabel(l, true, false, false);
                        foreach (var l in m_Labels)
                            entry.SetLabel(l, true, false, false);
                        if (entryFilter == null || entryFilter(entry))
                            assets.Add(entry);
                    }
                }
            }
        }

#pragma warning restore 0618

        internal void GatherFolderEntries(List<AddressableAssetEntry> assets, bool recurseAll, bool includeSubObjects, Func<AddressableAssetEntry, bool> entryFilter)
        {
            var path = AssetPath;
            var settings = parentGroup.Settings;
            foreach (var file in AddressablesFileEnumeration.EnumerateAddressableFolder(path, settings, recurseAll))
            {
                var subGuid = AssetDatabase.AssetPathToGUID(file);
                var entry = settings.CreateSubEntryIfUnique(subGuid, address + GetRelativePath(file, path), this);

                if (entry != null)
                {
                    entry.IsInResources =
                        IsInResources; //if this is a sub-folder of Resources, copy it on down
                    entry.m_Labels = m_Labels;
                    if (entryFilter == null || entryFilter(entry))
                        assets.Add(entry);

                    if (includeSubObjects)
                    {
                        var mainType = AssetDatabase.GetMainAssetTypeAtPath(entry.AssetPath);
                        if (mainType == typeof(SpriteAtlas))
                            entry.GatherSpriteAtlasEntries(assets);
                        else
                            entry.GatherSubObjectEntries(assets);
                    }
                }
            }

            if (!recurseAll)
            {
                foreach (var fo in Directory.EnumerateDirectories(path, "*.*", SearchOption.TopDirectoryOnly))
                {
                    var folder = fo.Replace('\\', '/');
                    if (AssetDatabase.IsValidFolder(folder))
                    {
                        var entry = settings.CreateSubEntryIfUnique(AssetDatabase.AssetPathToGUID(folder), address + GetRelativePath(folder, path), this);
                        if (entry != null)
                        {
                            entry.IsInResources = IsInResources; //if this is a sub-folder of Resources, copy it on down
                            entry.m_Labels = m_Labels;
                            entry.IsFolder = true;
                            if (entryFilter == null || entryFilter(entry))
                                assets.Add(entry);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Gets an entry for this folder entry
        /// </summary>
        /// <param name="subAssetGuid"></param>
        /// <param name="subAssetPath"></param>
        /// <returns></returns>
        /// <remarks>Assumes that this asset entry is a valid folder asset</remarks>
        internal AddressableAssetEntry GetFolderSubEntry(string subAssetGuid, string subAssetPath)
        {
            string assetPath = AssetPath;
            if (string.IsNullOrEmpty(assetPath) || !subAssetPath.StartsWith(assetPath, StringComparison.Ordinal))
                return null;
            var settings = parentGroup.Settings;

            AddressableAssetEntry assetEntry = settings.FindAssetEntry(subAssetGuid);
            if (assetEntry != null)
            {
                if (assetEntry.IsSubAsset && assetEntry.ParentEntry == this)
                    return assetEntry;
                return null;
            }

            string relativePath = subAssetPath.Remove(0, assetPath.Length + 1);
            string[] splitRelativePath = relativePath.Split('/');
            string folderPath = assetPath;
            for (int i = 0; i < splitRelativePath.Length - 1; ++i)
            {
                folderPath = folderPath + "/" + splitRelativePath[i];
                string folderGuid = AssetDatabase.AssetPathToGUID(folderPath);
                if (!AddressableAssetUtility.IsPathValidForEntry(folderPath))
                    return null;
                var folderEntry = settings.CreateSubEntryIfUnique(folderGuid, address + "/" + folderPath.Remove(assetPath.Length), this);
                if (folderEntry != null)
                {
                    folderEntry.IsInResources = IsInResources;
                    folderEntry.m_Labels = m_Labels;
                    folderEntry.IsFolder = true;
                }
                else
                    return null;
            }

            assetEntry = settings.CreateSubEntryIfUnique(subAssetGuid, address + "/" + relativePath, this);

            if (assetEntry != null)
            {
                assetEntry.m_Labels = m_Labels;
                assetEntry.IsFolder = false;
            }

            if (assetEntry == null || assetEntry.IsSubAsset == false)
                return null;
            return assetEntry;
        }

#pragma warning disable 0618
        internal AddressableAssetEntry GetAssetCollectionSubEntry(string subAssetGuid)
        {
            if (AssetPath.EndsWith(".asset", StringComparison.OrdinalIgnoreCase) && MainAssetType == typeof_AddressableAssetEntryCollection)
            {
                List<AddressableAssetEntry> implicitEntries = new List<AddressableAssetEntry>();
                GatherAssetEntryCollectionEntries(implicitEntries, null);
                return implicitEntries.FirstOrDefault(ie => ie.guid == subAssetGuid);
            }

            return null;
        }

#pragma warning restore 0618

        internal void GatherResourcesEntries(List<AddressableAssetEntry> assets, bool recurseAll, Func<AddressableAssetEntry, bool> entryFilter)
        {
            var settings = parentGroup.Settings;
            var pd = parentGroup.GetSchema<GroupSchemas.PlayerDataGroupSchema>();
            if (pd.IncludeResourcesFolders)
            {
                foreach (var resourcesDir in GetResourceDirectories())
                {
                    foreach (var file in Directory.GetFiles(resourcesDir, "*.*", recurseAll ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
                    {
                        if (AddressableAssetUtility.IsPathValidForEntry(file))
                        {
                            var g = AssetDatabase.AssetPathToGUID(file);
                            var addr = GetResourcesPath(file);
                            var entry = settings.CreateSubEntryIfUnique(g, addr, this);

                            if (entry != null) //TODO - it's probably really bad if this is ever null. need some error detection
                            {
                                entry.IsInResources = true;
                                entry.m_Labels = m_Labels;
                                if (entryFilter == null || entryFilter(entry))
                                    assets.Add(entry);
                            }
                        }
                    }

                    if (!recurseAll)
                    {
                        foreach (var folder in Directory.GetDirectories(resourcesDir))
                        {
                            if (AssetDatabase.IsValidFolder(folder))
                            {
                                var entry = settings.CreateSubEntryIfUnique(AssetDatabase.AssetPathToGUID(folder), GetResourcesPath(folder), this);
                                if (entry != null) //TODO - it's probably really bad if this is ever null. need some error detection
                                {
                                    entry.IsInResources = true;
                                    entry.m_Labels = m_Labels;
                                    if (entryFilter == null || entryFilter(entry))
                                        assets.Add(entry);
                                }
                            }
                        }
                    }
                }
            }
        }

        static IEnumerable<string> GetResourceDirectories()
        {
            string[] resourcesGuids = AssetDatabase.FindAssets("Resources", new string[] {"Assets", "Packages"});
            foreach (string resourcesGuid in resourcesGuids)
            {
                string resourcesAssetPath = AssetDatabase.GUIDToAssetPath(resourcesGuid);
                if (resourcesAssetPath.EndsWith("/resources", StringComparison.OrdinalIgnoreCase) && AssetDatabase.IsValidFolder(resourcesAssetPath) && Directory.Exists(resourcesAssetPath))
                {
                    yield return resourcesAssetPath;
                }
            }
        }

        void GatherEditorSceneEntries(List<AddressableAssetEntry> assets, Func<AddressableAssetEntry, bool> entryFilter)
        {
            var settings = parentGroup.Settings;
            var pd = parentGroup.GetSchema<GroupSchemas.PlayerDataGroupSchema>();
            if (pd.IncludeBuildSettingsScenes)
            {
                foreach (var s in BuiltinSceneCache.scenes)
                {
                    if (s.enabled)
                    {
                        var entry = settings.CreateSubEntryIfUnique(s.guid.ToString(), Path.GetFileNameWithoutExtension(s.path), this);
                        if (entry != null) //TODO - it's probably really bad if this is ever null. need some error detection
                        {
                            entry.IsInSceneList = true;
                            entry.m_Labels = m_Labels;
                            if (entryFilter == null || entryFilter(entry))
                                assets.Add(entry);
                        }
                    }
                }
            }
        }

        internal AddressableAssetEntry GetImplicitEntry(string implicitAssetGuid, string implicitAssetPath)
        {
            var e = GetAssetCollectionSubEntry(implicitAssetGuid);
            return e != null ? e : GetFolderSubEntry(implicitAssetGuid, implicitAssetPath);
        }

        string GetRelativePath(string file, string path)
        {
            return file.Substring(path.Length);
        }

        /// <summary>
        /// Implementation of ISerializationCallbackReceiver.  Converts data to serializable form before serialization.
        /// </summary>
        public void OnBeforeSerialize()
        {
            m_SerializedLabels = new List<string>();
            foreach (var t in m_Labels)
                m_SerializedLabels.Add(t);
        }

        /// <summary>
        /// Implementation of ISerializationCallbackReceiver.  Converts data from serializable form after deserialization.
        /// </summary>
        public void OnAfterDeserialize()
        {
            m_Labels = new HashSet<string>();
            foreach (var s in m_SerializedLabels)
                m_Labels.Add(s);
            m_SerializedLabels = null;
            m_cachedMainAssetType = null;
            m_cachedAssetPath = null;
            m_CheckedIsScene = false;
            m_MainAsset = null;
            m_TargetAsset = null;
            SubAssets?.Clear();
        }

        /// <summary>
        /// Returns the address of the AddressableAssetEntry.
        /// </summary>
        /// <returns>The address of the AddressableAssetEntry</returns>
        public override string ToString()
        {
            return m_Address;
        }

        /// <summary>
        /// Create all entries for this addressable asset.  This will expand subassets (Sprites, Meshes, etc) and also different representations.
        /// </summary>
        /// <param name="entries">The list of entries to fill in.</param>
        /// <param name="isBundled">Whether the entry is bundles or not.  This will affect the load path.</param>
        /// <param name="providerType">The provider type for the main entry.</param>
        /// <param name="dependencies">Keys of dependencies</param>
        /// <param name="extraData">Extra data to append to catalog entries.</param>
        /// <param name="providerTypes">Any unknown provider types are added to this set in order to ensure they are not stripped.</param>
        public void CreateCatalogEntries(List<ContentCatalogDataEntry> entries, bool isBundled, string providerType, IEnumerable<object> dependencies, object extraData, HashSet<Type> providerTypes)
        {
            CreateCatalogEntries(entries, isBundled, providerType, dependencies, extraData, null, providerTypes, true, true, true, null);
        }

        /// <summary>
        /// Create all entries for this addressable asset.  This will expand subassets (Sprites, Meshes, etc) and also different representations.
        /// </summary>
        /// <param name="entries">The list of entries to fill in.</param>
        /// <param name="isBundled">Whether the entry is bundles or not.  This will affect the load path.</param>
        /// <param name="providerType">The provider type for the main entry.</param>
        /// <param name="dependencies">Keys of dependencies</param>
        /// <param name="extraData">Extra data to append to catalog entries.</param>
        /// <param name="depInfo">Map of guids to AssetLoadInfo for object identifiers in an Asset.  If null, ContentBuildInterface gathers object ids automatically.</param>
        /// <param name="providerTypes">Any unknown provider types are added to this set in order to ensure they are not stripped.</param>
        /// <param name="includeAddress">Flag indicating if address locations should be included</param>
        /// <param name="includeGUID">Flag indicating if guid locations should be included</param>
        /// <param name="includeLabels">Flag indicating if label locations should be included</param>
        /// <param name="assetsInBundle">The internal ids of the asset, typically shortened versions of the asset's GUID.</param>
        public void CreateCatalogEntries(List<ContentCatalogDataEntry> entries, bool isBundled, string providerType, IEnumerable<object> dependencies, object extraData,
            Dictionary<GUID, AssetLoadInfo> depInfo, HashSet<Type> providerTypes, bool includeAddress, bool includeGUID, bool includeLabels, HashSet<string> assetsInBundle)
        {
            if (string.IsNullOrEmpty(AssetPath))
                return;

            string assetPath = GetAssetLoadPath(isBundled, assetsInBundle);
            List<object> keyList = CreateKeyList(includeAddress, includeGUID, includeLabels);
            if (keyList.Count == 0)
                return;

            //The asset may have previously been invalid. Since then, it may have been re-imported.
            //This can occur in particular when using ScriptedImporters with complex, multi-step behavior.
            //Double-check the type here in case the asset has been imported correctly after we cached its type.
            if (MainAssetType == typeof(DefaultAsset))
                m_cachedMainAssetType = null;

            Type mainType = AddressableAssetUtility.MapEditorTypeToRuntimeType(MainAssetType, false);
            if ((mainType == null || mainType == typeof(DefaultAsset)) && !IsInResources)
            {
                var t = MainAssetType;
                Debug.LogWarningFormat("Type {0} is in editor assembly {1}.  Asset location with internal id {2} will be stripped and not included in the build.", t.Name, t.Assembly.FullName, assetPath);
                return;
            }

            Type runtimeProvider = GetRuntimeProviderType(providerType, mainType);
            if (runtimeProvider != null)
                providerTypes.Add(runtimeProvider);

            if (!IsScene)
            {
                ObjectIdentifier[] ids = depInfo != null
                    ? depInfo[new GUID(guid)].includedObjects.ToArray()
                    : ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(new GUID(guid), EditorUserBuildSettings.activeBuildTarget);

                foreach (var t in GatherMainAndReferencedSerializedTypes(ids))
                    entries.Add(new ContentCatalogDataEntry(t, assetPath, providerType, keyList, dependencies, extraData));
            }
            else if (mainType != null && mainType != typeof(DefaultAsset))
            {
                entries.Add(new ContentCatalogDataEntry(mainType, assetPath, providerType, keyList, dependencies, extraData));
            }
        }

        static internal IEnumerable<Type> GatherMainAndReferencedSerializedTypes(ObjectIdentifier[] ids)
        {
            if (ids.Length > 0)
            {
                Type[] typesForObjs = ContentBuildInterface.GetTypeForObjects(ids);
                HashSet<Type> typesSeen = new HashSet<Type>();
                foreach (var objType in typesForObjs)
                {
                    if (!typeof(UnityEngine.Object).IsAssignableFrom(objType))
                        continue;
                    if (typeof(Component).IsAssignableFrom(objType))
                        continue;
                    Type rtType = AddressableAssetUtility.MapEditorTypeToRuntimeType(objType, false);
                    if (rtType != null && rtType != typeof(DefaultAsset) && !typesSeen.Contains(rtType))
                    {
                        yield return rtType;
                        typesSeen.Add(rtType);
                    }
                }
            }
        }

        static internal List<Type> GatherMainObjectTypes(ObjectIdentifier[] ids)
        {
            List<Type> typesSeen = new List<Type>();
            foreach (ObjectIdentifier id in ids)
            {
                Type objType = ContentBuildInterface.GetTypeForObject(id);
                if (typeof(Component).IsAssignableFrom(objType))
                    continue;
                Type rtType = AddressableAssetUtility.MapEditorTypeToRuntimeType(objType, false);
                if (rtType != null && rtType != typeof(DefaultAsset) && !typesSeen.Contains(rtType))
                    typesSeen.Add(rtType);
            }

            return typesSeen;
        }

        internal Type GetRuntimeProviderType(string providerType, Type mainEntryType)
        {
            if (string.IsNullOrEmpty(providerType))
                return null;

            if (mainEntryType == typeof(SpriteAtlas))
                return typeof(AtlasSpriteProvider);
            return Assembly.GetAssembly(typeof(ResourceProviderBase)).GetType(providerType);
        }
    }
}