using System;
using System.Collections.Generic;
using System.IO;
using AddressableAssetsIntegrationTests;
using NUnit.Framework;
using UnityEditor.AddressableAssets.Build.BuildPipelineTasks;
using UnityEditor.AddressableAssets.Build.Layout;
using UnityEditor.Animations;
using UnityEngine;

namespace UnityEditor.AddressableAssets.Tests.Diagnostics
{
    public class BuildLayoutTests
    {
        private BuildLayout.ExplicitAsset CreateExplicitAssetWithParentBundle(BuildLayout.Bundle parent, ulong filesize = 1)
        {
            BuildLayout.ExplicitAsset asset = new BuildLayout.ExplicitAsset();
            asset.Bundle = parent;
            asset.SerializedSize = filesize;
            return asset;
        }

        private BuildLayout.Bundle CreateBundleWithFileSize(ulong filesize, string name = "default")
        {
            var bundle = new BuildLayout.Bundle();
            bundle.Name = name;
            bundle.FileSize = (ulong) filesize;
            return bundle;
        }

        private BuildLayout.Bundle CreateBundleWithFileSize(int assetCount, string name = "default")
        {
            var bundle = new BuildLayout.Bundle();
            bundle.AssetCount = assetCount;
            bundle.Name = name;
            bundle.FileSize = (ulong) assetCount;
            return bundle;
        }


        [Test]
        public void BuildLayoutBundle_CalculateEfficiency_CorrectlyWorksInSimpleCase()
        {
            BuildLayout.Bundle parent = CreateBundleWithFileSize(5);
            BuildLayout.Bundle child = CreateBundleWithFileSize(10);

            BuildLayout.ExplicitAsset parentAsset = CreateExplicitAssetWithParentBundle(parent);
            BuildLayout.ExplicitAsset childAsset = CreateExplicitAssetWithParentBundle(child);

            child.DependentBundles.Add(parent);

            parent.UpdateBundleDependency(parentAsset, childAsset);
            parent.SerializeBundleToBundleDependency();
            BuildLayoutGenerationTask.CalculateEfficiency(parent);

            float expectedEfficiency = .10f;

            Assert.AreEqual(expectedEfficiency, parent.BundleDependencies[0].ExpandedEfficiency);
        }

        [Test]
        public void BuildLayoutBundle_CalculateEfficiency_MultipleAssetDependencyCaseStillCreatesCorrectEfficiency()
        {
            BuildLayout.Bundle parent = CreateBundleWithFileSize(5);
            BuildLayout.Bundle child = CreateBundleWithFileSize(10);

            BuildLayout.ExplicitAsset parentAsset = CreateExplicitAssetWithParentBundle(parent);
            for (int i = 0; i < 10; i++)
            {
                var asset = CreateExplicitAssetWithParentBundle(child);
                parent.UpdateBundleDependency(parentAsset, asset);
            }

            parent.SerializeBundleToBundleDependency();
            BuildLayoutGenerationTask.CalculateEfficiency(parent);

            float expectedEfficiency = 1f;
            Assert.AreEqual(expectedEfficiency, parent.BundleDependencies[0].ExpandedEfficiency);
        }

