using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.Build.Content;
using UnityEngine;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.ResourceManagement.ResourceProviders;

namespace UnityEditor.AddressableAssets.Build.Layout
{
    /// <summary>
    /// A storage class used to gather data about an Addressable build.
    /// </summary>
    [Serializable]
    public class BuildLayout
    {
        /// <summary>
        /// Helper class to wrap header values for BuildLayout
        /// </summary>
        public class LayoutHeader
        {
            /// <summary>
            /// Build layout for this header
            /// </summary>
            internal BuildLayout m_BuildLayout;

            /// <summary>
            /// Build Platform Addressables build is targeting
            /// </summary>
            public BuildTarget BuildTarget
            {
                get
                {
                    if (m_BuildLayout == null)
                        return BuildTarget.NoTarget;
                    return m_BuildLayout.BuildTarget;
                }
            }

            /// <summary>
            /// Hash of the build results
            /// </summary>
            public string BuildResultHash
            {
                get
                {
                    if (m_BuildLayout == null)
                        return null;
                    return m_BuildLayout.BuildResultHash;
                }
            }

            /// <summary>
            /// If the build was a new build or an update for a previous build
            /// </summary>
            public BuildType BuildType
            {
                get
                {
                    if (m_BuildLayout == null)
                        return BuildType.NewBuild;
                    return m_BuildLayout.BuildType;
                }
            }

            /// <summary>
            /// DateTime at the start of building Addressables
            /// </summary>
            public DateTime BuildStart
            {
                get
                {
                    if (m_BuildLayout == null)
                        return DateTime.MinValue;
                    return m_BuildLayout.BuildStart;
                }
            }

            /// <summary>
            /// Time in seconds taken to build Addressables Content
            /// </summary>
            public double Duration
            {
                get
                {
                    if (m_BuildLayout == null)
                        return 0;
                    return m_BuildLayout.Duration;
                }
            }

            /// <summary>
            /// Null or Empty if the build completed successfully, else contains error causing the failure
            /// </summary>
            public string BuildError
            {
                get
                {
                    if (m_BuildLayout == null)
                        return "";
                    return m_BuildLayout.BuildError;
                }
            }
        }

        /// <summary>
        /// Helper object to get header values for this build layout
        /// </summary>
        public LayoutHeader Header
        {
            get
            {
                if (m_Header == null)
                    m_Header = new LayoutHeader() {m_BuildLayout = this};
                return m_Header;
            }
        }

        private LayoutHeader m_Header;

        #region HeaderValues // Any values in here should also be in BuildLayoutHeader class

        /// <summary>
        /// Build Platform Addressables build is targeting
        /// </summary>
        public BuildTarget BuildTarget;

        /// <summary>
        /// Hash of the build results
        /// </summary>
        public string BuildResultHash;

        /// <summary>
        /// If the build was a new build or an update for a previous build
        /// </summary>
        public BuildType BuildType;

        /// <summary>
        /// DateTime at the start of building Addressables
        /// </summary>
        public DateTime BuildStart
        {
            get
            {
                if (m_BuildStartDateTime.Year > 2000)
                    return m_BuildStartDateTime;
                if (DateTime.TryParse(BuildStartTime, out DateTime result))
                {
                    m_BuildStartDateTime = result;
                    return m_BuildStartDateTime;
                }
                return DateTime.MinValue;
            }
            set
            {
                BuildStartTime = value.ToString();
            }
        }
        private DateTime m_BuildStartDateTime;

        [SerializeField]
        internal string BuildStartTime;

        /// <summary>
        /// Time in seconds taken to build Addressables Content
        /// </summary>
        public double Duration;

        /// <summary>
        /// Null or Empty if the build completed successfully, else contains error causing the failure
        /// </summary>
        public string BuildError;

        #endregion // End of header values

        /// <summary>
        /// Version of the Unity edtior used to perform the build.
        /// </summary>
        public string UnityVersion;

        /// <summary>
        /// Version of the Addressables package used to perform the build.
        /// </summary>
        public string PackageVersion;

        /// <summary>
        /// Player build version for the build, this is a timestamp if PlayerVersionOverride is not set in the settings
        /// </summary>
        public string PlayerBuildVersion;

        /// <summary>
        /// Settings used by the Addressables settings at the time of building
        /// </summary>
        public AddressablesEditorData AddressablesEditorSettings;

        /// <summary>
        /// Values used by the Addressables runtime
        /// </summary>
        public AddressablesRuntimeData AddressablesRuntimeSettings;

        /// <summary>
        /// Name of the build script to build
        /// </summary>
        public string BuildScript;

        /// <summary>
        /// Default group at the time of building
        /// </summary>
        [SerializeReference]
        public Group DefaultGroup;

        /// <summary>
        /// The Addressable Groups that reference this data
        /// </summary>
        [SerializeReference]
        public List<Group> Groups = new List<Group>();

