using System;
using System.Data;
using System.IO.Compression;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using Authentication;
using gaseous_server.Classes.Metadata;
using gaseous_server.Controllers;
using gaseous_server.Controllers.v1_1;
using gaseous_server.Models;
using IGDB.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json;
using SharpCompress.Common;
using static gaseous_server.Classes.Metadata.Games;

namespace gaseous_server.Classes
{
	public class Collections
	{
        public static List<CollectionItem> GetCollections(string userid) {
            Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
            string sql = "SELECT * FROM RomCollections WHERE OwnedBy=@ownedby ORDER BY `Name`";
            Dictionary<string, object> dbDict = new Dictionary<string, object>{
                { "ownedby", userid }
            };
            DataTable data = db.ExecuteCMD(sql, dbDict);

            List<CollectionItem> collectionItems = new List<CollectionItem>();

            foreach(DataRow row in data.Rows) {
                collectionItems.Add(BuildCollectionItem(row));
            }

            return collectionItems;
        }

        public static CollectionItem GetCollection(long Id, string userid) {
            Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
			string sql;
            if (userid == "")
            {
                // reserved for internal operations
                sql = "SELECT * FROM RomCollections WHERE Id = @id ORDER BY `Name`";
            }
            else
            {
                // instigated by a user
                sql = "SELECT * FROM RomCollections WHERE Id = @id AND OwnedBy = @ownedby ORDER BY `Name`";
            }
			Dictionary<string, object> dbDict = new Dictionary<string, object>
            {
                { "id", Id },
                { "ownedby", userid }
            };
			DataTable romDT = db.ExecuteCMD(sql, dbDict);

			if (romDT.Rows.Count > 0)
			{
				DataRow row = romDT.Rows[0];
				CollectionItem collectionItem = BuildCollectionItem(row);

				return collectionItem;
			}
			else
			{
				throw new Exception("Unknown Collection Id");
			}
        }