        [Test]
        [TestCase(5, 10, 1, 5)]
        [TestCase(6, 12, 2, 7)]
        [TestCase(20, 43, 7, 15)]
        [TestCase(27, 68, 25, 60)]
        public void BuildLayoutBundle_CalculateEfficiency_ThreeLayeredTreeGeneratesCorrectEfficiencyAtAllLevels(
            int parentAssetCount,
            int childAssetCount,
            int parentDependencyCount,
            int childDependencyCount)
        {
            BuildLayout.Bundle grandparent = CreateBundleWithFileSize(1);
            BuildLayout.Bundle parent = CreateBundleWithFileSize(parentAssetCount);
            BuildLayout.Bundle child = CreateBundleWithFileSize(childAssetCount);

            BuildLayout.ExplicitAsset grandparentAsset = CreateExplicitAssetWithParentBundle(grandparent);
            List<BuildLayout.ExplicitAsset> parentAssets = new List<BuildLayout.ExplicitAsset>();
            for (int i = 0; i < parentDependencyCount; i++)
            {
                var parentAsset = CreateExplicitAssetWithParentBundle(parent);
                grandparent.UpdateBundleDependency(grandparentAsset, parentAsset);
                parentAssets.Add(parentAsset);
            }
            for (int i = 0; i < childDependencyCount; i++)
            {
                var childAsset = CreateExplicitAssetWithParentBundle(child);
                parent.UpdateBundleDependency(parentAssets[0], childAsset);
            }

            grandparent.SerializeBundleToBundleDependency();
            parent.SerializeBundleToBundleDependency();

            BuildLayoutGenerationTask.CalculateEfficiency(grandparent);

            float expectedGrandparentEfficiency = (float) parentDependencyCount / parentAssetCount;
            float expectedGrandparentExpandedEfficiency = (float) (childDependencyCount + parentDependencyCount) / (childAssetCount + parentAssetCount);
            float expectedParentEfficiency = (float) childDependencyCount / childAssetCount;
            Assert.AreEqual(expectedGrandparentEfficiency, grandparent.BundleDependencies[0].Efficiency, "Grandparent Efficiency not calculated correctly - this should not be affected by the children of parent.");
            Assert.AreEqual(expectedGrandparentExpandedEfficiency, grandparent.BundleDependencies[0].ExpandedEfficiency, "Grandparent ExpandedEfficiency not calculated correctly - this should take into account the total Efficiency of both the direct children Dependency Links as well as all BundleDependencies below the child.");
            Assert.AreEqual(expectedParentEfficiency, parent.BundleDependencies[0].ExpandedEfficiency, "Parent ExpandedEfficiency not calculated correctly - this should take into account the Efficiency of the direct child only.");
        }

        [Test]
        public void BuildLayoutBundle_CalculateEfficiency_AssetDependenciesAreNotDoubleCounted()
        {
            var parentBundle = CreateBundleWithFileSize(1);
            var childBundle = CreateBundleWithFileSize(1);

            var parentAsset = CreateExplicitAssetWithParentBundle(parentBundle);
            var childAsset = CreateExplicitAssetWithParentBundle(childBundle);

            parentBundle.UpdateBundleDependency(parentAsset, childAsset);
            parentBundle.SerializeBundleToBundleDependency();
            BuildLayoutGenerationTask.CalculateEfficiency(parentBundle);

            Assert.AreEqual(1f, parentBundle.BundleDependencies[0].ExpandedEfficiency, "ExpandedEfficiency not correctly calculated");
            Assert.AreEqual(1f, parentBundle.BundleDependencies[0].Efficiency, "Efficiency not correctly calculated");
        }

        [Test]
        [TestCase(3, 3, 1, 1 )]
        [TestCase(50, 5, 5, 2)]
        [TestCase(100, 100, 1, 50)]
        public void BuildLayoutBundle_CalculateEfficiency_MultiProngedShortTreeGeneratesCorrectEfficiency(
            int child1AssetCount,
            int child2AssetCount,
            int child1DependencyCount,
            int child2DependencyCount)
        {
            BuildLayout.Bundle parentBundle = CreateBundleWithFileSize(2);
            BuildLayout.Bundle childBundle1 = CreateBundleWithFileSize(child1AssetCount);
            BuildLayout.Bundle childBundle2 = CreateBundleWithFileSize(child2AssetCount);

            var parentAsset1 = CreateExplicitAssetWithParentBundle(parentBundle);
            var parentAsset2 = CreateExplicitAssetWithParentBundle(parentBundle);

            for (int i = 0; i < child1DependencyCount; i++)
            {
                var childAsset = CreateExplicitAssetWithParentBundle(childBundle1);
                parentBundle.UpdateBundleDependency(parentAsset1, childAsset);
            }

            for (int i = 0; i < child2DependencyCount; i++)
            {
                var childAsset = CreateExplicitAssetWithParentBundle(childBundle2);
                parentBundle.UpdateBundleDependency(parentAsset2, childAsset);
            }

            parentBundle.SerializeBundleToBundleDependency();
            BuildLayoutGenerationTask.CalculateEfficiency(parentBundle);

            float expectedChild1Efficiency = (float) child1DependencyCount / child1AssetCount;
            float expectedChild2Efficiency = (float) child2DependencyCount / child2AssetCount;
            Assert.AreEqual(expectedChild1Efficiency, parentBundle.BundleDependencies[0].ExpandedEfficiency);
            Assert.AreEqual(expectedChild2Efficiency, parentBundle.BundleDependencies[1].ExpandedEfficiency);
        }

