/// <summary>
 /// Returns true if it's either an empty directory or it contains CanvasManifest.json file.
 /// </summary>
 /// <returns></returns>
 private bool ValidateSafeToDelete(ErrorContainer errors)
 {
     if (Directory.EnumerateFiles(_directory).Any() && !File.Exists(Path.Combine(_directory, "CanvasManifest.json")))
     {
         errors.BadParameter("Must provide path to either empty directory or a directory where the app was previously unpacked.");
         throw new DocumentException();
     }
     return(true);
 }
        // Write back out to a msapp file.
        public static void SaveAsMsApp(CanvasDocument app, string fullpathToMsApp, ErrorContainer errors, bool isValidation = false)
        {
            app.ApplyBeforeMsAppWriteTransforms(errors);

            if (!fullpathToMsApp.EndsWith(".msapp", StringComparison.OrdinalIgnoreCase) &&
                fullpathToMsApp.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
            {
                errors.BadParameter("Only works for .msapp files");
                return;
            }

            if (File.Exists(fullpathToMsApp)) // Overwrite!
            {
                File.Delete(fullpathToMsApp);
            }

            var checksum = new ChecksumMaker();

            DirectoryWriter.EnsureFileDirExists(fullpathToMsApp);
            using (var z = ZipFile.Open(fullpathToMsApp, ZipArchiveMode.Create))
            {
                foreach (FileEntry entry in app.GetMsAppFiles(errors))
                {
                    if (entry != null)
                    {
                        var e = z.CreateEntry(entry.Name.ToMsAppPath());
                        using (var dest = e.Open())
                        {
                            dest.Write(entry.RawBytes, 0, entry.RawBytes.Length);
                            checksum.AddFile(entry.Name.ToMsAppPath(), entry.RawBytes);
                        }
                    }
                }

                ComputeAndWriteChecksum(app, checksum, z, errors, isValidation);
            }

            // Undo BeforeWrite transforms so CanvasDocument representation is unchanged
            app.ApplyAfterMsAppLoadTransforms(errors);
        }
        /// <summary>
        /// Load an .msapp file for a Canvas Document.
        /// </summary>
        /// <param name="fullPathToMsApp">path to an .msapp file</param>
        /// <returns>A tuple of the document and errors and warnings. If there are errors, the document is null.  </returns>
        public static (CanvasDocument, ErrorContainer) LoadFromMsapp(string fullPathToMsApp)
        {
            var errors = new ErrorContainer();

            Utilities.EnsurePathRooted(fullPathToMsApp);

            if (!fullPathToMsApp.EndsWith(".msapp", StringComparison.OrdinalIgnoreCase))
            {
                errors.BadParameter("Only works for .msapp files");
            }

            Utilities.VerifyFileExists(errors, fullPathToMsApp);
            if (errors.HasErrors)
            {
                return(null, errors);
            }

            using (var stream = new FileStream(fullPathToMsApp, FileMode.Open))
            {
                var doc = Wrapper(() => MsAppSerializer.Load(stream, errors), errors);
                return(doc, errors);
            }
        }
        // Full fidelity read-write

        public static CanvasDocument LoadFromSource(string directory2, ErrorContainer errors)
        {
            if (File.Exists(directory2))
            {
                if (directory2.EndsWith(".msapp", StringComparison.OrdinalIgnoreCase))
                {
                    errors.BadParameter($"Must point to a source directory, not an msapp file ({directory2}");
                }
            }

            Utilities.VerifyDirectoryExists(errors, directory2);

            if (errors.HasErrors)
            {
                return(null);
            }

            var    dir = new DirectoryReader(directory2);
            var    app = new CanvasDocument();
            string appInsightsInstumentationKey = null;

            // Do the manifest check (and version check) first.
            // MAnifest lives in top-level directory.
            foreach (var file in dir.EnumerateFiles("", "*.json"))
            {
                switch (file.Kind)
                {
                case FileKind.CanvasManifest:
                    var manifest = file.ToObject <CanvasManifestJson>();

                    if (manifest.FormatVersion != CurrentSourceVersion)
                    {
                        errors.FormatNotSupported($"This tool only supports {CurrentSourceVersion}, the manifest version is {manifest.FormatVersion}");
                        throw new DocumentException();
                    }

                    app._properties  = manifest.Properties;
                    app._header      = manifest.Header;
                    app._publishInfo = manifest.PublishInfo;
                    app._screenOrder = manifest.ScreenOrder;
                    break;

                case FileKind.Templates:
                    foreach (var kvp in file.ToObject <Dictionary <string, CombinedTemplateState> >())
                    {
                        app._templateStore.AddTemplate(kvp.Key, kvp.Value);
                    }
                    break;

                case FileKind.ComponentReferences:
                    var refs = file.ToObject <ComponentDependencyInfo[]>();
                    app._libraryReferences = refs;
                    break;

                case FileKind.AppInsightsKey:
                    var appInsights = file.ToObject <AppInsightsKeyJson>();
                    appInsightsInstumentationKey = appInsights.InstrumentationKey;
                    break;
                }
            }
            if (appInsightsInstumentationKey != null)
            {
                app._properties.InstrumentationKey = appInsightsInstumentationKey;
            }
            if (app._header == null)
            {
                // Manifest not found.
                errors.FormatNotSupported($"Can't find CanvasManifest.json file - is sources an old version?");
                throw new DocumentException();
            }

            // Load template files, recreate References/templates.json
            LoadTemplateFiles(errors, app, Path.Combine(directory2, PackagesDir), out var templateDefaults);

            foreach (var file in dir.EnumerateFiles(EntropyDir))
            {
                switch (file.Kind)
                {
                case FileKind.Entropy:
                    app._entropy = file.ToObject <Entropy>();
                    break;

                case FileKind.AppCheckerResult:
                    app._appCheckerResultJson = file.ToObject <AppCheckerResultJson>();
                    break;

                case FileKind.Checksum:
                    app._checksum = file.ToObject <ChecksumJson>();
                    app._checksum.ClientBuildDetails = _buildVerJson;
                    break;

                default:
                    errors.GenericWarning($"Unexpected file in Entropy, discarding");
                    break;
                }
            }

            // The resource entries for sample data is sharded into individual json files.
            // Add each of these entries back into Resrouces.json
            var resources = new List <ResourceJson>();

            app._resourcesJson = new ResourcesJson()
            {
                Resources = new ResourceJson[0]
            };
            foreach (var file in dir.EnumerateFiles(AssetsDir, "*", false))
            {
                var fileEntry = file.ToFileEntry();
                if (fileEntry.Name.GetExtension() == ".json")
                {
                    // If its a json file then this must be one of the sharded files from Resources.json
                    resources.Add(file.ToObject <ResourceJson>());
                }
            }

            // Add the resources from sharded files to _resourcesJson.Resources
            if (resources.Count > 0)
            {
                app._resourcesJson.Resources = resources.ToArray();
            }

            // We have processed all the json files in Assets directory, now interate through all tge files to add the asset files.
            foreach (var file in dir.EnumerateFiles(AssetsDir))
            {
                // Skip adding the json files which were created to contain the information for duplicate asset files.
                // The name of the such json files is of the format - <assetFileName>.<assetFileExtension>.json (eg. close_1.jpg.json)
                var fileName = file._relativeName;
                var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);

                // Check if the original extension was .json and the remaining file name has still got an extension,
                // Then this is an additional file that was created to contain information for duplicate assets.
                if (Path.HasExtension(fileNameWithoutExtension) && Path.GetExtension(fileName) == ".json")
                {
                    var localAssetInfoJson = file.ToObject <LocalAssetInfoJson>();
                    app._localAssetInfoJson.Add(localAssetInfoJson.NewFileName, localAssetInfoJson);
                }
                // Add non json files to _assetFiles
                else if (Path.GetExtension(fileName) != ".json")
                {
                    app.AddAssetFile(file.ToFileEntry());
                }
            }

            app.GetLogoFile();

            // Add the entries for local assets back to resrouces.json
            TranformResourceJson.AddLocalAssetEntriesToResourceJson(app);

            foreach (var file in dir.EnumerateFiles(OtherDir))
            {
                // Special files like Header / Properties
                switch (file.Kind)
                {
                case FileKind.Unknown:
                    // Track any unrecognized files so we can save back.
                    app.AddFile(file.ToFileEntry());
                    break;

                default:
                    // Shouldn't find anything else not unknown in here, but just ignore them for now
                    errors.GenericWarning($"Unexpected file in Other, discarding");
                    break;
                }
            } // each loose file in '\other'

            LoadDataSources(app, dir, errors);
            LoadSourceFiles(app, dir, templateDefaults, errors);

            foreach (var file in dir.EnumerateFiles(ConnectionDir))
            {
                // Special files like Header / Properties
                switch (file.Kind)
                {
                case FileKind.Connections:
                    app._connections = file.ToObject <IDictionary <string, ConnectionJson> >();
                    break;
                }
            }


            // Defaults.
            // - DynamicTypes.Json, Resources.Json , Templates.Json - could all be empty
            // - Themes.json- default to


            app.OnLoadComplete(errors);

            return(app);
        }