        /// <summary>
        /// The List of AssetBundles that were built without a group associated to them, such as the BuiltIn Shaders Bundle and the MonoScript Bundle
        /// </summary>
        [SerializeReference]
        public List<Bundle> BuiltInBundles = new List<Bundle>();

        /// <summary>
        /// List of assets with implicitly included Objects
        /// </summary>
        public List<AssetDuplicationData> DuplicatedAssets = new List<AssetDuplicationData>();

        /// <summary>
        /// The build path on disk of the default local content catalog
        /// </summary>
        [SerializeField]
        internal string LocalCatalogBuildPath;

        /// <summary>
        /// The build path of the remote content catalog, if one was built
        /// </summary>
        [SerializeField]
        internal string RemoteCatalogBuildPath;

        internal string m_FilePath;

        private bool m_HeaderRead = false;
        private bool m_BodyRead = false;

        private FileStream m_FileStream = null;
        private StreamReader m_StreamReader = null;

        /// <summary>
        /// Used for serialising the header info for the BuildLayout.
        /// Names must match values in BuildLayout class
        /// </summary>
        [Serializable]
        private class BuildLayoutHeader
        {
            public BuildTarget BuildTarget;
            public string BuildResultHash;
            public BuildType BuildType;
            public string BuildStartTime;
            public double Duration;
            public string BuildError;
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="path">Path to the BuildLayout json file on disk</param>
        /// <param name="readHeader">If the basic header information should be read</param>
        /// <param name="readFullFile">If the full build layout should be read</param>
        /// <returns></returns>
        public static BuildLayout Open(string path, bool readHeader = true, bool readFullFile = false)
        {
            if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path))
            {
                Debug.LogError($"Invalid path provided : {path}");
                return null;
            }

            BuildLayout readLayout = new BuildLayout
            {
                m_FilePath = path
            };

            if (readFullFile)
                readLayout.ReadFull();
            else if (readHeader)
                readLayout.ReadHeader();

            return readLayout;
        }