        [Test]
        public void BuildLayoutBundle_CalculateEfficiency_DoesNotDoubleCountDependencies()
        {
            BuildLayout.Bundle parent = CreateBundleWithFileSize(2);
            BuildLayout.Bundle child = CreateBundleWithFileSize(2);

            BuildLayout.ExplicitAsset parentAsset1 = CreateExplicitAssetWithParentBundle(parent);
            BuildLayout.ExplicitAsset parentAsset2 = CreateExplicitAssetWithParentBundle(parent);
            BuildLayout.ExplicitAsset childAsset1 = CreateExplicitAssetWithParentBundle(child);

            parent.UpdateBundleDependency(parentAsset1, childAsset1);
            parent.UpdateBundleDependency(parentAsset2, childAsset1);

            parent.SerializeBundleToBundleDependency();
            BuildLayoutGenerationTask.CalculateEfficiency(parent);

            float expectedEfficiency = .5f;
            Assert.AreEqual(expectedEfficiency, parent.BundleDependencies[0].ExpandedEfficiency, "If multiple assets in a parent bundle depend on a single asset in the child, this reliance should not be double counted.");
            Assert.AreEqual(expectedEfficiency, parent.BundleDependencies[0].Efficiency, "If multiple assets in a parent bundle depend on a single asset in the child, this reliance should not be double counted.");
        }

        [Test]
        [TestCase(2, 1, 1, 1, 1)]
        [TestCase(2, 5, 5, 2, 3)]
        [TestCase(10, 3,3, 3,3)]
        [TestCase(30, 30, 30, 20, 20)]
        [TestCase(50, 50, 50, 50, 50)]
        public void BuildLayoutBundle_CalculateEfficiency_HigherLevelTreesCorrectlyPromulgateLowerLevelEfficiency(
            int parentAssetCount,
            int child1AssetCount,
            int child2AssetCount,
            int child1DependencyCount,
            int child2DependencyCount)
        {
            BuildLayout.Bundle grandparent = CreateBundleWithFileSize(1);
            BuildLayout.Bundle parent = CreateBundleWithFileSize(parentAssetCount);
            BuildLayout.Bundle childBundle1 = CreateBundleWithFileSize(child1AssetCount);
            BuildLayout.Bundle childBundle2 = CreateBundleWithFileSize(child2AssetCount);

            BuildLayout.ExplicitAsset grandparentAsset = CreateExplicitAssetWithParentBundle(grandparent);
            BuildLayout.ExplicitAsset parentAsset = CreateExplicitAssetWithParentBundle(parent);

            grandparent.UpdateBundleDependency(grandparentAsset, parentAsset);

            for (int i = 0; i < child1DependencyCount; i++)
            {
                var childAsset = CreateExplicitAssetWithParentBundle(childBundle1);
                parent.UpdateBundleDependency(parentAsset, childAsset);
            }

            for (int i = 0; i < child2DependencyCount; i++)
            {
                var childAsset = CreateExplicitAssetWithParentBundle(childBundle2);
                parent.UpdateBundleDependency(parentAsset, childAsset);
            }

            grandparent.SerializeBundleToBundleDependency();
            parent.SerializeBundleToBundleDependency();

            BuildLayoutGenerationTask.CalculateEfficiency(grandparent);


            float expectedGrandparentToParentEfficiency = (float) 1 / parentAssetCount;
            float expectedGrandparentToParentExpandedEfficiency = (float) (1 + child1DependencyCount + child2DependencyCount) / (parentAssetCount + child1AssetCount + child2AssetCount);
            float expectedParentToChild1Efficiency = (float) child1DependencyCount / child1AssetCount;
            float expectedParentToChild2Efficiency = (float) child2DependencyCount / child2AssetCount;

            Assert.AreEqual(expectedGrandparentToParentEfficiency, grandparent.BundleDependencies[0].Efficiency);
            Assert.AreEqual(expectedGrandparentToParentExpandedEfficiency, grandparent.BundleDependencies[0].ExpandedEfficiency);
            Assert.AreEqual(expectedParentToChild1Efficiency, parent.BundleDependencies[0].Efficiency);
            Assert.AreEqual(expectedParentToChild2Efficiency, parent.BundleDependencies[1].Efficiency);

        }