        public static CollectionItem NewCollection(CollectionItem item, string userid)
        {
            Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
            string sql = "INSERT INTO RomCollections (`Name`, Description, Platforms, Genres, Players, PlayerPerspectives, Themes, MinimumRating, MaximumRating, MaximumRomsPerPlatform, MaximumBytesPerPlatform, MaximumCollectionSizeInBytes, FolderStructure, IncludeBIOSFiles, ArchiveType, AlwaysInclude, BuiltStatus, OwnedBy) VALUES (@name, @description, @platforms, @genres, @players, @playerperspectives, @themes, @minimumrating, @maximumrating, @maximumromsperplatform, @maximumbytesperplatform, @maximumcollectionsizeinbytes, @folderstructure, @includebiosfiles, @archivetype, @alwaysinclude, @builtstatus, @ownedby); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
            Dictionary<string, object> dbDict = new Dictionary<string, object>
            {
                { "name", item.Name },
                { "description", item.Description },
                { "platforms", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Platforms, new List<long>())) },
                { "genres", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Genres, new List<long>())) },
                { "players", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Players, new List<long>())) },
                { "playerperspectives", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.PlayerPerspectives, new List<long>())) },
                { "themes", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Themes, new List<long>())) },
                { "minimumrating", Common.ReturnValueIfNull(item.MinimumRating, -1) },
                { "maximumrating", Common.ReturnValueIfNull(item.MaximumRating, -1) },
                { "maximumromsperplatform", Common.ReturnValueIfNull(item.MaximumRomsPerPlatform, -1) },
                { "maximumbytesperplatform", Common.ReturnValueIfNull(item.MaximumBytesPerPlatform, -1) },
                { "maximumcollectionsizeinbytes", Common.ReturnValueIfNull(item.MaximumCollectionSizeInBytes, -1) },
                { "folderstructure", Common.ReturnValueIfNull(item.FolderStructure, CollectionItem.FolderStructures.Gaseous) },
                { "includebiosfiles", Common.ReturnValueIfNull(item.IncludeBIOSFiles, 0) },
                { "archivetype", Common.ReturnValueIfNull(item.ArchiveType, CollectionItem.ArchiveTypes.Zip) },
                { "alwaysinclude", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.AlwaysInclude, new List<CollectionItem.AlwaysIncludeItem>())) },
                { "builtstatus", CollectionItem.CollectionBuildStatus.WaitingForBuild },
                { "ownedby", userid }
            };
            DataTable romDT = db.ExecuteCMD(sql, dbDict);
            long CollectionId = (long)romDT.Rows[0][0];

            CollectionItem collectionItem = GetCollection(CollectionId, userid);

            StartCollectionItemBuild(CollectionId, userid);

            return collectionItem;
        }

        public static CollectionItem EditCollection(long Id, CollectionItem item, string userid, bool ForceRebuild = true)
        {
            Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
            string sql = "UPDATE RomCollections SET `Name`=@name, Description=@description, Platforms=@platforms, Genres=@genres, Players=@players, PlayerPerspectives=@playerperspectives, Themes=@themes, MinimumRating=@minimumrating, MaximumRating=@maximumrating, MaximumRomsPerPlatform=@maximumromsperplatform, MaximumBytesPerPlatform=@maximumbytesperplatform, MaximumCollectionSizeInBytes=@maximumcollectionsizeinbytes, FolderStructure=@folderstructure, IncludeBIOSFiles=@includebiosfiles, ArchiveType=@archivetype, AlwaysInclude=@alwaysinclude, BuiltStatus=@builtstatus WHERE Id=@id AND OwnedBy=@ownedby";
            Dictionary<string, object> dbDict = new Dictionary<string, object>
            {
                { "id", Id },
                { "name", item.Name },
                { "description", item.Description },
                { "platforms", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Platforms, new List<long>())) },
                { "genres", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Genres, new List<long>())) },
                { "players", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Players, new List<long>())) },
                { "playerperspectives", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.PlayerPerspectives, new List<long>())) },
                { "themes", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Themes, new List<long>())) },
                { "minimumrating", Common.ReturnValueIfNull(item.MinimumRating, -1) },
                { "maximumrating", Common.ReturnValueIfNull(item.MaximumRating, -1) },
                { "maximumromsperplatform", Common.ReturnValueIfNull(item.MaximumRomsPerPlatform, -1) },
                { "maximumbytesperplatform", Common.ReturnValueIfNull(item.MaximumBytesPerPlatform, -1) },
                { "maximumcollectionsizeinbytes", Common.ReturnValueIfNull(item.MaximumCollectionSizeInBytes, -1) },
                { "folderstructure", Common.ReturnValueIfNull(item.FolderStructure, CollectionItem.FolderStructures.Gaseous) },
                { "includebiosfiles", Common.ReturnValueIfNull(item.IncludeBIOSFiles, 0) },
                { "alwaysinclude", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.AlwaysInclude, new List<CollectionItem.AlwaysIncludeItem>())) },
                { "archivetype", Common.ReturnValueIfNull(item.ArchiveType, CollectionItem.ArchiveTypes.Zip) },
                { "ownedby", userid }
            };
            
            string CollectionZipFile = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + item.ArchiveExtension);
            if (ForceRebuild == true) 
            {
                dbDict.Add("builtstatus", CollectionItem.CollectionBuildStatus.WaitingForBuild);
                if (File.Exists(CollectionZipFile))
                {
                    Logging.Log(Logging.LogType.Warning, "Collections", "Deleting existing build of collection: " + item.Name);
                    File.Delete(CollectionZipFile);
                }
            }
            else
            {
                if (File.Exists(CollectionZipFile))
                {
                    dbDict.Add("builtstatus", CollectionItem.CollectionBuildStatus.Completed);
                }
                else
                {
                    dbDict.Add("builtstatus", CollectionItem.CollectionBuildStatus.NoStatus);
                }
            }
            db.ExecuteCMD(sql, dbDict);
            
            CollectionItem collectionItem = GetCollection(Id, userid);

            if (collectionItem.BuildStatus == CollectionItem.CollectionBuildStatus.WaitingForBuild)
            {
                StartCollectionItemBuild(Id, userid);
            }

            return collectionItem;
        }

        public static void DeleteCollection(long Id, string userid)
        {
            Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
            string sql = "DELETE FROM RomCollections WHERE Id=@id AND OwnedBy=@ownedby";
            Dictionary<string, object> dbDict = new Dictionary<string, object>
            {
                { "id", Id },
                { "ownedby", userid }
            };
            db.ExecuteCMD(sql, dbDict);

            string CollectionZipFile = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + ".zip");
            if (File.Exists(CollectionZipFile))
            {
                File.Delete(CollectionZipFile);
            }
        }

        public static void StartCollectionItemBuild(long Id, string userid)
        {
            // send blank user id to getcollection as this is not a user initiated process
            CollectionItem collectionItem = GetCollection(Id, userid);

            if (collectionItem.BuildStatus != CollectionItem.CollectionBuildStatus.Building)
            {
                // set collection item to waitingforbuild
                Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
                string sql = "UPDATE RomCollections SET BuiltStatus=@bs WHERE Id=@id";
                Dictionary<string, object> dbDict = new Dictionary<string, object>();
                dbDict.Add("id", Id);
                dbDict.Add("bs", CollectionItem.CollectionBuildStatus.WaitingForBuild);
                db.ExecuteCMD(sql, dbDict);

                // start background task
                ProcessQueue.QueueItem queueItem = new ProcessQueue.QueueItem(ProcessQueue.QueueItemType.CollectionCompiler, 1, false, true);
                queueItem.Options = new Dictionary<string, object>{
                    { "Id", Id },
                    { "UserId", userid }
                };
                queueItem.ForceExecute();
                ProcessQueue.QueueItems.Add(queueItem);
            }
        }

        public static CollectionContents GetCollectionContent(CollectionItem collectionItem, string userid) {
            Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);

            // get age ratings for specified user
            List<AgeGroups.AgeRestrictionGroupings> UserAgeGroupings = new List<AgeGroups.AgeRestrictionGroupings>();
            bool UserAgeGroupIncludeUnrated = true;
            if (userid != "")
            {
                Authentication.UserTable<Authentication.ApplicationUser> userTable = new UserTable<ApplicationUser>(db);
                var user = userTable.GetUserById(userid);

                if (user.SecurityProfile.AgeRestrictionPolicy.IncludeUnrated == false)
                {
                    UserAgeGroupIncludeUnrated = false;
                }

                foreach (AgeGroups.AgeRestrictionGroupings ageGrouping in Enum.GetValues(typeof(AgeGroups.AgeRestrictionGroupings)))
                {
                    if (ageGrouping <= user.SecurityProfile.AgeRestrictionPolicy.MaximumAgeRestriction && ageGrouping != AgeGroups.AgeRestrictionGroupings.Unclassified)
                    {
                        UserAgeGroupings.Add(ageGrouping);
                    }
                }
            }

            List<CollectionContents.CollectionPlatformItem> collectionPlatformItems = new List<CollectionContents.CollectionPlatformItem>();

            // get platforms
            List<long> platformids = new List<long>();
            platformids.AddRange(collectionItem.Platforms);

            List<long>? DynamicPlatforms = new List<long>();
            DynamicPlatforms.AddRange(collectionItem.Platforms);

            List<Platform> platforms = new List<Platform>();

            // add platforms with an inclusion status
            foreach (CollectionItem.AlwaysIncludeItem alwaysIncludeItem in collectionItem.AlwaysInclude) 
            {
                if (
                        alwaysIncludeItem.InclusionState == CollectionItem.AlwaysIncludeStatus.AlwaysInclude ||
                        alwaysIncludeItem.InclusionState == CollectionItem.AlwaysIncludeStatus.AlwaysExclude
                    )
                    {
                        if (!platformids.Contains(alwaysIncludeItem.PlatformId))
                        {
                            platformids.Add(alwaysIncludeItem.PlatformId);
                        }
                }
            }

            // add dynamic platforms
            if (DynamicPlatforms.Count > 0) {
                foreach (long PlatformId in platformids) {
                    platforms.Add(Platforms.GetPlatform(PlatformId));
                }
            } else {
                // get all platforms to pull from
                Dictionary<string, List<Filters.FilterItem>> FilterDict = Filters.Filter(AgeGroups.AgeRestrictionGroupings.Adult, true);
                List<Classes.Filters.FilterItem> filteredPlatforms = (List<Classes.Filters.FilterItem>)FilterDict["platforms"];
                foreach (Filters.FilterItem filterItem in filteredPlatforms) {
                    platforms.Add(Platforms.GetPlatform(filterItem.Id));
                }
            }

            // age ratings
            AgeGroups.AgeRestrictionGroupings AgeGrouping = AgeGroups.AgeRestrictionGroupings.Unclassified;
            bool ContainsUnclassifiedAgeGroup = false;

            // build collection
            List<CollectionContents.CollectionPlatformItem> platformItems = new List<CollectionContents.CollectionPlatformItem>();

            foreach (Platform platform in platforms) {
                long TotalRomSize = 0;
                long TotalGameCount = 0;

                bool isDynamic = false;
                if (DynamicPlatforms.Contains((long)platform.Id))
                {
                    isDynamic = true;
                }
                else if (DynamicPlatforms.Count == 0)
                {
                    isDynamic = true;
                }

                Controllers.v1_1.GamesController.GameReturnPackage games = new Controllers.v1_1.GamesController.GameReturnPackage();
                if (isDynamic == true)
                {
                    Controllers.v1_1.GamesController.GameSearchModel searchModel = new Controllers.v1_1.GamesController.GameSearchModel{
                        Name = "",
                        Platform = new List<string>{
                            platform.Id.ToString()
                        },
                        Genre = collectionItem.Genres.ConvertAll(s => s.ToString()),
                        GameMode = collectionItem.Players.ConvertAll(s => s.ToString()),
                        PlayerPerspective = collectionItem.PlayerPerspectives.ConvertAll(s => s.ToString()),
                        Theme = collectionItem.Themes.ConvertAll(s => s.ToString()),
                        GameRating = new Controllers.v1_1.GamesController.GameSearchModel.GameRatingItem{
                            MinimumRating = collectionItem.MinimumRating,
                            MaximumRating = collectionItem.MaximumRating
                        },
                        GameAgeRating = new Controllers.v1_1.GamesController.GameSearchModel.GameAgeRatingItem{
                            AgeGroupings = UserAgeGroupings,
                            IncludeUnrated = UserAgeGroupIncludeUnrated
                        }
                    };
                    games = Controllers.v1_1.GamesController.GetGames(searchModel, userid);
                    
                }

                CollectionContents.CollectionPlatformItem collectionPlatformItem = new CollectionContents.CollectionPlatformItem(platform);
                collectionPlatformItem.Games = new List<CollectionContents.CollectionPlatformItem.CollectionGameItem>();

                // add titles with an inclusion status
                foreach (CollectionItem.AlwaysIncludeItem alwaysIncludeItem in collectionItem.AlwaysInclude) 
                {
                    if (
                        (
                            alwaysIncludeItem.InclusionState == CollectionItem.AlwaysIncludeStatus.AlwaysInclude ||
                            alwaysIncludeItem.InclusionState == CollectionItem.AlwaysIncludeStatus.AlwaysExclude
                        ) && alwaysIncludeItem.PlatformId == platform.Id
                        ) 
                        {
                            MinimalGameItem AlwaysIncludeGame = new MinimalGameItem(Games.GetGame(alwaysIncludeItem.GameId, false, false, false));
                            CollectionContents.CollectionPlatformItem.CollectionGameItem gameItem = new CollectionContents.CollectionPlatformItem.CollectionGameItem(AlwaysIncludeGame);
                            gameItem.InclusionStatus = new CollectionItem.AlwaysIncludeItem();
                            gameItem.InclusionStatus.PlatformId = alwaysIncludeItem.PlatformId;
                            gameItem.InclusionStatus.GameId = alwaysIncludeItem.GameId;
                            gameItem.InclusionStatus.InclusionState = alwaysIncludeItem.InclusionState;
                            gameItem.Roms = Roms.GetRoms((long)gameItem.Id, (long)platform.Id).GameRomItems;

                            collectionPlatformItem.Games.Add(gameItem);
                    }
                }

                foreach (MinimalGameItem game in games.Games) {
                    bool gameAlreadyInList = false;
                    foreach (CollectionContents.CollectionPlatformItem.CollectionGameItem existingGame in collectionPlatformItem.Games) 
                    {
                        if (existingGame.Id == game.Id) 
                        {
                            gameAlreadyInList = true;
                        }
                    }

                    if (gameAlreadyInList == false)
                    {
                        CollectionContents.CollectionPlatformItem.CollectionGameItem collectionGameItem = new CollectionContents.CollectionPlatformItem.CollectionGameItem(game);

                        List<Roms.GameRomItem> gameRoms = Roms.GetRoms((long)game.Id, (long)platform.Id).GameRomItems;
                        
                        bool AddGame = false;

                        // calculate total rom size for the game
                        long GameRomSize = 0;
                        foreach (Roms.GameRomItem gameRom in gameRoms) {
                            GameRomSize += (long)gameRom.Size;
                        }
                        if (collectionItem.MaximumBytesPerPlatform > 0) {
                            if ((TotalRomSize + GameRomSize) < collectionItem.MaximumBytesPerPlatform) {
                                AddGame = true;
                            }
                        }
                        else 
                        {
                            AddGame = true;
                        }

                        if (AddGame == true) {
                            TotalRomSize += GameRomSize;

                            bool AddRoms = false;

                            if (collectionItem.MaximumRomsPerPlatform > 0) { 
                                if (TotalGameCount < collectionItem.MaximumRomsPerPlatform) {
                                    AddRoms = true;
                                }
                            }
                            else
                            {
                                AddRoms = true;
                            }

                            if (AddRoms == true) {
                                TotalGameCount += 1;
                                collectionGameItem.Roms = gameRoms;
                                collectionPlatformItem.Games.Add(collectionGameItem);
                            }
                        }
                    }

                    // handle age grouping
                    AgeGroups.AgeRestrictionGroupings CurrentAgeGroup = AgeGroups.GetAgeGroupFromAgeRatings(game.AgeRatings);
                    if (CurrentAgeGroup > AgeGrouping)
                    {
                        AgeGrouping = CurrentAgeGroup;
                    }
                    if (CurrentAgeGroup == AgeGroups.AgeRestrictionGroupings.Unclassified)
                    {
                        ContainsUnclassifiedAgeGroup = true;
                    }
                }

                collectionPlatformItem.Games.Sort((x, y) => x.Name.CompareTo(y.Name));

                if (collectionPlatformItem.Games.Count > 0)
                {
                    bool AddPlatform = false;
                    if (collectionItem.MaximumCollectionSizeInBytes > 0)
                    {
                        if (TotalRomSize < collectionItem.MaximumCollectionSizeInBytes)
                        {
                            AddPlatform = true;
                        }
                    }
                    else
                    {
                        AddPlatform = true;
                    }

                    if (AddPlatform == true)
                    {
                        collectionPlatformItems.Add(collectionPlatformItem);
                    }
                }
            }

            collectionPlatformItems.Sort((x, y) => x.Name.CompareTo(y.Name));

            CollectionContents collectionContents = new CollectionContents
            {
                Collection = collectionPlatformItems,
                AgeGroup = AgeGrouping,
                ContainsUnclassifiedAgeGroup = ContainsUnclassifiedAgeGroup
            };

            return collectionContents;
        }

        public static void CompileCollections(long CollectionId, string userid)
        {
            Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);

            CollectionItem collectionItem = GetCollection(CollectionId, userid);
            if (collectionItem.BuildStatus == CollectionItem.CollectionBuildStatus.WaitingForBuild)
            {
                Logging.Log(Logging.LogType.Information, "Collections", "Beginning build of collection: " + collectionItem.Name);

                CollectionContents collectionContents = GetCollectionContent(collectionItem, userid);

                // set starting
                string sql = "UPDATE RomCollections SET BuiltStatus=@bs, AgeGroup=@ag, AgeGroupUnclassified=@agu WHERE Id=@id";
                Dictionary<string, object> dbDict = new Dictionary<string, object>
                {
                    { "id", collectionItem.Id },
                    { "bs", CollectionItem.CollectionBuildStatus.Building },
                    { "ag", collectionContents.AgeGroup },
                    { "agu", collectionContents.ContainsUnclassifiedAgeGroup }
                };
                db.ExecuteCMD(sql, dbDict);
                
                List<CollectionContents.CollectionPlatformItem> collectionPlatformItems = collectionContents.Collection;
                string ZipFilePath = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, collectionItem.Id + collectionItem.ArchiveExtension);
                string ZipFileTempPath = Path.Combine(Config.LibraryConfiguration.LibraryTempDirectory, collectionItem.Id.ToString());

                try
                {
                    
                    // clean up if needed
                    if (File.Exists(ZipFilePath))
                    {
                        Logging.Log(Logging.LogType.Warning, "Collections", "Deleting existing build of collection: " + collectionItem.Name);
                        File.Delete(ZipFilePath);
                    }

                    if (Directory.Exists(ZipFileTempPath))
                    {
                        Directory.Delete(ZipFileTempPath, true);
                    }

                    // gather collection files
                    Directory.CreateDirectory(ZipFileTempPath);
                    string ZipBiosPath = Path.Combine(ZipFileTempPath, "BIOS");

                    // get the games
                    foreach (CollectionContents.CollectionPlatformItem collectionPlatformItem in collectionPlatformItems)
                    {
                        // get platform bios files if present
                        if (collectionItem.IncludeBIOSFiles == true)
                        {
                            List<Bios.BiosItem> bios = Bios.GetBios(collectionPlatformItem.Id, true);
                            if (!Directory.Exists(ZipBiosPath)) {
                                Directory.CreateDirectory(ZipBiosPath);
                            }

                            foreach (Bios.BiosItem biosItem in bios) 
                            {
                                if (File.Exists(biosItem.biosPath))
                                {
                                    Logging.Log(Logging.LogType.Information, "Collections", "Copying BIOS file: " + biosItem.filename);
                                    File.Copy(biosItem.biosPath, Path.Combine(ZipBiosPath, biosItem.filename), true);
                                }
                            }
                        }

                        // create platform directory
                        string ZipPlatformPath = "";
                        switch (collectionItem.FolderStructure)
                        {
                            case CollectionItem.FolderStructures.Gaseous:
                                ZipPlatformPath = Path.Combine(ZipFileTempPath, collectionPlatformItem.Slug);
                                break;

                            case CollectionItem.FolderStructures.RetroPie:
                                try
                                {
                                    PlatformMapping.PlatformMapItem platformMapItem = PlatformMapping.GetPlatformMap(collectionPlatformItem.Id);
                                    ZipPlatformPath = Path.Combine(ZipFileTempPath, "roms", platformMapItem.RetroPieDirectoryName);
                                }
                                catch
                                {
                                    ZipPlatformPath = Path.Combine(ZipFileTempPath, collectionPlatformItem.Slug);
                                }

                                break;

                        }
                        if (!Directory.Exists(ZipPlatformPath))
                        {
                            Directory.CreateDirectory(ZipPlatformPath);
                        }

                        foreach (CollectionContents.CollectionPlatformItem.CollectionGameItem collectionGameItem in collectionPlatformItem.Games)
                        {
                            bool includeGame = false;
                            if (collectionGameItem.InclusionStatus == null)
                            {
                                includeGame = true;
                            }
                            else
                            {
                                if (collectionGameItem.InclusionStatus.InclusionState == CollectionItem.AlwaysIncludeStatus.AlwaysInclude)
                                {
                                    includeGame = true;
                                }
                            }

                            if (includeGame == true)
                            {
                                string ZipGamePath = "";
                                switch (collectionItem.FolderStructure)
                                {
                                    case CollectionItem.FolderStructures.Gaseous:
                                        // create game directory
                                        ZipGamePath = Path.Combine(ZipPlatformPath, collectionGameItem.Slug);
                                        if (!Directory.Exists(ZipGamePath))
                                        {
                                            Directory.CreateDirectory(ZipGamePath);
                                        }
                                        break;

                                    case CollectionItem.FolderStructures.RetroPie:
                                        ZipGamePath = ZipPlatformPath;
                                        break;
                                }                                    
                                
                                // copy in roms
                                foreach (Roms.GameRomItem gameRomItem in collectionGameItem.Roms)
                                {
                                    if (File.Exists(gameRomItem.Path))
                                    {
                                        Logging.Log(Logging.LogType.Information, "Collections", "Copying ROM: " + gameRomItem.Name);
                                        File.Copy(gameRomItem.Path, Path.Combine(ZipGamePath, gameRomItem.Name), true);
                                    }
                                }
                            }
                        }
                    }

                    // compress to zip
                    Logging.Log(Logging.LogType.Information, "Collections", "Compressing collection");
                    switch(collectionItem.ArchiveType)
                    {
                        case CollectionItem.ArchiveTypes.Zip:
                            ZipFile.CreateFromDirectory(ZipFileTempPath, ZipFilePath, CompressionLevel.SmallestSize, false);
                            break;

                        case CollectionItem.ArchiveTypes.RAR:

                            break;

                        case CollectionItem.ArchiveTypes.SevenZip:
                            
                            break;
                    }
                    

                    // clean up
                    if (Directory.Exists(ZipFileTempPath))
                    {
                        Logging.Log(Logging.LogType.Information, "Collections", "Cleaning up");
                        Directory.Delete(ZipFileTempPath, true);
                    }

                    // set completed
                    dbDict["bs"] = CollectionItem.CollectionBuildStatus.Completed;
                    db.ExecuteCMD(sql, dbDict);
                }
                catch (Exception ex)
                {
                    // clean up
                    if (Directory.Exists(ZipFileTempPath))
                    {
                        Directory.Delete(ZipFileTempPath, true);
                    }

                    if (File.Exists(ZipFilePath))
                    {
                        File.Delete(ZipFilePath);
                    }

                    // set failed
                    dbDict["bs"] = CollectionItem.CollectionBuildStatus.Failed;
                    db.ExecuteCMD(sql, dbDict);

                    Logging.Log(Logging.LogType.Critical, "Collection Builder", "Collection building has failed", ex);
                }
            }
        }

        private static CollectionItem BuildCollectionItem(DataRow row) {
            string strPlatforms = (string)Common.ReturnValueIfNull(row["Platforms"], "[ ]");
            string strGenres = (string)Common.ReturnValueIfNull(row["Genres"], "[ ]");
            string strPlayers = (string)Common.ReturnValueIfNull(row["Players"], "[ ]");
            string strPlayerPerspectives = (string)Common.ReturnValueIfNull(row["PlayerPerspectives"], "[ ]");
            string strThemes = (string)Common.ReturnValueIfNull(row["Themes"], "[ ]");
            string strAlwaysInclude = (string)Common.ReturnValueIfNull(row["AlwaysInclude"], "[ ]");

            CollectionItem item = new CollectionItem();
            item.Id = (long)row["Id"];
            item.Name = (string)row["Name"];
            item.Description = (string)row["Description"];
            item.Platforms = Newtonsoft.Json.JsonConvert.DeserializeObject<List<long>>(strPlatforms);
            item.Genres = Newtonsoft.Json.JsonConvert.DeserializeObject<List<long>>(strGenres);
            item.Players = Newtonsoft.Json.JsonConvert.DeserializeObject<List<long>>(strPlayers);
            item.PlayerPerspectives = Newtonsoft.Json.JsonConvert.DeserializeObject<List<long>>(strPlayerPerspectives);
            item.Themes = Newtonsoft.Json.JsonConvert.DeserializeObject<List<long>>(strThemes);
            item.MinimumRating = (int)Common.ReturnValueIfNull(row["MinimumRating"], -1);
            item.MaximumRating = (int)Common.ReturnValueIfNull(row["MaximumRating"], -1);
            item.MaximumRomsPerPlatform = (int)Common.ReturnValueIfNull(row["MaximumRomsPerPlatform"], (int)-1);
            item.MaximumBytesPerPlatform = (long)Common.ReturnValueIfNull(row["MaximumBytesPerPlatform"], (long)-1);
            item.MaximumCollectionSizeInBytes = (long)Common.ReturnValueIfNull(row["MaximumCollectionSizeInBytes"], (long)-1);
            item.FolderStructure = (CollectionItem.FolderStructures)(int)Common.ReturnValueIfNull(row["FolderStructure"], 0);
            item.IncludeBIOSFiles = (bool)row["IncludeBIOSFiles"];
            item.ArchiveType = (CollectionItem.ArchiveTypes)(int)Common.ReturnValueIfNull(row["ArchiveType"], 0);
            item.AlwaysInclude = Newtonsoft.Json.JsonConvert.DeserializeObject<List<CollectionItem.AlwaysIncludeItem>>(strAlwaysInclude);
            item.BuildStatus = (CollectionItem.CollectionBuildStatus)(int)Common.ReturnValueIfNull(row["BuiltStatus"], 0);

            return item;
        }

        public class CollectionItem
        {
            public CollectionItem()
            {

            }

            public long Id { get; set; }
            public string Name { get; set; }
            public string Description { get; set; }
            public List<long>? Platforms { get; set; }
            public List<long>? Genres { get; set; }
            public List<long>? Players { get; set; }
            public List<long>? PlayerPerspectives { get; set; }
            public List<long>? Themes { get; set; }
            public int MinimumRating { get; set; }
            public int MaximumRating { get; set; }
            public int? MaximumRomsPerPlatform { get; set; }
            public long? MaximumBytesPerPlatform { get; set; }
            public long? MaximumCollectionSizeInBytes { get; set; }
            public FolderStructures FolderStructure { get; set; } = FolderStructures.Gaseous;
            public bool IncludeBIOSFiles { get; set; } = true;
            public ArchiveTypes ArchiveType { get; set; } = CollectionItem.ArchiveTypes.Zip;
            public string ArchiveExtension
            {
                get
                {
                    if (ArchiveType != null)
                    {
                        switch (ArchiveType)
                        {
                            case ArchiveTypes.Zip:
                            default:
                                return ".zip";
                            
                            case ArchiveTypes.RAR:
                                return ".rar";

                            case ArchiveTypes.SevenZip:
                                return ".7z";
                        }
                    }
                    else
                    {
                        return ".zip";
                    }
                }
            }
            public List<AlwaysIncludeItem> AlwaysInclude { get; set; }

            [JsonIgnore]
            public CollectionBuildStatus BuildStatus
            {
                get
                {
                    if (_BuildStatus == CollectionBuildStatus.Completed)
                    {
                        if (File.Exists(Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + ArchiveExtension)))
                        {
                            return CollectionBuildStatus.Completed;
                        }
                        else
                        {
                            return CollectionBuildStatus.NoStatus;
                        }
                    }
                    else
                    {
                        return _BuildStatus;
                    }
                }
                set
                {
                    _BuildStatus = value;
                }
            }
            private CollectionBuildStatus _BuildStatus { get; set; }

            [JsonIgnore]
            public long CollectionBuiltSizeBytes
            {
                get
                {
                    if (BuildStatus == CollectionBuildStatus.Completed)
                    {
                        string ZipFilePath = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + ArchiveExtension);
                        if (File.Exists(ZipFilePath))
                        {
                            FileInfo fi = new FileInfo(ZipFilePath);
                            return fi.Length;
                        }
                        else
                        {
                            return 0;
                        }
                    }
                    else
                    {
                        return 0;
                    }
                }
            }

            public enum CollectionBuildStatus
            {
                NoStatus = 0,
                WaitingForBuild = 1,
                Building = 2,
                Completed = 3,
                Failed = 4
            }

            public enum FolderStructures
            {
                Gaseous = 0,
                RetroPie = 1
            }

            public enum ArchiveTypes
            {
                Zip = 0,
                RAR = 1,
                SevenZip = 2
            }

            public class AlwaysIncludeItem
            {
                public long PlatformId { get; set; }
                public long GameId { get; set; }
                public AlwaysIncludeStatus InclusionState { get; set; }
            }

            public enum AlwaysIncludeStatus
            {
                None = 0,
                AlwaysInclude = 1,
                AlwaysExclude = 2
            }
        }

        public class CollectionContents {
            [JsonIgnore]
            public List<CollectionPlatformItem> Collection { get; set; }

            [JsonIgnore]
            public long CollectionProjectedSizeBytes
            {
                get
                {
                    long CollectionSize = 0;

                    List<CollectionPlatformItem> collectionPlatformItems = new List<CollectionPlatformItem>();

                    if (Collection != null)
                    {
                        collectionPlatformItems = Collection;
                    }

                    foreach (CollectionPlatformItem platformItem in collectionPlatformItems)
                    {
                        CollectionSize += platformItem.RomSize;
                    }

                    return CollectionSize;
                }
            }

            public AgeGroups.AgeRestrictionGroupings AgeGroup { get; set; }
            public bool ContainsUnclassifiedAgeGroup { get; set; }

            public class CollectionPlatformItem {
                public CollectionPlatformItem(IGDB.Models.Platform platform) {
                    string[] PropertyWhitelist = new string[] { "Id", "Name", "Slug" };

                    PropertyInfo[] srcProperties = typeof(IGDB.Models.Platform).GetProperties();
                    PropertyInfo[] dstProperties = typeof(CollectionPlatformItem).GetProperties();
                    foreach (PropertyInfo srcProperty in srcProperties) {
                        if (PropertyWhitelist.Contains<string>(srcProperty.Name))
                        {
                            foreach (PropertyInfo dstProperty in dstProperties)
                            {
                                if (srcProperty.Name == dstProperty.Name)
                                {
                                    dstProperty.SetValue(this, srcProperty.GetValue(platform));
                                }
                            }
                        }
                    }
                }

                public long Id { get; set; }
                public string Name { get; set; }
                public string Slug { get; set; }

                public List<CollectionGameItem> Games { get; set; }

                public int RomCount {
                    get {
                        int Counter = 0;
                        foreach (CollectionGameItem Game in Games) {
                            Counter += 1;
                        }

                        return Counter;
                    }
                }

                public long RomSize {
                    get {
                        long Size = 0;
                        foreach (CollectionGameItem Game in Games) {
                            foreach (Roms.GameRomItem Rom in Game.Roms) {
                                Size += (long)Rom.Size;
                            }
                        }

                        return Size;
                    }
                }

                public class CollectionGameItem : MinimalGameItem
                {
                    public CollectionGameItem(MinimalGameItem gameObject)
                    {
                        this.Id = gameObject.Id;
                        this.Name = gameObject.Name;
                        this.Slug = gameObject.Slug;
                        this.TotalRating = gameObject.TotalRating;
                        this.TotalRatingCount = gameObject.TotalRatingCount;
                        this.Cover = gameObject.Cover;
                        this.Artworks = gameObject.Artworks;
                        this.FirstReleaseDate = gameObject.FirstReleaseDate;
                        this.AgeRatings = gameObject.AgeRatings;
                    }
                    
                    public IGDB.Models.Cover? CoverItem
                    {
                        get
                        {
                            if (Cover != null)
                            {
                                IGDB.Models.Cover cover = Covers.GetCover(Cover.Id, Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory, "Games", Slug), false);

                                return cover;
                            }
                            else
                            {
                                return null;
                            }
                        }
                    }

                    public AgeGroups.AgeRestrictionGroupings AgeGrouping
                    { 
                        get
                        {
                            return AgeGroups.GetAgeGroupFromAgeRatings(this.AgeRatings);
                        }
                    }

                    public CollectionItem.AlwaysIncludeItem InclusionStatus { get; set; }

                    public List<Roms.GameRomItem> Roms { get; set; }

                    public long RomSize {
                    get {
                        long Size = 0;
                        foreach (Roms.GameRomItem Rom in Roms) {
                            Size += (long)Rom.Size;
                        }
                    
                        return Size;
                    }
                }
                }
            }
        }
    }
}