        /// <summary>
        /// Writes json file for the build layout to the destination path
        /// </summary>
        /// <param name="destinationPath">File path to write build layout</param>
        /// <param name="prettyPrint">If json should be written using pretty print</param>
        public void WriteToFile(string destinationPath, bool prettyPrint)
        {
            Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));

            string versionElementString = "\"UnityVersion\":";
            string headerJson = null;
            string bodyJson = JsonUtility.ToJson(this, prettyPrint);

            if (prettyPrint)
            {
                BuildLayoutHeader header = new BuildLayoutHeader()
                {
                    BuildTarget = this.BuildTarget,
                    BuildResultHash = this.BuildResultHash,
                    BuildType = this.BuildType,
                    BuildStartTime = this.BuildStartTime,
                    Duration = this.Duration,
                    BuildError = this.BuildError
                };
                headerJson = JsonUtility.ToJson(header, false);
                headerJson = headerJson.Remove(headerJson.Length - 1, 1) + ',';
            }

            int index = bodyJson.IndexOf(versionElementString);
            if (prettyPrint)
                bodyJson = bodyJson.Remove(0, index);
            else
                bodyJson = bodyJson.Insert(index, "\n");

            using (FileStream s = System.IO.File.Open(destinationPath, FileMode.Create))
            {
                using (StreamWriter sw = new StreamWriter(s))
                {
                    if (prettyPrint)
                        sw.WriteLine(headerJson);
                    sw.Write(bodyJson);
                }
            }
        }

        /// <summary>
        /// Closes streams for loading the build layout
        /// </summary>
        public void Close()
        {
            if (m_StreamReader != null)
            {
                m_StreamReader.Close();
                m_StreamReader = null;
            }

            if (m_FileStream != null)
            {
                m_FileStream.Close();
                m_FileStream = null;
            }
        }

        /// <summary>
        /// Reads basic information about the build layout
        /// </summary>
        /// <param name="keepFileStreamsActive">If false, the file will be closed after reading the header line.</param>
        /// <returns>true is successful, else false</returns>
        public bool ReadHeader(bool keepFileStreamsActive = false)
        {
            if (m_HeaderRead)
                return true;

            if (string.IsNullOrEmpty(m_FilePath))
            {
                Debug.LogError("Cannot read BuildLayout header, A file has not been selected to open. Open must be called before reading any data");
                return false;
            }

            try
            {
                if (m_FileStream == null)
                {
                    m_FileStream = System.IO.File.Open(m_FilePath, FileMode.Open);
                    m_StreamReader = new StreamReader(m_FileStream);
                }

                string fileJsonText = m_StreamReader.ReadLine();
                int lastComma = fileJsonText.LastIndexOf(',');
                if (lastComma > 0)
                {
                    fileJsonText = fileJsonText.Remove(lastComma) + '}';
                    try
                    {
                        EditorJsonUtility.FromJsonOverwrite(fileJsonText, this);
                    }
                    catch (Exception e)
                    {
                        Debug.LogError($"Failed to read header for BuildLayout at {m_FilePath}, with exception: {e.Message}");
                        return false;
                    }
                }
                else
                {
                    Debug.LogError($"Failed to read header for BuildLayout at {m_FilePath}, invalid json format");
                    return false;
                }

                m_HeaderRead = true;
            }
            catch (Exception e)
            {
                Debug.LogException(e);
                return false;
            }
            finally
            {
                if (!keepFileStreamsActive)
                    Close();
            }

            return true;
        }

        /// <summary>
        /// Reads the full build layout data from file
        /// </summary>
        /// <returns>true is successful, else false</returns>
        public bool ReadFull()
        {
            if (m_BodyRead)
                return true;

            if (string.IsNullOrEmpty(m_FilePath))
            {
                Debug.LogError("Cannot read BuildLayout header, BuildLayout has not open for a file");
                return false;
            }

            try
            {
                if (m_FileStream == null)
                {
                    m_FileStream = System.IO.File.Open(m_FilePath, FileMode.Open);
                    m_StreamReader = new StreamReader(m_FileStream);
                }
                else if (m_HeaderRead)
                {
                    // reset to read the whole file
                    m_FileStream.Position = 0;
                    m_StreamReader.DiscardBufferedData();
                }

                string fileJsonText = m_StreamReader.ReadToEnd();
                EditorJsonUtility.FromJsonOverwrite(fileJsonText, this);
                m_HeaderRead = true;
                m_BodyRead = true;
            }
            catch (Exception e)
            {
                Debug.LogError($"Failed to read header for BuildLayout at {m_FilePath}, with exception: {e.Message}");
                return false;
            }
            finally
            {
                Close();
            }

            return true;
        }

        /// <summary>
        /// Values set for the AddressablesAssetSettings at the time of building
        /// </summary>
        [Serializable]
        public class AddressablesEditorData
        {
            /// <summary>
            /// Hash value of the settings at the time of building
            /// </summary>
            public string SettingsHash;

            /// <summary>
            /// Active Addressables profile set at time of Building
            /// </summary>
            public Profile ActiveProfile;

            /// <summary>
            /// Addressables setting value set for building the remote catalog
            /// </summary>
            public bool BuildRemoteCatalog;

            /// <summary>
            /// Load path for the remote catalog if enabled
            /// </summary>
            public string RemoteCatalogLoadPath;

            /// <summary>
            /// Addressables setting value set for bundling the local catalog
            /// </summary>
            public bool BundleLocalCatalog;

            /// <summary>
            /// Addressables setting value set for optimising the catalog size
            /// </summary>
            public bool OptimizeCatalogSize;

            /// <summary>
            /// Addressables setting value set for time out when downloading catalogs
            /// </summary>
            public int CatalogRequestsTimeout;

            /// <summary>
            /// Runtime setting value set for the maximum number of concurrent web requests
            /// </summary>
            public int MaxConcurrentWebRequests;

            /// <summary>
            /// Addressables setting value set for is to update the remote catalog on startup
            /// </summary>
            public bool DisableCatalogUpdateOnStartup;

            /// <summary>
            /// Addressables setting value set for if the build created unique bundle ids
            /// </summary>
            public bool UniqueBundleIds;

            /// <summary>
            /// Addressables setting value set for if the build used non recursive dependency calculation
            /// </summary>
            public bool NonRecursiveBuilding;

            /// <summary>
            /// Addressables setting value set for if the build used contiguous bundle objects
            /// </summary>
            public bool ContiguousBundles;

            /// <summary>
            /// Addressables setting value set for disabling sub asset representation in the Bundle
            /// </summary>
            public bool DisableSubAssetRepresentations;

            /// <summary>
            /// Internal naming prefix of the built in shaders bundle
            /// </summary>
            public string ShaderBundleNaming;

            /// <summary>
            /// Internal naming prefix of the monoScript bundle,
            /// No MonoScript bundle is built if set to disabled
            /// </summary>
            public string MonoScriptBundleNaming;

            /// <summary>
            /// Addressables setting value set for is the unity version was stripped from the built bundles
            /// </summary>
            public bool StripUnityVersionFromBundleBuild;
        }

        /// <summary>
        /// Values set for runtime initialisation of Addressables
        /// </summary>
        [Serializable]
        public class AddressablesRuntimeData
        {
            /// <summary>
            /// Runtime setting value set for if the runtime will submit profiler events
            /// </summary>
            public bool ProfilerEvents;

            /// <summary>
            /// Runtime setting value set for if resource manager exceptions are logged or not
            /// </summary>
            public bool LogResourceManagerExceptions;

            /// <summary>
            /// Runtime setting value set for catalogs to load (First catalog found in the list is used)
            /// </summary>
            public List<string> CatalogLoadPaths = new List<string>();

            /// <summary>
            /// Hash of the build catalog
            /// </summary>
            public string CatalogHash;
        }

        /// <summary>
        /// Information about the AssetBundleObject
        /// </summary>
        [Serializable]
        public class AssetBundleObjectInfo
        {
            /// <summary>
            /// The size, in bytes, of the AssetBundleObject
            /// </summary>
            public ulong Size;
        }

        /// <summary>
        /// Key value pair of string type
        /// </summary>
        [Serializable]
        public struct StringPair
        {
            /// <summary>
            /// String key
            /// </summary>
            public string Key;

            /// <summary>
            /// String value
            /// </summary>
            public string Value;
        }

        /// <summary>
        /// Addressables Profile data
        /// </summary>
        [Serializable]
        public class Profile
        {
            /// <summary>
            /// Name of the profile
            /// </summary>
            public string Name;

            /// <summary>
            /// ID assigned within the ProfileSettings of the profile
            /// </summary>
            public string Id;

            /// <summary>
            /// Profile variables assigned to the profile
            /// </summary>
            public StringPair[] Values;
        }

        /// <summary>
        /// Data about the AddressableAssetGroup that gets processed during a build.
        /// </summary>
        [Serializable]
        public class Group
        {
            /// <summary>
            /// The Name of the AdressableAssetGroup
            /// </summary>
            public string Name;

            /// <summary>
            /// The Guid of the AddressableAssetGroup
            /// </summary>
            public string Guid;

            /// <summary>
            /// The packing mode as defined by the BundledAssetGroupSchema on the AddressableAssetGroup
            /// </summary>
            public string PackingMode;

            /// <summary>
            /// A list of the AssetBundles associated with the Group
            /// </summary>
            [SerializeReference]
            public List<Bundle> Bundles = new List<Bundle>();

            /// <summary>
            /// Data about the AddressableAssetGroupSchemas associated with the Group
            /// </summary>
            [SerializeReference]
            public List<SchemaData> Schemas = new List<SchemaData>();
        }

        /// <summary>
        /// Data container for AddressableAssetGroupSchemas
        /// </summary>
        [Serializable]
        public class SchemaData : ISerializationCallbackReceiver
        {
            /// <summary>
            /// The Guid of the AddressableAssetGroupSchema
            /// </summary>
            public string Guid;

            /// <summary>
            /// The class type of the AddressableAssetGroupSchema
            /// </summary>
            public string Type;

            /// <summary>
            /// These key-value-pairs include data about the AddressableAssetGroupSchema, such as PackingMode and Compression.
            /// </summary>
            public List<Tuple<string, string>> KvpDetails = new List<Tuple<string, string>>();

            [SerializeField]
            private StringPair[] SchemaDataPairs;

            /// <summary>
            /// Converts the unserializable KvpDetails to a serializable type for writing
            /// </summary>
            public void OnBeforeSerialize()
            {
                SchemaDataPairs = new StringPair[KvpDetails.Count];
                for (int i = 0; i < SchemaDataPairs.Length; ++i)
                    SchemaDataPairs[i] = new StringPair() {Key = KvpDetails[i].Item1, Value = KvpDetails[i].Item2};
            }

            /// <summary>
            /// Writes data to KvpDetails after Deserializing to temporary data fields
            /// </summary>
            public void OnAfterDeserialize()
            {
                for (int i = 0; i < SchemaDataPairs.Length; ++i)
                    KvpDetails.Add(new Tuple<string, string>(SchemaDataPairs[i].Key, SchemaDataPairs[i].Value));
                SchemaDataPairs = null;
            }
        }

        /// <summary>
        /// Data store for AssetBundle information.
        /// </summary>
        [Serializable]
        public class Bundle
        {
            /// <summary>
            /// The name of the AssetBundle
            /// </summary>
            public string Name;

            /// <summary>
            /// Name used to identify the asset bundle
            /// </summary>
            public string InternalName;

            /// <summary>
            /// The file size of the AssetBundle on disk, in bytes
            /// </summary>
            public ulong FileSize;

            /// <summary>
            /// Status of the bundle after an update build
            /// </summary>
            public BundleBuildStatus BuildStatus;

            /// <summary>
            /// The file size of all of the Expanded Dependencies of this AssetBundle, in bytes
            /// Expanded dependencies are the dependencies of this AssetBundle's dependencies
            /// </summary>
            public ulong ExpandedDependencyFileSize;

            /// <summary>
            /// The file size
            /// </summary>
            public ulong DependencyFileSize;

            /// <summary>
            /// The file size of the AssetBundle on disk when uncompressed, in bytes
            /// </summary>
            public ulong UncompressedFileSize
            {
                get
                {
                    ulong total = 0;
                    foreach (File file in Files)
                        total += file.UncompressedSize;
                    return total;
                }
            }

            /// <summary>
            /// The number of Assets contained within the bundle
            /// </summary>
            public int AssetCount = 0;

            /// <summary>
            /// Represents a dependency from the containing Bundle to dependentBundle, with AssetDependencies representing each of the assets in parentBundle that create the link to dependentBundle
            /// </summary>
            [Serializable]
            public class BundleDependency
            {
                /// <summary>
                /// The bundle that the parent bundle depends on
                /// </summary>
                [SerializeReference]
                public Bundle DependencyBundle;

                /// <summary>
                /// The list of assets that link the parent bundle to the DependencyBundle
                /// </summary>
                public List<AssetDependency> AssetDependencies;

                /// <summary>
                /// Percentage of Efficiency asset usage that uses the entire dependency tree of this bundle dependency.
                /// This includes DependencyBundle and all bundles beneath it.
                /// Value is equal to [Total Filesize of Dependency Assets] / [Total size of all dependency bundles on disk]
                /// Example: There are 3 bundles A, B, and C, that are each 10 MB on disk. A depends on 2 MB worth of assets in B, and B depends on 4 MB worth of assets in C.
                /// The Efficiency of the dependencyLink from A->B would be 2/10 -> 20% and the ExpandedEfficiency of A->B would be (2 + 4)/(10 + 10) -> 6/20 -> 30%
                ///  </summary>
                public float ExpandedEfficiency;

                /// <summary>
                /// The Efficiency of the connection between the parent bundle and DependencyBundle irrespective of the full dependency tree below DependencyBundle.
                /// Value is equal to [Serialized Filesize of assets In Dependency Bundle Referenced By Parent]/[Total size of Dependency Bundle on disk]
                /// Example: Given two Bundles A and B that are each 10 MB on disk, and A depends on 5 MB worth of assets in B, then the Efficiency of DependencyLink A->B is 5/10 = .5
                /// </summary>
                public float Efficiency;

                private HashSet<ExplicitAsset> referencedAssets = new HashSet<ExplicitAsset>();

                /// <summary>
                /// The number of uniquely assets that the parent bundle uniquely references in dependency bundle. This is used to calculate Efficiency without double counting.
                /// </summary>
                internal ulong referencedAssetsFileSize = 0;

                internal BundleDependency(Bundle b)
                {
                    DependencyBundle = b;
                    AssetDependencies = new List<AssetDependency>();
                }

                internal void CreateAssetDependency(ExplicitAsset root, ExplicitAsset dependencyAsset)
                {
                    if (referencedAssets.Contains(dependencyAsset))
                        return;
                    referencedAssets.Add(dependencyAsset);
                    AssetDependencies.Add(new AssetDependency(root, dependencyAsset));
                    referencedAssetsFileSize += dependencyAsset.SerializedSize;
                }


                /// <summary>
                /// Represents a dependency from a root Asset to a dependent Asset.
                /// </summary>
                [Serializable]
                public struct AssetDependency
                {
                    [SerializeReference]
                    internal ExplicitAsset rootAsset;

                    [SerializeReference]
                    internal ExplicitAsset dependencyAsset;

                    internal AssetDependency(ExplicitAsset root, ExplicitAsset depAsset)
                    {
                        rootAsset = root;
                        dependencyAsset = depAsset;
                    }
                }
            }

            internal Dictionary<Bundle, BundleDependency> BundleDependencyMap = new Dictionary<Bundle, BundleDependency>();

            /// <summary>
            /// A list of bundles that this bundle depends upon.
            /// </summary>
            [SerializeField]
            public BundleDependency[] BundleDependencies = Array.Empty<BundleDependency>();


            /// <summary>
            /// Convert BundleDependencyMap to a format that is able to be serialized and plays nicer with
            /// CalculateEfficiency - this must be called on a bundle before CalculateEfficiency can be called.
            /// </summary>
            internal void SerializeBundleToBundleDependency()
            {
                BundleDependencies = new BundleDependency[BundleDependencyMap.Values.Count];
                BundleDependencyMap.Values.CopyTo(BundleDependencies, 0);
            }

            /// <summary>
            /// Updates the BundleDependency from the current bundle to the bundle that contains referencedAsset. If no such BundleDependency exists,
            /// one is created. Does nothing if rootAsset's bundle is not the current bundle or
            /// if the two assets are in the same bundle.
            /// </summary>
            /// <param name="rootAsset"></param>
            /// <param name="referencedAsset"></param>
            internal void UpdateBundleDependency(ExplicitAsset rootAsset, ExplicitAsset referencedAsset)
            {
                if (rootAsset.Bundle != this || referencedAsset.Bundle == rootAsset.Bundle)
                    return;

                if (!BundleDependencyMap.ContainsKey(referencedAsset.Bundle))
                    BundleDependencyMap.Add(referencedAsset.Bundle, new BundleDependency(referencedAsset.Bundle));
                BundleDependencyMap[referencedAsset.Bundle].CreateAssetDependency(rootAsset, referencedAsset);
            }

            // Helper struct for calculating Efficiency
            internal struct EfficiencyInfo
            {
                internal ulong totalAssetFileSize;
                internal ulong referencedAssetFileSize;
            }


            /// <summary>
            /// The Compression method used for the AssetBundle.
            /// </summary>
            public string Compression;

            /// <summary>
            /// Cyclic redundancy check of the content contained inside of the asset bundle.
            /// This value will not change between identical asset bundles with different compression options.
            /// </summary>
            public uint CRC;

            /// <summary>
            /// The hash version of the contents contained inside of the asset bundle.
            /// This value will not change between identical asset bundles with different compression options.
            /// </summary>
            public Hash128 Hash;

            /// <summary>
            /// A reference to the Group data that this AssetBundle was generated from
            /// </summary>
            [SerializeReference]
            public Group Group;

            /// <summary>
            /// Path Provider uses to load the Asset Bundle
            /// </summary>
            public string LoadPath;

            /// <summary>
            /// Provider used to load the Asset Bundle
            /// </summary>
            public string Provider;

            /// <summary>
            /// Result provided by the Provider loading the Asset Bundle
            /// </summary>
            public string ResultType;

            /// <summary>
            /// List of the Files referenced by the AssetBundle
            /// </summary>
            [SerializeReference]
            public List<File> Files = new List<File>();

            /// <summary>
            /// A list of the bundles that directly depend on this AssetBundle
            /// </summary>
            [SerializeReference]
            public List<Bundle> DependentBundles = new List<Bundle>();

            /// <summary>
            /// A list of the direct dependencies of the AssetBundle
            /// </summary>
            [SerializeReference]
            public List<Bundle> Dependencies;

            /// <summary>
            /// The second order dependencies and greater of a bundle
            /// </summary>
            [SerializeReference]
            public List<Bundle> ExpandedDependencies;
        }

        /// <summary>
        /// Data store for resource files generated by the build pipeline and referenced by a main File
        /// </summary>
        [Serializable]
        public class SubFile
        {
            /// <summary>
            /// The name of the sub-file
            /// </summary>
            public string Name;

            /// <summary>
            /// If the main File is a serialized file, this will be true.
            /// </summary>
            public bool IsSerializedFile;

            /// <summary>
            /// The size of the sub-file, in bytes
            /// </summary>
            public ulong Size;
        }

        /// <summary>
        /// Data store for the main File created for the AssetBundle
        /// </summary>
        [Serializable]
        public class File
        {
            /// <summary>
            /// The name of the File.
            /// </summary>
            public string Name;

            /// <summary>
            /// The AssetBundle data that relates to a built file.
            /// </summary>
            [SerializeReference]
            public Bundle Bundle;

            /// <summary>
            /// The file size of the AssetBundle on disk when uncompressed, in bytes
            /// </summary>
            public ulong UncompressedSize
            {
                get
                {
                    ulong total = 0;
                    foreach (SubFile subFile in SubFiles)
                        total += subFile.Size;
                    return total;
                }
            }

            /// <summary>
            /// List of the resource files created by the build pipeline that a File references
            /// </summary>
            [SerializeReference]
            public List<SubFile> SubFiles = new List<SubFile>();

            /// <summary>
            /// A list of the explicit asset defined in the AssetBundle
            /// </summary>
            [SerializeReference]
            public List<ExplicitAsset> Assets = new List<ExplicitAsset>();

            /// <summary>
            /// A list of implicit assets built into the AssetBundle, typically through references by Assets that are explicitly defined.
            /// </summary>
            [SerializeReference]
            public List<DataFromOtherAsset> OtherAssets = new List<DataFromOtherAsset>();

            [SerializeReference]
            internal List<ExplicitAsset> ExternalReferences = new List<ExplicitAsset>();

            /// <summary>
            /// The final filename of the AssetBundle file
            /// </summary>
            public string WriteResultFilename;

            /// <summary>
            /// Data about the AssetBundleObject
            /// </summary>
            public AssetBundleObjectInfo BundleObjectInfo;

            /// <summary>
            /// The size of the data that needs to be preloaded for this File.
            /// </summary>
            public int PreloadInfoSize;

            /// <summary>
            /// The number of Mono scripts referenced by the File
            /// </summary>
            public int MonoScriptCount;

            /// <summary>
            /// The size of the Mono scripts referenced by the File
            /// </summary>
            public ulong MonoScriptSize;
        }

        /// <summary>
        /// A representation of an object in an asset file.
        /// </summary>
        [Serializable]
        public class ObjectData
        {
            /// <summary>
            /// FileId of Object in Asset File
            /// </summary>
            public long LocalIdentifierInFile;

            /// <summary>
            /// Object name within the Asset
            /// </summary>
            [SerializeField] internal string ObjectName;

            /// <summary>
            /// Component name if AssetType is a MonoBehaviour or Component
            /// </summary>
            [SerializeField] internal string ComponentName;

            /// <summary>
            /// Type of Object
            /// </summary>
            public AssetType AssetType;

            /// <summary>
            /// The size of the file on disk.
            /// </summary>
            public ulong SerializedSize;

            /// <summary>
            /// The size of the streamed Asset.
            /// </summary>
            public ulong StreamedSize;

            /// <summary>
            /// References to other Objects
            /// </summary>
            [SerializeField] internal List<ObjectReference> References = new List<ObjectReference>();
        }

        /// <summary>
        /// Identification of an Object within the same file
        /// </summary>
        [Serializable]
        internal class ObjectReference
        {
            public int AssetId;
            public List<int> ObjectIds;
        }

        /// <summary>
        /// Data store for Assets explicitly defined in an AssetBundle
        /// </summary>
        [Serializable]
        public class ExplicitAsset
        {
            /// <summary>
            /// The Asset Guid.
            /// </summary>
            public string Guid;

            /// <summary>
            /// The Asset path on disk
            /// </summary>
            public string AssetPath;

            /// <summary>
            /// Name used to identify the asset within the asset bundle containing it
            /// </summary>
            public string InternalId;

            /// <summary>
            /// Hash of the asset content
            /// </summary>
            public Hash128 AssetHash;

            /// <summary>
            /// Objects that consist of the overall asset
            /// </summary>
            public List<ObjectData> Objects = new List<ObjectData>();

            /// <summary>
            /// AssetType of the main Object for the Asset
            /// </summary>
            public AssetType MainAssetType;

            /// <summary>
            /// True if is a scene asset, else false
            /// </summary>
            public bool IsScene => AssetPath.EndsWith(".unity", StringComparison.Ordinal);

            /// <summary>
            /// Guid of the Addressable group this Asset entry was built using.
            /// </summary>
            public string GroupGuid;

            /// <summary>
            /// The Addressable address defined in the Addressable Group window for an Asset.
            /// </summary>
            public string AddressableName;

            /// <summary>
            /// Addressable labels for this asset entry.
            /// </summary>
            [SerializeField]
            public string[] Labels = Array.Empty<string>();

            /// <summary>
            /// The size of the file on disk.
            /// </summary>
            public ulong SerializedSize;

            /// <summary>
            /// The size of the streamed Asset.
            /// </summary>
            public ulong StreamedSize;

            /// <summary>
            /// The file that the Asset was added to
            /// </summary>
            [SerializeReference]
            public File File;

            /// <summary>
            /// The AssetBundle that contains the asset
            /// </summary>
            [SerializeReference]
            public Bundle Bundle;

            /// <summary>
            /// List of data from other Assets referenced by an Asset in the File
            /// </summary>
            [SerializeReference]
            public List<DataFromOtherAsset> InternalReferencedOtherAssets = new List<DataFromOtherAsset>();

            /// <summary>
            /// List of explicit Assets referenced by this asset that are in the same AssetBundle
            /// </summary>
            [SerializeReference]
            public List<ExplicitAsset> InternalReferencedExplicitAssets = new List<ExplicitAsset>();

            /// <summary>
            /// List of explicit Assets referenced by this asset that are in a different AssetBundle
            /// </summary>
            [SerializeReference]
            public List<ExplicitAsset> ExternallyReferencedAssets = new List<ExplicitAsset>();

            /// <summary>
            /// List of Assets that reference this Asset
            /// </summary>
            [SerializeReference]
            internal List<ExplicitAsset> ReferencingAssets = new List<ExplicitAsset>();
        }

        /// <summary>
        /// Data store for implicit Asset references
        /// </summary>
        [Serializable]
        public class DataFromOtherAsset
        {
            /// <summary>
            /// The Guid of the Asset
            /// </summary>
            public string AssetGuid;

            /// <summary>
            /// The Asset path on disk
            /// </summary>
            public string AssetPath;

            /// <summary>
            /// The file that the Asset was added to
            /// </summary>
            [SerializeReference]
            public File File;

            /// <summary>
            /// Objects that consist of the overall asset
            /// </summary>
            public List<ObjectData> Objects = new List<ObjectData>();

            /// <summary>
            /// AssetType of the main Object for the Asset
            /// </summary>
            public AssetType MainAssetType;

            /// <summary>
            /// True if is a scene asset, else false
            /// </summary>
            public bool IsScene => AssetPath.EndsWith(".unity", StringComparison.Ordinal);

            /// <summary>
            /// A list of Assets that reference this data
            /// </summary>
            [SerializeReference]
            public List<ExplicitAsset> ReferencingAssets = new List<ExplicitAsset>();

            /// <summary>
            /// The number of Objects in the data
            /// </summary>
            public int ObjectCount;

            /// <summary>
            /// The size of the data on disk
            /// </summary>
            public ulong SerializedSize;

            /// <summary>
            /// The size of the streamed data
            /// </summary>
            public ulong StreamedSize;
        }

        /// <summary>
        /// Data store for duplicated Implicit Asset information
        /// </summary>
        [Serializable]
        public class AssetDuplicationData
        {
            /// <summary>
            /// The Guid of the Asset with duplicates
            /// </summary>
            public string AssetGuid;
            /// <summary>
            /// A list of duplicated objects and the bundles that contain them.
            /// </summary>
            public List<ObjectDuplicationData> DuplicatedObjects = new List<ObjectDuplicationData>();
        }

        /// <summary>
        /// Data store for duplicated Object information
        /// </summary>
        [Serializable]
        public class ObjectDuplicationData
        {
            /// <summary>
            /// The local identifier for an object.
            /// </summary>
            public long LocalIdentifierInFile;
            /// <summary>
            /// A list of bundles that include the referenced file.
            /// </summary>
            [SerializeReference] public List<File> IncludedInBundleFiles = new List<File>();
        }
    }

    /// <summary>
    /// Utility used to quickly reference data built with the build pipeline
    /// </summary>
    public class LayoutLookupTables
    {
        /// <summary>
        /// The default AssetBundle name to the Bundle data map.
        /// </summary>
        public Dictionary<string, BuildLayout.Bundle> Bundles = new Dictionary<string, BuildLayout.Bundle>();

        /// <summary>
        /// File name to File data map.
        /// </summary>
        public Dictionary<string, BuildLayout.File> Files = new Dictionary<string, BuildLayout.File>();

        internal Dictionary<BuildLayout.File, FileObjectData> FileToFileObjectData = new Dictionary<BuildLayout.File, FileObjectData>();

        /// <summary>
        /// Guid to ExplicitAsset data map.
        /// </summary>
        public Dictionary<string, BuildLayout.ExplicitAsset> GuidToExplicitAsset = new Dictionary<string, BuildLayout.ExplicitAsset>();

        /// <summary>
        /// Group name to Group data map.
        /// </summary>
        public Dictionary<string, BuildLayout.Group> GroupLookup = new Dictionary<string, BuildLayout.Group>();

        /// <summary>
        /// The remapped AssetBundle name to the Bundle data map
        /// </summary>
        internal Dictionary<string, BuildLayout.Bundle> FilenameToBundle = new Dictionary<string, BuildLayout.Bundle>();


        /// Maps used for lookups while building the BuildLayout
        internal Dictionary<string, List<BuildLayout.DataFromOtherAsset>> UsedImplicits = new Dictionary<string, List<BuildLayout.DataFromOtherAsset>>();

        internal Dictionary<string, AssetBundleRequestOptions> BundleNameToRequestOptions = new Dictionary<string, AssetBundleRequestOptions>();

        internal Dictionary<string, AssetBundleRequestOptions> BundleNameToPreviousRequestOptions = new Dictionary<string, AssetBundleRequestOptions>();

        internal Dictionary<string, ContentCatalogDataEntry> BundleNameToCatalogEntry = new Dictionary<string, ContentCatalogDataEntry>();

        internal Dictionary<string, string> GroupNameToBuildPath = new Dictionary<string, string>();

        internal Dictionary<string, AddressableAssetEntry> GuidToEntry = new Dictionary<string, AddressableAssetEntry>();
        internal Dictionary<string, AssetType> AssetPathToTypeMap = new Dictionary<string, AssetType>();
    }

    internal class FileObjectData
    {
        // id's for internal explicit asset and implicit asset
        public Dictionary<ObjectIdentifier, (int, int)> InternalObjectIds = new Dictionary<ObjectIdentifier, (int, int)>();

        public Dictionary<BuildLayout.ObjectData, ObjectIdentifier> Objects = new Dictionary<BuildLayout.ObjectData, ObjectIdentifier>();

        public void Add(ObjectIdentifier buildObjectIdentifier, BuildLayout.ObjectData layoutObject, int assetId, int objectIndex)
        {
            InternalObjectIds[buildObjectIdentifier] = (assetId, objectIndex);
            Objects[layoutObject] = buildObjectIdentifier;
        }

        public bool TryGetObjectReferenceData(ObjectIdentifier obj, out (int, int) value)
        {
            if (!InternalObjectIds.TryGetValue(obj, out (int, int) data))
            {
                value = default;
                return false;
            }

            value = data;
            return true;
        }

        public bool TryGetObjectIdentifier(BuildLayout.ObjectData obj, out ObjectIdentifier objectIdOut)
        {
            if (!Objects.TryGetValue(obj, out objectIdOut))
            {
                objectIdOut = default;
                return false;
            }

            return true;
        }
    }
}