        [Test]
        [TestCase(5, 10, 10, 3, 5)]
        [TestCase(7, 20, 15, 5, 8)]
        [TestCase(11, 35, 40, 15, 20)]
        public void BuildLayoutBundle_CalculateEfficiency_CacheCorrectlyAvoidsDoubleComputation(
            int parentAssetCount,
            int child1AssetCount,
            int child2AssetCount,
            int child1DependencyCount,
            int child2DependencyCount)
        {
            BuildLayout.Bundle grandparent1 = CreateBundleWithFileSize(1);
            BuildLayout.Bundle grandparent2 = CreateBundleWithFileSize(1);
            BuildLayout.Bundle parent = CreateBundleWithFileSize(parentAssetCount);
            BuildLayout.Bundle child1 = CreateBundleWithFileSize(child1AssetCount);
            BuildLayout.Bundle child2 = CreateBundleWithFileSize(child2AssetCount);

            BuildLayout.ExplicitAsset grandparent1Asset = CreateExplicitAssetWithParentBundle(grandparent1);
            BuildLayout.ExplicitAsset grandparent2Asset = CreateExplicitAssetWithParentBundle(grandparent2);
            BuildLayout.ExplicitAsset parentAsset = CreateExplicitAssetWithParentBundle(parent);

            grandparent1.UpdateBundleDependency(grandparent1Asset, parentAsset);
            grandparent2.UpdateBundleDependency(grandparent2Asset, parentAsset);

            for (int i = 0; i < child1DependencyCount; i++)
            {
                var childAsset = CreateExplicitAssetWithParentBundle(child1);
                parent.UpdateBundleDependency(parentAsset, childAsset);
            }

            for (int i = 0; i < child2DependencyCount; i++)
            {
                var childAsset = CreateExplicitAssetWithParentBundle(child2);
                parent.UpdateBundleDependency(parentAsset, childAsset);
            }

            Dictionary<BuildLayout.Bundle.BundleDependency, BuildLayout.Bundle.EfficiencyInfo> bdCache = new Dictionary<BuildLayout.Bundle.BundleDependency, BuildLayout.Bundle.EfficiencyInfo>();

            grandparent1.SerializeBundleToBundleDependency();
            grandparent2.SerializeBundleToBundleDependency();
            parent.SerializeBundleToBundleDependency();

            BuildLayoutGenerationTask.CalculateEfficiency(grandparent1, bdCache);

            var ef1 = new BuildLayout.Bundle.EfficiencyInfo
            {
                totalAssetFileSize = (ulong) child1AssetCount,
                referencedAssetFileSize = (ulong) child1DependencyCount
            };

            var ef2 = new BuildLayout.Bundle.EfficiencyInfo
            {
                totalAssetFileSize = (ulong) child2AssetCount,
                referencedAssetFileSize = (ulong) child2DependencyCount
            };


            float bd1Efficiency = (float) child1DependencyCount / child1AssetCount;
            float bd2Efficiency = (float) child2DependencyCount / child2AssetCount;

            float gp1Efficiency = (float) (1 + child1DependencyCount + child2DependencyCount) / (parentAssetCount + child1AssetCount + child2AssetCount);

            Assert.IsTrue(bdCache.ContainsKey(parent.BundleDependencies[0]), "BundleDependencyCache not properly populated");
            Assert.AreEqual(bdCache[parent.BundleDependencies[0]], ef1);
            Assert.AreEqual(parent.BundleDependencies[0].ExpandedEfficiency, bd1Efficiency);

            Assert.IsTrue(bdCache.ContainsKey(parent.BundleDependencies[1]), "BundleDependencyCache not properly populated");
            Assert.AreEqual(bdCache[parent.BundleDependencies[1]], ef2);
            Assert.AreEqual(parent.BundleDependencies[1].ExpandedEfficiency, bd2Efficiency);

            Assert.AreEqual(grandparent1.BundleDependencies[0].ExpandedEfficiency, gp1Efficiency);

            // Create a new asset that is not cached - we do this to ensure that cached values are used and that we're not recalculating Efficiency
            // This will never happen in normal execution - this is just a workaround to make sure the cache is working properly.
            var uncachedAsset = CreateExplicitAssetWithParentBundle(child1);
            parent.UpdateBundleDependency(parentAsset, uncachedAsset);

            BuildLayoutGenerationTask.CalculateEfficiency(grandparent2, bdCache);

            Assert.IsTrue(bdCache.ContainsKey(parent.BundleDependencies[0]), "Key should not be removed");
            Assert.AreEqual(bdCache[parent.BundleDependencies[0]], ef1, "Efficiency should not change since cached value should be used");
            Assert.AreEqual(parent.BundleDependencies[0].ExpandedEfficiency, bd1Efficiency, "Efficiency should not change since cached value should be used.");
            Assert.AreEqual(grandparent2.BundleDependencies[0].ExpandedEfficiency, gp1Efficiency, "Efficiency of grandparent2 should be equal to grandparent1, since cache was used.");

            //Calculation without cache should be incorrect
            BuildLayoutGenerationTask.CalculateEfficiency(grandparent2);
            Assert.AreNotEqual(parent.BundleDependencies[0].ExpandedEfficiency, bd1Efficiency, "Efficiency should change since cached value is not used");
            Assert.AreNotEqual(grandparent2.BundleDependencies[0].ExpandedEfficiency, gp1Efficiency, "Efficiency of grandparent2 should not be equal to grandparent1 since cache isnt used");
        }

