/// <summary>
        /// Loads the mockup project into the model
        /// </summary>
        /// <param name="sourceFile">the mockup archive file</param>
        /// <returns></returns>
        public static MockupProject LoadProject(String sourceFile, bool createHashes)
        {
            MockupProject project = new MockupProject();

            using (DatabaseAccess db = new DatabaseAccess(sourceFile))
            {
                // Validate database schema
                ValidDatabase(db, sourceFile);

                // Load info
                project.Info = InternalLoadProjectInfo(db);

                // Load resources
                InternalLoadResources(db, project, createHashes);

                // Load branches
                InternalLoadBranches(db, project);

                // Load thumbnails
                InternalLoadThumbnails(db, project, createHashes);
            }

            // Sort members so that serialization always produces similar results
            project.Mockups         = project.Mockups.OrderBy(n => n.Attributes.Name).ToList();
            project.SymbolLibraries = project.SymbolLibraries.OrderBy(n => n.Attributes.Name).ToList();
            project.Assets          = project.Assets.OrderBy(i => i.Attributes.Name).ToList();
            project.Branches        = project.Branches.OrderBy(i => i.Id).ToList();

            return(project);
        }
        private static void InternalLoadBranches(DatabaseAccess db, MockupProject project)
        {
            String[][] data = db.GetTableContent(BarTable.BRANCHES.ToString());
            foreach (String[] row in data)
            {
                BranchInfo branch = new BranchInfo();
                branch.Id = row[0];

                // Read resource attributes
                if (row[1].Length == 0)
                {
                    row[1] = "{}";
                }

                branch.Attributes = JObject.Parse(row[1]).ToObject <Dictionary <String, JToken> >();
                project.Branches.Add(branch);
            }

#if (DEBUG)
            // Report unrecognized attributes
            HashSet <String> unknown = new HashSet <string>();
            foreach (BranchInfo branch in project.Branches)
            {
                foreach (String u in branch.Attributes.Keys)
                {
                    unknown.Add(u);
                }
            }

            ReportUnknownThings(unknown, typeof(BranchInfo).Name);
#endif
        }
        private static void InternalLoadThumbnails(DatabaseAccess db, MockupProject project, bool createHashes)
        {
            String[][] data = db.GetTableContent(BarTable.THUMBNAILS.ToString());
            foreach (String[] row in data)
            {
                ResourceThumbnail thumbnail = new ResourceThumbnail();
                thumbnail.Id = row[0];

                if (row[1].Length == 0)
                {
                    row[1] = "{}";
                }

                thumbnail.Attributes = JObject.Parse(row[1]).ToObject <Dictionary <String, JToken> >();

                // Create a hash for thumbnail image, as no value in reading thumbnail data
                if (createHashes)
                {
                    using (MD5 md5 = MD5.Create())
                    {
                        byte[] b = ASCIIEncoding.ASCII.GetBytes((String)thumbnail.Attributes["image"]);
                        b = md5.ComputeHash(b);
                        thumbnail.Attributes["image-hash"] = BitConverter.ToString(b);

                        // Dont want to show image binary data if showing hashes
                        thumbnail.Attributes.Remove("image");
                    }
                }

                project.Thumbnails.Add(thumbnail);
            }

#if (DEBUG)
            // Report unrecognized attributes
            HashSet <String> unknown = new HashSet <string>();
            foreach (ResourceThumbnail thumbnail in project.Thumbnails)
            {
                foreach (String u in thumbnail.Attributes.Keys)
                {
                    unknown.Add(u);
                }
            }

            ReportUnknownThings(unknown, typeof(ResourceThumbnail).Name);
#endif
        }
        public static Dictionary <String, HashSet <String> > GetUnknownProperties(MockupProject project)
        {
            Dictionary <String, HashSet <String> > unknown = new Dictionary <String, HashSet <String> >();

            String acType = typeof(AbstractControlContainer).Name;

            unknown[acType] = new HashSet <String>();

            String mcType = typeof(MockupControl).Name;

            unknown[mcType] = new HashSet <String>();

            String mcpType = typeof(ControlProperties).Name;

            unknown[mcpType] = new HashSet <String>();

            String msType = typeof(MockupSymbol).Name;

            unknown[msType] = new HashSet <String>();

            List <AbstractControlContainer> allContainers = new List <AbstractControlContainer>();

            allContainers.AddRange(project.Mockups);
            allContainers.AddRange(project.SymbolLibraries);

            foreach (AbstractControlContainer container in allContainers)
            {
                // Check Mockup
                foreach (String key in container.UnknownProperties.Keys)
                {
                    unknown[acType].Add(key);
                }

                // Check MockupControl
                if (container is Mockup)
                {
                    Mockup m = (Mockup)container;
                    if (m.Controls != null && m.Controls.Length > 0)
                    {
                        foreach (MockupControl c in m.Controls)
                        {
                            foreach (String key in c.UnknownProperties.Keys)
                            {
                                unknown[mcType].Add(key);
                            }

                            // Check MockupControlProperties
                            if (c.Properties != null)
                            {
                                foreach (String key in c.Properties.Unknown.Keys)
                                {
                                    unknown[mcpType].Add(key);
                                }
                            }
                        }
                    }
                }
                else if (container is SymbolLibrary)
                {
                    SymbolLibrary m = (SymbolLibrary)container;
                    if (m.Symbols != null && m.Symbols.Length > 0)
                    {
                        foreach (MockupSymbol c in m.Symbols)
                        {
                            foreach (String key in c.UnknownProperties.Keys)
                            {
                                unknown[msType].Add(key);
                            }

                            // Check MockupControlProperties
                            if (c.Properties != null)
                            {
                                foreach (String key in c.Properties.Unknown.Keys)
                                {
                                    unknown[mcpType].Add(key);
                                }
                            }
                        }
                    }
                }
            }

            return(unknown);
        }
        private static void InternalLoadResources(DatabaseAccess db, MockupProject project, bool createHashes)
        {
            String[][] data = db.GetTableContent(BarTable.RESOURCES.ToString());
            foreach (String[] row in data)
            {
                if (row[2].Length == 0)
                {
                    row[2] = "{}";
                }

                // Read resource attributes
                ResourceAttributes attributes = new ResourceAttributes(row[2]);

#if (DEBUG)
                ReportUnknownThings(attributes.Unknown, typeof(ResourceAttributes).Name);
#endif

                switch (attributes.Kind)
                {
                case ResourceKind.Mockup:
                case ResourceKind.SymbolLibrary:
                    // Create appropariate instance for each case and add to project
                    AbstractControlContainer container;
                    if (attributes.Kind == ResourceKind.Mockup)
                    {
                        container = new Mockup();
                        project.Mockups.Add(container as Mockup);
                    }
                    else
                    {
                        container = new SymbolLibrary();
                        project.SymbolLibraries.Add(container as SymbolLibrary);
                    }

                    // Set properties and attributes
                    container.Id         = row[0];
                    container.BranchId   = row[1];
                    container.Attributes = attributes;

                    // Load the 'data' value which is a JSON string, basically definition of all controls
                    JObject mData = JObject.Parse(row[3]);
                    if (mData != null && mData[MOCKUP_PROP] != null)
                    {
                        container.MeasuredW = (String)mData[MOCKUP_PROP][MEASUREDW_PROP];
                        container.MeasuredH = (String)mData[MOCKUP_PROP][MEASUREDH_PROP];
                        container.MockupW   = (String)mData[MOCKUP_PROP][MOCKUPW_PROP];
                        container.MockupH   = (String)mData[MOCKUP_PROP][MOCKUPH_PROP];
                        container.Version   = (String)mData[MOCKUP_PROP][VERSION_PROP];

                        if (mData[MOCKUP_PROP][CONTROLS_PROP] != null)
                        {
                            JToken control = mData[MOCKUP_PROP][CONTROLS_PROP][CONTROL_PROP];
                            if (control != null)
                            {
                                if (attributes.Kind == ResourceKind.Mockup)
                                {
                                    (container as Mockup).Controls = control.ToObject <MockupControl[]>().OrderBy(i => i.ID).ToArray();
                                }
                                else
                                {
                                    (container as SymbolLibrary).Symbols = control.ToObject <MockupSymbol[]>().OrderBy(i => i.ID).ToArray();
                                }
                            }
                            else if (!attributes.Trashed)
                            {
                                Warning(String.Format("Empty {0}, may be removed: {1}", attributes.Kind.ToString().ToLower(), attributes.Name));
                            }
                        }
                    }

                    break;

                case ResourceKind.Asset:
                    ProjectAsset asset = new ProjectAsset();
                    asset.Id         = row[0];
                    asset.BranchId   = row[1];
                    asset.Attributes = attributes;
                    asset.Data       = row[3];

                    if (createHashes)
                    {
                        using (MD5 md5 = MD5.Create())
                        {
                            byte[] b = ASCIIEncoding.ASCII.GetBytes(asset.Data);
                            b = md5.ComputeHash(b);
                            asset.DataHash = BitConverter.ToString(b);

                            // Dont want to show data if using hashes
                            asset.Data = null;
                        }
                    }

                    project.Assets.Add(asset);
                    break;

                default:
                    Warning(String.Format("Unkown resource kind: {0}", row[2]));
                    break;
                }
            }

#if (DEBUG)
            // Report unrecognized stuff... useful for adding more detail to
            // model classes, which is useful for custom processing, and perhaps
            // editing capabilities in the future
            Dictionary <String, HashSet <String> > unknown = GetUnknownProperties(project);
            foreach (String type in unknown.Keys)
            {
                ReportUnknownThings(unknown[type], type);
            }
#endif
        }