// Given an artwork, construct a ready-to-append XmlElement (including assets, etc).
        // This method performs no validation on the artwork, so be sure to validate before calling.
        public static XmlElement XmlElementFromValidArtwork(XmlDocument doc, artwork aw)
        {
            XmlElement el = doc.CreateElement("Image");
            el.SetAttribute("path", "" + aw.uniqueName);
            el.SetAttribute("title", "" + aw.title);
            el.SetAttribute("year", "" + aw.year);
            el.SetAttribute("artist", "" + aw.artist);
            el.SetAttribute("medium", "" + aw.medium);
            // TODO: Do we want this?
            // newEntry.SetAttribute("description", "" + aw.);

            // If there are keywords, add them.
            if (aw.keywords.Count > 0)
            {
                XmlElement keywords = doc.CreateElement("Keywords");
                foreach (string keyword in aw.keywords)
                {
                    XmlElement keyword_val = doc.CreateElement("Keyword");
                    keyword_val.SetAttribute("Value", "" + keyword);
                    keywords.AppendChild(keyword_val);
                }
                el.AppendChild(keywords);
            }

            // If there are metadata assets, add them.
            if (aw.validatedAssets.Count > 0)
            {
                XmlElement metadata_element = doc.CreateElement("Metadata");
                XmlElement group_element = doc.CreateElement("Group");
                group_element.SetAttribute("name", "A");
                foreach (asset ass in aw.validatedAssets)
                {
                    XmlElement item_element = doc.CreateElement("Item");
                    item_element.SetAttribute("Filename", ass.uniqueName);
                    item_element.SetAttribute("Name", ass.name);
                    item_element.SetAttribute("Description", ass.description);
                    // TODO: Implement Type = "Web"
                    item_element.SetAttribute("Type", "Image");
                    group_element.AppendChild(item_element);
                }
                metadata_element.AppendChild(group_element);
                el.AppendChild(metadata_element);
            }
            return el;
        }
        // Attempts to validate the artwork, throwing an exception at the first invalid occurence.
        // This doesn't attempt to actually load images for artworks or assets: that check is performed
        // later implicitly, when the DeepZoom images/thumbnails are created.
        // An artwork is invalid if:
        // - Its path is not an existing image file.
        // - Its title is missing.
        // - Its year is not a valid number between -9999 and 9999
        public static void validateArtwork(artwork aw)
        {
            // Convert the image path to an absolute path to eliminate ambiguities.
            // If the path is relative, it should be relative to the CSV directory.
            if (!Path.IsPathRooted(aw.path))
            {
                aw.path = Path.GetDirectoryName(inputCSVPath) + "\\" + aw.path;
            }

            if (!Helpers.staticIsImageFile(aw.path) || !File.Exists(aw.path))
                throw new InvalidCSVArtworkException("Artwork path is not an existing image file.");
            if (String.IsNullOrWhiteSpace(aw.title))
                throw new InvalidCSVArtworkException("Artwork title missing.");
            if (aw.year < -9999 || aw.year > 9999)
                throw new InvalidCSVArtworkException("Artwork year must be a valid number from -9999 to 9999.");

            for (int i = 0; i < aw.assets.Count; i++)
            {
                asset ass = aw.assets[i];
                try { validateAsset(ass); }
                catch (Exception e)
                {
                    csvLog("Error validating asset at: " + ass.path + ".\nMessage: " + e.Message);
                }
            }

            // Also do things that are handled in AddNewImageControl.Browse_Click(), namely:
            // - Generate a unique name for this instance of this file
            aw.uniqueName = generateUniqueName(aw.path, "Data/Images/");
        }
        // Parses a CSV file into a list of artwork structs.
        public static List<artwork> parseCSV(string path)
        {
            List<artwork> artworks = new List<artwork>();

            using (CsvReader csv =
                   new CsvReader(new StreamReader(path), true))
            {
                // The CSV exported from excel will generally have rows of identical lengths, even though our rows in the excel
                // document will be of variable length.  Thus there will be a large number of empty cells.
                int fieldCount = csv.FieldCount;
                while (csv.ReadNextRecord())
                {
                    fieldCount = csv.FieldCount;
                    artwork aw = new artwork();

                    // Parse the artwork.
                    aw.path = csv[IMAGE_PATH_INDEX];
                    aw.thumbPath = csv[IMAGE_THUMB_INDEX];
                    aw.title = csv[TITLE_INDEX];
                    try
                    {
                        aw.year = Convert.ToInt32(csv[YEAR_INDEX]);
                    }
                    catch (Exception e)
                    {
                        throw new InvalidCSVArtworkException("Year for artwork at " + aw.path + " is not a number.");
                    }
                    aw.artist = csv[ARTIST_INDEX];
                    aw.medium = csv[MEDIUM_INDEX];
                    aw.keywords = parseKeywords(csv[KEYWORDS_INDEX]);
                    aw.assets = new List<asset>();
                    for (int i = FIRST_ASSET_INDEX; i < fieldCount; i++)
                    {
                        try
                        {
                            if (!String.IsNullOrWhiteSpace(csv[i]))
                                aw.assets.Add(parseAsset(csv[i]));
                        }
                        catch (Exception e)
                        {
                            throw new InvalidCSVArtworkException("Error parsing asset " + (i - FIRST_ASSET_INDEX + 1) + " for artwork at " + aw.path + "."
                                                                 + Environment.NewLine + "  Message: " + e.Message);
                        }
                    }
                    artworks.Add(aw);
                }
            }
            return artworks;
        }
        // Saves both thumbnails for an artwork.
        public static void saveThumbs(artwork aw)
        {
            String dataDir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\Data\\";
            string thumbPath = dataDir + "Images/Thumbnail/" + aw.uniqueName;
            string imgPath = dataDir + "Images/" + aw.uniqueName;
            if (aw.path.Equals(thumbPath))
            {
                throw new InvalidCSVArtworkException("Artwork thumbnails already exist.");
            }

            // If a thumbnail path is provided, attempt to use it.
            // If this fails, we cascade to the general case rather than throwing an exception.
            if (!String.IsNullOrWhiteSpace(aw.thumbPath))
            {
                string customThumbPath = aw.thumbPath;
                // Convert to absolute path.  If relative, it's relative to the CSV directory.
                if (!Path.IsPathRooted(customThumbPath))
                {
                    customThumbPath = Path.GetDirectoryName(inputCSVPath) + "\\" + customThumbPath;
                }
                try
                {
                    File.Delete(thumbPath);
                    File.Delete(imgPath);
                    System.Drawing.Image img = Helpers.getThumbnail(customThumbPath, 800);
                    img.Save(thumbPath);
                    img.Save(imgPath);
                    img.Dispose();
                    return;
                }
                catch (Exception e)
                {
                    csvLog("Error in loading custom thumbnail."
                           + Environment.NewLine + "  Message: " + e.Message
                           + Environment.NewLine + "Attempting to automatically generate a thumbnail from the image.");
                }
            }

            // The general case.
            try
            {
                File.Delete(thumbPath);
                File.Delete(imgPath);
                // TODO: Get thumbnail from thumbnail image instead, if it exists!
                System.Drawing.Image img = Helpers.getThumbnail(aw.path, 800);
                img.Save(thumbPath);
                img.Save(imgPath);
                img.Dispose();
            }
            catch (Exception e)
            {
                throw new InvalidCSVArtworkException("Error creating thumbnail for artwork. Message: " + e.Message);
            }
        }
        // Creates the DeepZoom images.
        // Should throw an exception if the DeepZoom creation fails (for instance if the image is broken)
        public static void createDeepZoomImages(artwork aw)
        {
            string imagePath = aw.path;
            string imageName = aw.uniqueName;
            string destFolderPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\Data\\Images\\DeepZoom";

            ImageCreator ic = new ImageCreator();
            ic.TileFormat = ImageFormat.Jpg;
            ic.TileOverlap = 1;
            ic.TileSize = 256;
            ic.ImageQuality = 0.92;
            ic.UseOptimizations = true;
            Directory.CreateDirectory(destFolderPath + "\\" + imageName);
            string target = destFolderPath + "\\" + imageName + "\\dz.xml";
            ic.Create(imagePath, target);
            ic = null;
            System.GC.Collect();
        }