        [Test]
        [TestCase(5, 5, 5, 3, 2, 3, 4)]
        [TestCase(50, 2, 10, 10, 1, 2, 4)]
        [TestCase(1, 1, 1, 1, 1, 1, 1)]
        [TestCase(100, 100, 100, 20, 30, 40, 40)]
        public void BuildLayoutBundle_CalculateEfficiency_NonTreeStructureProperlyHandled(
            int bundleBAssetCount,
            int bundleCAssetCount,
            int bundleDAssetCount,
            int bundleBDependencyCount,
            int bundleBToBundleCDependencyCount,
            int bundleCDependencyCount,
            int bundleDDependencyCount)
        {
            BuildLayout.Bundle bundleA = CreateBundleWithFileSize(1);
            BuildLayout.Bundle bundleB = CreateBundleWithFileSize(bundleBAssetCount);
            BuildLayout.Bundle bundleC = CreateBundleWithFileSize(bundleCAssetCount);
            BuildLayout.Bundle bundleD = CreateBundleWithFileSize(bundleDAssetCount);

            var assetA = CreateExplicitAssetWithParentBundle(bundleA);
            var assetB = CreateExplicitAssetWithParentBundle(bundleB);
            var assetC = CreateExplicitAssetWithParentBundle(bundleC);

            bundleA.UpdateBundleDependency(assetA, assetB);

            for (int i = 0; i < bundleBDependencyCount - 1; i++)
            {
                var childAsset = CreateExplicitAssetWithParentBundle(bundleB);
                bundleA.UpdateBundleDependency(assetA, childAsset);
            }

            bundleA.UpdateBundleDependency(assetA, assetC);

            for (int i = 0; (i < bundleCDependencyCount - 1) || (i < bundleBToBundleCDependencyCount); i++)
            {
                var childAsset = CreateExplicitAssetWithParentBundle(bundleC);
                if (i < bundleCDependencyCount - 1)
                    bundleA.UpdateBundleDependency(assetA, childAsset);
                if (i < bundleBToBundleCDependencyCount)
                    bundleB.UpdateBundleDependency(assetB, childAsset);
            }


            for (int i = 0; i < bundleDDependencyCount; i++)
            {
                var childAsset = CreateExplicitAssetWithParentBundle(bundleD);
                bundleC.UpdateBundleDependency(assetC, childAsset);
            }

            bundleA.SerializeBundleToBundleDependency();
            bundleB.SerializeBundleToBundleDependency();
            bundleC.SerializeBundleToBundleDependency();
            bundleD.SerializeBundleToBundleDependency();

            BuildLayoutGenerationTask.CalculateEfficiency(bundleA);

            var cToDExpandedEfficiency = (float)bundleDDependencyCount / bundleDAssetCount;
            var BToCExpandedEfficiency = (float)(bundleDDependencyCount + bundleBToBundleCDependencyCount) / (bundleCAssetCount + bundleDAssetCount);
            var AToBExpandedEfficiency = (float) (bundleBDependencyCount + bundleDDependencyCount + bundleBToBundleCDependencyCount) / (bundleBAssetCount + bundleCAssetCount + bundleDAssetCount);

            Assert.AreEqual(bundleC.BundleDependencies[0].ExpandedEfficiency, cToDExpandedEfficiency);
            Assert.AreEqual(bundleB.BundleDependencies[0].ExpandedEfficiency, BToCExpandedEfficiency);
            Assert.AreEqual(bundleA.BundleDependencies[0].ExpandedEfficiency, AToBExpandedEfficiency);
        }

        [Test]
        [TestCase(20, 20, 20, 5, 5, 5, 5)]
        [TestCase(50, 30, 10, 15, 10, 8, 7)]
        [TestCase(100, 75, 35, 35, 7, 30, 25)]
        public void BuildLayoutBundle_CalculateEfficiency_HandlesCycleWithLeadInByCuttingOffLastNodeConnections(
            int bundleBAssetCount,
            int bundleCAssetCount,
            int bundleDAssetCount,
            int bundleAToBDependencyCount,
            int bundleBToCDependencyCount,
            int bundleCToDDependencyCount,
            int bundleDToBDependencyCount)
        {
            BuildLayout.Bundle bundleA = CreateBundleWithFileSize(1);
            BuildLayout.Bundle bundleB = CreateBundleWithFileSize(bundleBAssetCount, "B");
            BuildLayout.Bundle bundleC = CreateBundleWithFileSize(bundleCAssetCount, "C");
            BuildLayout.Bundle bundleD = CreateBundleWithFileSize(bundleDAssetCount, "D");

            var assetA = CreateExplicitAssetWithParentBundle(bundleA);
            var assetB = CreateExplicitAssetWithParentBundle(bundleB);
            var assetC = CreateExplicitAssetWithParentBundle(bundleC);
            var assetD = CreateExplicitAssetWithParentBundle(bundleD);

            bundleA.UpdateBundleDependency(assetA, assetB);
            bundleB.UpdateBundleDependency(assetB, assetC);
            bundleC.UpdateBundleDependency(assetC, assetD);
            bundleD.UpdateBundleDependency(assetD, assetB);

            for (int i = 0; i < bundleAToBDependencyCount - 1; i++)
            {
                var childAsset = CreateExplicitAssetWithParentBundle(bundleB);
                bundleA.UpdateBundleDependency(assetA, childAsset);
            }

            for (int i = 0; i < bundleBToCDependencyCount - 1; i++)
            {
                var childAsset = CreateExplicitAssetWithParentBundle(bundleC);
                bundleB.UpdateBundleDependency(assetB, childAsset);
            }

            for (int i = 0; i < bundleCToDDependencyCount - 1; i++)
            {
                var childAsset = CreateExplicitAssetWithParentBundle(bundleD);
                bundleC.UpdateBundleDependency(assetC, childAsset);
            }

            for (int i = 0; i < bundleDToBDependencyCount - 1; i++)
            {
                var childAsset = CreateExplicitAssetWithParentBundle(bundleB);
                bundleD.UpdateBundleDependency(assetD, childAsset);
            }

            bundleA.SerializeBundleToBundleDependency();
            bundleB.SerializeBundleToBundleDependency();
            bundleC.SerializeBundleToBundleDependency();
            bundleD.SerializeBundleToBundleDependency();

            BuildLayoutGenerationTask.CalculateEfficiency(bundleA);

            float bundleDToBExpandedEfficiency = (float) bundleDToBDependencyCount / bundleBAssetCount;
            float bundleCToDExpandedEfficiency = (float) (bundleCToDDependencyCount + bundleDToBDependencyCount) / (bundleDAssetCount + bundleBAssetCount);
            float bundleBToCExpandedEfficiency = (float) (bundleBToCDependencyCount + bundleCToDDependencyCount + bundleDToBDependencyCount) / (bundleCAssetCount + bundleDAssetCount + bundleBAssetCount);
            float bundleAToBExpandedEfficiency = (float) (bundleAToBDependencyCount + bundleBToCDependencyCount + bundleCToDDependencyCount + bundleDToBDependencyCount) / (bundleBAssetCount + bundleCAssetCount + bundleDAssetCount + bundleBAssetCount);

            Assert.AreEqual(bundleAToBExpandedEfficiency, bundleA.BundleDependencies[0].ExpandedEfficiency);
            Assert.AreEqual(bundleBToCExpandedEfficiency, bundleB.BundleDependencies[0].ExpandedEfficiency);
            Assert.AreEqual(bundleCToDExpandedEfficiency, bundleC.BundleDependencies[0].ExpandedEfficiency);
            Assert.AreEqual(bundleDToBExpandedEfficiency, bundleD.BundleDependencies[0].ExpandedEfficiency);
        }

        BuildLayout CreateTestLayout(DateTime buildDate, string error = null)
        {
            BuildLayout layout = new BuildLayout();

            layout.BuildTarget = BuildTarget.Android;
            layout.BuildResultHash = "testHash123";
            layout.BuildType = BuildType.UpdateBuild;
            layout.BuildStart = buildDate;
            layout.Duration = 10;
            layout.BuildError = error;

            layout.BuildScript = "DefaultBuildScript";

            BuildLayout.Bundle bundle = CreateBundleWithFileSize(2);
            bundle.Files = new List<BuildLayout.File>();
            BuildLayout.File file = new BuildLayout.File();
            bundle.Files.Add(file);
            file.Assets = new List<BuildLayout.ExplicitAsset>();
            file.Assets.Add(CreateExplicitAssetWithParentBundle(bundle));
            file.Assets.Add(CreateExplicitAssetWithParentBundle(bundle));

            BuildLayout.Group group = new BuildLayout.Group();
            group.Bundles = new List<BuildLayout.Bundle>();
            group.Bundles.Add(bundle);
            layout.Groups = new List<BuildLayout.Group>();
            layout.Groups.Add(group);
            layout.DefaultGroup = group;

            return layout;
        }

        [Test]
        public void BuildLayoutBundle_SaveAndLoad_SavesCorrectlyToDisk()
        {
            DateTime time = DateTime.Now;
            BuildLayout layout = CreateTestLayout(time);
            string filePath = $"{Application.dataPath}/testLayout.json";

            try
            {
                layout.WriteToFile(filePath, false);
                FileAssert.Exists(filePath, $"Failed to save build layout to {filePath}");
            }
            finally
            {
                if (File.Exists(filePath))
                    File.Delete(filePath);
            }
        }

        [Test]
        [TestCase(50UL, 50UL, 20UL, 20UL, 10, 5)]
        [TestCase(30UL, 50UL, 10UL, 5UL, 3, 2)]
        [TestCase(1UL, 1UL, 1UL, 1UL, 1, 1)]
        [TestCase(30UL, 1UL, 30UL, 1UL, 1, 1)]
        [TestCase(5000UL, 5000UL, 500UL, 500UL, 30, 50)]
        public void BuildLayoutBundle_CalculateEfficiency_CorrectlyUsesFilesizeForEfficiency(
            ulong parentTotalFilesize, ulong childTotalFilesize,
            ulong parentDepFileSize, ulong childDepFilesize,
            int parentDependencyCount, int childDependencyCount)
        {
            BuildLayout.Bundle grandparent = CreateBundleWithFileSize(1);
            BuildLayout.Bundle parent = CreateBundleWithFileSize(parentTotalFilesize);
            BuildLayout.Bundle child = CreateBundleWithFileSize(childTotalFilesize);

            BuildLayout.ExplicitAsset grandparentAsset = CreateExplicitAssetWithParentBundle(grandparent);
            List<BuildLayout.ExplicitAsset> parentAssets = new List<BuildLayout.ExplicitAsset>();
            ulong expectedParentDepFileSize = 0;
            for (int i = 0; i < parentDependencyCount; i++)
            {
                var curFileSize = (ulong)parentDependencyCount / parentDepFileSize;
                expectedParentDepFileSize += curFileSize;
                var parentAsset = CreateExplicitAssetWithParentBundle(parent, curFileSize);
                grandparent.UpdateBundleDependency(grandparentAsset, parentAsset);
                parentAssets.Add(parentAsset);
            }

            ulong expectedChildDepFileSize = 0;
            for (int i = 0; i < childDependencyCount; i++)
            {
                var curFileSize = (ulong)childDependencyCount / childDepFilesize;
                expectedChildDepFileSize += curFileSize;
                var childAsset = CreateExplicitAssetWithParentBundle(child, curFileSize);
                parent.UpdateBundleDependency(parentAssets[0], childAsset);
            }

            grandparent.SerializeBundleToBundleDependency();
            parent.SerializeBundleToBundleDependency();

            BuildLayoutGenerationTask.CalculateEfficiency(grandparent);

            float expectedGrandparentEfficiency = (float) expectedParentDepFileSize / parentTotalFilesize;
            float expectedGrandparentExpandedEfficiency = (float) (expectedParentDepFileSize + expectedChildDepFileSize) / (parentTotalFilesize + childTotalFilesize);
            float expectedParentEfficiency = (float) expectedChildDepFileSize / childTotalFilesize;
            Assert.AreEqual(expectedGrandparentEfficiency, grandparent.BundleDependencies[0].Efficiency, "Grandparent Efficiency not calculated correctly - this should not be affected by the children of parent.");
            Assert.AreEqual(expectedGrandparentExpandedEfficiency, grandparent.BundleDependencies[0].ExpandedEfficiency, "Grandparent ExpandedEfficiency not calculated correctly - this should take into account the total Efficiency of both the direct children Dependency Links as well as all BundleDependencies below the child.");
            Assert.AreEqual(expectedParentEfficiency, parent.BundleDependencies[0].ExpandedEfficiency, "Parent ExpandedEfficiency not calculated correctly - this should take into account the Efficiency of the direct child only.");
        }

        [Test]
        public void BuildLayoutBundle_SaveAndLoad_ReadsCorrectDataForHeader()
        {
            DateTime time = DateTime.Now;
            BuildLayout layout = CreateTestLayout(time, "testError123");
            time = layout.BuildStart; // due to removing miliseconds in the layout
            string filePath = $"{Application.dataPath}/testLayout.json";

            try
            {
                layout.WriteToFile(filePath, false);
                FileAssert.Exists(filePath, $"Failed to save build layout to {filePath}");
                BuildLayout readLayout = BuildLayout.Open(filePath, false, false);
                Assert.AreEqual(DateTime.MinValue, readLayout.BuildStart, "StartDate was not expected to have read a valid date");

                // test all header values to ensure the values are as expect (not the same as default values)
                readLayout.ReadHeader();
                Assert.AreEqual(time, readLayout.Header.BuildStart, "Header Data written or Read incorrectly");
                Assert.AreEqual(10, readLayout.Header.Duration, "Header Data written or Read incorrectly");
                Assert.AreEqual("testError123", readLayout.Header.BuildError, "Header Data written or Read incorrectly");
                Assert.AreEqual(BuildTarget.Android, readLayout.Header.BuildTarget, "Header Data written or Read incorrectly");
                Assert.AreEqual(BuildType.UpdateBuild, readLayout.Header.BuildType, "Header Data written or Read incorrectly");
                Assert.AreEqual("testHash123", readLayout.Header.BuildResultHash, "Header Data written or Read incorrectly");

                // that the test groups have not been read
                Assert.AreEqual(0, readLayout.Groups.Count, "Expect to have not read any Groups yet with only Header read");
            }
            finally
            {
                if (File.Exists(filePath))
                    File.Delete(filePath);
            }
        }

        [Test]
        public void BuildLayoutBundle_SaveAndLoad_ReadsCorrectDataForBody()
        {
            DateTime time = DateTime.Now;
            BuildLayout layout = CreateTestLayout(time);
            string filePath = $"{Application.dataPath}/testLayout.json";

            try
            {
                layout.WriteToFile(filePath, false);
                FileAssert.Exists(filePath, $"Failed to save build layout to {filePath}");
                BuildLayout readLayout = BuildLayout.Open(filePath, false, false);

                Assert.AreEqual(0, readLayout.Groups.Count, "Expect to have not read any Groups yet with no data yet read read");
                readLayout.ReadHeader();
                Assert.AreEqual(0, readLayout.Groups.Count, "Expect to have not read any Groups yet with only Header read");
                readLayout.ReadFull();
                Assert.AreEqual(1, readLayout.Groups.Count, "Expect to have 1 Groups after doing a full read read");
            }
            finally
            {
                if (File.Exists(filePath))
                    File.Delete(filePath);
            }
        }

        [Test]
        [TestCase(typeof(GameObject), AssetType.GameObject)]
        [TestCase(typeof(SceneAsset), AssetType.Scene)]
        [TestCase(typeof(AnimatorController), AssetType.AnimationController)]
        [TestCase(typeof(TestObject), AssetType.ScriptableObject)]
        [TestCase(typeof(ObjectReferenceMonoBehaviour), AssetType.MonoBehaviour)]
        public void GetAssetType_ReturnsCorrect(System.Type typeIn, AssetType expectedType)
        {
            AssetType actual = BuildLayoutHelpers.GetAssetType(typeIn);
            Assert.AreEqual(expectedType, actual);
        }
    }
}