using AssetStudio;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace AssetStudioCLI.Options
{
    internal enum HelpGroups
    {
        General,
        Convert,
        Logger,
        Live2D,
        FBX,
        Filter,
        Advanced,
    }

    internal enum WorkMode
    {
        Extract,
        Export,
        ExportRaw,
        Dump,
        Info,
        Live2D,
        SplitObjects,
    }

    internal enum AssetGroupOption
    {
        None,
        TypeName,
        ContainerPath,
        ContainerPathFull,
        SourceFileName,
        SceneHierarchy,
    }

    internal enum FilenameFormat
    {
        AssetName,
        AssetName_PathID,
        PathID,
    }

    internal enum ExportListType
    {
        None,
        XML,
    }

    internal enum AudioFormat
    {
        None,
        Wav,
    }

    internal enum FilterBy
    {
        None,
        Name,
        Container,
        PathID,
        NameOrContainer,
        NameAndContainer,
    }

    internal enum CustomCompressionType
    {
        Zstd,
        Lz4,
    }

    internal static class CLIOptions
    {
        public static bool isParsed;
        public static bool showHelp;
        public static string[] cliArgs;
        public static string inputPath;
        public static FilterBy filterBy;
        private static Dictionary<string, string> optionsDict;
        private static Dictionary<string, string> flagsDict;
        private static Dictionary<HelpGroups, Dictionary<string, string>> optionGroups;
        private static List<ClassIDType> exportableAssetTypes;
        private static Dictionary<string, ClassIDType> knownAssetTypesDict;
        //general
        public static Option<WorkMode> o_workMode;
        public static Option<List<ClassIDType>> o_exportAssetTypes;
        public static Option<AssetGroupOption> o_groupAssetsBy;
        public static Option<FilenameFormat> o_filenameFormat;
        public static Option<string> o_outputFolder;
        public static Option<bool> o_displayHelp;
        //logger
        public static Option<LoggerEvent> o_logLevel;
        public static Option<LogOutputMode> o_logOutput;
        //convert
        public static bool convertTexture;
        public static Option<ImageFormat> o_imageFormat;
        public static Option<AudioFormat> o_audioFormat;
        //live2d
        public static Option<CubismLive2DExtractor.Live2DModelGroupOption> o_l2dGroupOption;
        public static Option<bool> f_l2dAssetSearchByFilename;
        public static Option<CubismLive2DExtractor.Live2DMotionMode> o_l2dMotionMode;
        public static Option<bool> f_l2dForceBezier;
        //fbx
        public static Option<float> o_fbxScaleFactor;
        public static Option<int> o_fbxBoneSize;
        public static Option<bool> f_fbxUvsAsDiffuseMaps;
        //filter
        public static Option<List<string>> o_filterByName;
        public static Option<List<string>> o_filterByContainer;
        public static Option<List<string>> o_filterByPathID;
        public static Option<List<string>> o_filterByText;
        //advanced
        public static Option<CustomCompressionType> o_customCompressionType;
        public static Option<int> o_maxParallelExportTasks;
        public static Option<ExportListType> o_exportAssetList;
        public static Option<string> o_assemblyPath;
        public static Option<UnityVersion> o_unityVersion;
        public static Option<bool> f_notRestoreExtensionName;
        public static Option<bool> f_avoidLoadingViaTypetree;
        public static Option<bool> f_loadAllAssets;

        static CLIOptions()
        {
            OptionExtensions.OptionGrouping = OptionGrouping;
            InitOptions();
        }

        private static void OptionGrouping(string name, string desc, string example, HelpGroups helpGroup, bool isFlag)
        {
            if (string.IsNullOrEmpty(name))
            {
                return;
            }

            var optionDesc = desc + example.Color(ColorConsole.BrightCyan);
            var optionDict = new Dictionary<string, string> { { name, optionDesc } };
            if (optionGroups.TryGetValue(helpGroup, out Dictionary<string, string> groupDict))
            {
                groupDict.Add(name, optionDesc);
            }
            else
            {
                optionGroups.Add(helpGroup, optionDict);
            }

            if (isFlag)
            {
                flagsDict.Add(name, optionDesc);
            }
            else
            {
                optionsDict.Add(name, optionDesc);
            }
        }

        private static void InitOptions()
        {
            isParsed = false;
            showHelp = false;
            cliArgs = null;
            inputPath = "";
            filterBy = FilterBy.None;
            optionsDict = new Dictionary<string, string>();
            flagsDict = new Dictionary<string, string>();
            optionGroups = new Dictionary<HelpGroups, Dictionary<string, string>>();
            exportableAssetTypes = new List<ClassIDType>
            {
                ClassIDType.Texture2D,
                ClassIDType.Texture2DArray,
                ClassIDType.Sprite,
                ClassIDType.TextAsset,
                ClassIDType.MonoBehaviour,
                ClassIDType.Font,
                ClassIDType.Shader,
                ClassIDType.AudioClip,
                ClassIDType.VideoClip,
                ClassIDType.MovieTexture,
                ClassIDType.Mesh,
            };
            knownAssetTypesDict = ((ClassIDType[])Enum.GetValues(typeof(ClassIDType))).ToHashSet().ToDictionary(x => x.ToString().ToLower(), y => y);

            #region Init General Options
            o_workMode = new GroupedOption<WorkMode>
            (
                optionDefaultValue: WorkMode.Export,
                optionName: "-m, --mode <value>",
                optionDescription: "Specify working mode\n" +
                    "<Value: extract | export(default) | exportRaw | dump | info | live2d | splitObjects>\n" +
                    "Extract - Extracts(Decompresses) asset bundles\n" +
                    "Export - Exports converted assets\n" +
                    "ExportRaw - Exports raw data\n" +
                    "Dump - Makes asset dumps\n" +
                    "Info - Loads file(s), shows the number of available for export assets and exits\n" +
                    "Live2D - Exports Live2D Cubism models\n" +
                    "SplitObjects - Exports split objects (fbx)\n",
                optionExample: "Example: \"-m info\"\n",
                optionHelpGroup: HelpGroups.General
            );
            o_exportAssetTypes = new GroupedOption<List<ClassIDType>>
            (
                optionDefaultValue: exportableAssetTypes,
                optionName: "-t, --asset-type <value(s)>",
                optionDescription: "Specify asset type(s) to export\n" +
                    "<Value(s): tex2d, tex2dArray, sprite, textAsset, monoBehaviour, font, shader\n" +
                    "movieTexture, audio, video, mesh | all(default)>\n" +
                    "All - export all asset types, which are listed in the values\n" +
                    "*To specify multiple asset types, write them separated by ',' or ';' without spaces\n",
                optionExample: "Examples: \"-t sprite\" or \"-t tex2d,sprite,audio\" or \"-t tex2d;sprite;font\"\n",
                optionHelpGroup: HelpGroups.General
            );
            o_groupAssetsBy = new GroupedOption<AssetGroupOption>
            (
                optionDefaultValue: AssetGroupOption.ContainerPath,
                optionName: "-g, --group-option <value>",
                optionDescription: "Specify the way in which exported assets should be grouped\n" +
                    "<Value: none | type | container(default) | containerFull | fileName | sceneHierarchy>\n" +
                    "None - Do not group exported assets\n" +
                    "Type - Group exported assets by type name\n" +
                    "Container - Group exported assets by container path\n" +
                    "ContainerFull - Group exported assets by full container path (e.g. with prefab name)\n" +
                    "SceneHierarchy - Group exported assets by their node path in scene hierarchy\n" +
                    "FileName - Group exported assets by source file name\n",
                optionExample: "Example: \"-g containerFull\"\n",
                optionHelpGroup: HelpGroups.General
            );
            o_filenameFormat = new GroupedOption<FilenameFormat>
            (
                optionDefaultValue: FilenameFormat.AssetName,
                optionName: "-f, --filename-format <value>",
                optionDescription: "Specify the file name format for exported assets\n" +
                    "<Value: assetName(default) | assetName_pathID | pathID>\n" +
                    "AssetName - Asset file names will look like \"assetName.extension\"\n" +
                    "AssetName_pathID - Asset file names will look like \"assetName @pathID.extension\"\n" +
                    "PathID - Asset file names will look like \"pathID.extension\"\n",
                optionExample: "Example: \"-f assetName_pathID\"\n",
                optionHelpGroup: HelpGroups.General
            );
            o_outputFolder = new GroupedOption<string>
            (
                optionDefaultValue: "ASExport",
                optionName: "-o, --output <path>",
                optionDescription: "Specify path to the output folder\n" +
                    "If path isn't specified, 'ASExport' folder will be created in the program's work folder\n",
                optionExample: "",
                optionHelpGroup: HelpGroups.General
            );
            o_displayHelp = new GroupedOption<bool>
            (
                optionDefaultValue: false,
                optionName: "-h, --help",
                optionDescription: "Display help and exit",
                optionExample: "",
                optionHelpGroup: HelpGroups.General
            );
            #endregion

            #region Init Logger Options
            o_logLevel = new GroupedOption<LoggerEvent>
            (
                optionDefaultValue: LoggerEvent.Info,
                optionName: "--log-level <value>",
                optionDescription: "Specify the log level\n" +
                    "<Value: verbose | debug | info(default) | warning | error>\n",
                optionExample: "Example: \"--log-level warning\"\n",
                optionHelpGroup: HelpGroups.Logger
            );
            o_logOutput = new GroupedOption<LogOutputMode> 
            (
                optionDefaultValue: LogOutputMode.Console,
                optionName: "--log-output <value>",
                optionDescription: "Specify the log output\n" +
                    "<Value: console(default) | file | both>\n",
                optionExample: "Example: \"--log-output both\"",
                optionHelpGroup: HelpGroups.Logger
            );
            #endregion

            #region Init Convert Options
            convertTexture = true;
            o_imageFormat = new GroupedOption<ImageFormat>
            (
                optionDefaultValue: ImageFormat.Png,
                optionName: "--image-format <value>",
                optionDescription: "Specify the format for converting image assets\n" +
                    "<Value: none | jpg | png(default) | bmp | tga | webp>\n" +
                    "None - Do not convert images and export them as texture data (.tex)\n",
                optionExample: "Example: \"--image-format jpg\"\n",
                optionHelpGroup: HelpGroups.Convert
            );
            o_audioFormat = new GroupedOption<AudioFormat>
            (
                optionDefaultValue: AudioFormat.Wav,
                optionName: "--audio-format <value>",
                optionDescription: "Specify the format for converting FMOD audio assets\n" +
                    "<Value: none | wav(default)>\n" +
                    "None - Do not convert FMOD audios and export them in their own format\n",
                optionExample: "Example: \"--audio-format wav\"",
                optionHelpGroup: HelpGroups.Convert
            );
            #endregion

            #region Init Cubism Live2D Options
            o_l2dGroupOption = new GroupedOption<CubismLive2DExtractor.Live2DModelGroupOption>
            (
                optionDefaultValue: CubismLive2DExtractor.Live2DModelGroupOption.ContainerPath,
                optionName: "--l2d-group-option <value>",
                optionDescription: "Specify the way in which exported models should be grouped\n" +
                    "<Value: container(default) | fileName | modelName>\n" +
                    "Container - Group exported models by container path\n" +
                    "FileName - Group exported models by source file name\n" +
                    "ModelName - Group exported models by model name\n",
                optionExample: "Example: \"--l2d-group-option modelName\"\n",
                optionHelpGroup: HelpGroups.Live2D
            );
            o_l2dMotionMode = new GroupedOption<CubismLive2DExtractor.Live2DMotionMode>
            (
                optionDefaultValue: CubismLive2DExtractor.Live2DMotionMode.MonoBehaviour,
                optionName: "--l2d-motion-mode <value>",
                optionDescription: "Specify Live2D motion export mode\n" +
                    "<Value: monoBehaviour(default) | animationClip>\n" +
                    "MonoBehaviour - Try to export motions from MonoBehaviour Fade motions\n" +
                    "If no Fade motions are found, the AnimationClip method will be used\n" +
                    "AnimationClip - Try to export motions using AnimationClip assets\n",
                optionExample: "Example: \"--l2d-motion-mode animationClip\"\n",
                optionHelpGroup: HelpGroups.Live2D
            );
            f_l2dAssetSearchByFilename = new GroupedOption<bool>
            (
                optionDefaultValue: false,
                optionName: "--l2d-search-by-filename",
                optionDescription: "(Flag) If specified, Studio will search for model-related Live2D assets by file name\n" + 
                    "rather than by container\n" +
                    "(Preferred option if all l2d assets of a single model are stored in a single file\n" + 
                    "or containers are obfuscated)\n",
                optionExample: "",
                optionHelpGroup: HelpGroups.Live2D,
                isFlag: true
            );
            f_l2dForceBezier = new GroupedOption<bool>
            (
                optionDefaultValue: false,
                optionName: "--l2d-force-bezier",
                optionDescription: "(Flag) If specified, Linear motion segments will be calculated as Bezier segments\n" +
                    "(May help if the exported motions look jerky/not smooth enough)",
                optionExample: "",
                optionHelpGroup: HelpGroups.Live2D,
                isFlag: true
            );
            #endregion

            #region Init FBX Options
            o_fbxScaleFactor = new GroupedOption<float>
            (
                optionDefaultValue: 1f,
                optionName: "--fbx-scale-factor <value>",
                optionDescription: "Specify the FBX Scale Factor\n" +
                    "<Value: float number from 0 to 100 (default=1)>\n",
                optionExample: "Example: \"--fbx-scale-factor 50\"\n",
                optionHelpGroup: HelpGroups.FBX
            );
            o_fbxBoneSize = new GroupedOption<int>
            (
                optionDefaultValue: 10,
                optionName: "--fbx-bone-size <value>",
                optionDescription: "Specify the FBX Bone Size\n" +
                    "<Value: integer number from 0 to 100 (default=10)>\n",
                optionExample: "Example: \"--fbx-bone-size 10\"\n",
                optionHelpGroup: HelpGroups.FBX
            );
            f_fbxUvsAsDiffuseMaps = new GroupedOption<bool>
            (
                optionDefaultValue: false,
                optionName: "--fbx-uvs-as-diffuse",
                optionDescription: "(Flag) If specified, Studio will export all UVs as Diffuse maps.\n" +
                    "Сan be useful if you cannot find some UVs after exporting (e.g. in Blender)\n" +
                    "(But can also cause some bugs with UVs)",
                optionExample: "",
                optionHelpGroup: HelpGroups.FBX
            );
            #endregion

            #region Init Filter Options
            o_filterByName = new GroupedOption<List<string>>
            (
                optionDefaultValue: new List<string>(),
                optionName: "--filter-by-name <text>",
                optionDescription: "Specify the name by which assets should be filtered\n" +
                    "*To specify multiple names write them separated by ',' or ';' without spaces\n",
                optionExample: "Example: \"--filter-by-name char\" or \"--filter-by-name char,bg\"\n",
                optionHelpGroup: HelpGroups.Filter
            );
            o_filterByContainer = new GroupedOption<List<string>>
            (
                optionDefaultValue: new List<string>(),
                optionName: "--filter-by-container <text>",
                optionDescription: "Specify the container by which assets should be filtered\n" +
                    "*To specify multiple containers write them separated by ',' or ';' without spaces\n",
                optionExample: "Example: \"--filter-by-container arts\" or \"--filter-by-container arts,icons\"\n",
                optionHelpGroup: HelpGroups.Filter
            );
            o_filterByPathID = new GroupedOption<List<string>>
            (
                optionDefaultValue: new List<string>(),
                optionName: "--filter-by-pathid <text>",
                optionDescription: "Specify the PathID by which assets should be filtered\n" +
                    "*To specify multiple PathIDs write them separated by ',' or ';' without spaces\n",
                optionExample: "Example: \"--filter-by-pathid 7238605633795851352,-2430306240205277265\"\n",
                optionHelpGroup: HelpGroups.Filter
            );
            o_filterByText = new GroupedOption<List<string>>
            (
                optionDefaultValue: new List<string>(),
                optionName: "--filter-by-text <text>",
                optionDescription: "Specify the text by which assets should be filtered\n" +
                    "Looks for assets that contain the specified text in their names or containers\n" +
                    "*To specify multiple values write them separated by ',' or ';' without spaces\n",
                optionExample: "Example: \"--filter-by-text portrait\" or \"--filter-by-text portrait,art\"",
                optionHelpGroup: HelpGroups.Filter
            );
            #endregion

            #region Init Advanced Options
            o_customCompressionType = new GroupedOption<CustomCompressionType>
            (
                optionDefaultValue: CustomCompressionType.Zstd,
                optionName: "--custom-compression <value>",
                optionDescription: "Specify the compression type for assets that use custom compression\n" + 
                    "<Value: zstd(default) | lz4>\n" +
                    "Zstd - Try to decompress as zstd archive\n" +
                    "Lz4 - Try to decompress as lz4 archive\n",
                optionExample: "Example: \"--custom-compression lz4\"\n",
                optionHelpGroup: HelpGroups.Advanced
            );

            o_maxParallelExportTasks = new GroupedOption<int>
            (
                optionDefaultValue: Environment.ProcessorCount - 1,
                optionName: "--max-export-tasks <value>",
                optionDescription: "Specify the number of parallel tasks for asset export\n" +
                    "<Value: integer number from 1 to max number of cores (default=max)>\n" +
                    "Max - Number of cores in your CPU\n",
                optionExample: "Example: \"--max-export-tasks 8\"\n",
                optionHelpGroup: HelpGroups.Advanced
            );

            o_exportAssetList = new GroupedOption<ExportListType>
            (
                optionDefaultValue: ExportListType.None,
                optionName: "--export-asset-list <value>",
                optionDescription: "Specify the format in which you want to export asset list\n" +
                    "<Value: none(default) | xml>\n" +
                    "None - Do not export asset list\n",
                optionExample: "Example: \"--export-asset-list xml\"\n",
                optionHelpGroup: HelpGroups.Advanced
            );
            o_assemblyPath = new GroupedOption<string>
            (
                optionDefaultValue: "",
                optionName: "--assembly-folder <path>",
                optionDescription: "Specify the path to the assembly folder\n",
                optionExample: "",
                optionHelpGroup: HelpGroups.Advanced
            );
            o_unityVersion = new GroupedOption<UnityVersion>
            (
                optionDefaultValue: null,
                optionName: "--unity-version <text>",
                optionDescription: "Specify Unity version\n",
                optionExample: "Example: \"--unity-version 2017.4.39f1\"\n",
                optionHelpGroup: HelpGroups.Advanced
            );
            f_notRestoreExtensionName = new GroupedOption<bool>
            (
                optionDefaultValue: false,
                optionName: "--not-restore-extension",
                optionDescription: "(Flag) If specified, AssetStudio will not try to use/restore original TextAsset\nextension name, and will just export all TextAssets with the \".txt\" extension\n",
                optionExample: "",
                optionHelpGroup: HelpGroups.Advanced,
                isFlag: true
            );
            f_avoidLoadingViaTypetree = new GroupedOption<bool>
            (
                optionDefaultValue: false,
                optionName: "--avoid-typetree-loading",
                optionDescription: "(Flag) If specified, AssetStudio will not try to parse assets at load time\nusing their type tree\n",
                optionExample: "",
                optionHelpGroup: HelpGroups.Advanced,
                isFlag: true
            );
            f_loadAllAssets = new GroupedOption<bool>
            (
                optionDefaultValue: false,
                optionName: "--load-all",
                optionDescription: "(Flag) If specified, AssetStudio will load assets of all types\n(Only for Dump, Info and ExportRaw modes)",
                optionExample: "",
                optionHelpGroup: HelpGroups.Advanced,
                isFlag: true
            );
            #endregion
        }

        public static void ParseArgs(string[] args)
        {
            cliArgs = args;

            var brightYellow = ColorConsole.BrightYellow;
            var brightRed = ColorConsole.BrightRed;

            if (args.Length == 0 || args.Any(x => x.ToLower() == "-h" || x.ToLower() == "--help" || x.ToLower() == "-?"))
            {
                showHelp = true;
                return;
            }

            if (!args[0].StartsWith("-"))
            {
                inputPath = Path.GetFullPath(args[0]).Replace("\"", "");
                if (!Directory.Exists(inputPath) && !File.Exists(inputPath))
                {
                    Console.WriteLine($"{"Error:".Color(brightRed)} Invalid input path \"{args[0].Color(brightRed)}\".\n" +
                        $"Specified file or folder was not found. The input path must be specified as the first argument.");
                    return;
                }
            }
            else
            {
                Console.WriteLine($"{"Error:".Color(brightRed)} Input path was empty. Specify the input path as the first argument.");
                return;
            }

            var resplittedArgs = new List<string>();
            for (int i = 1; i < args.Length; i++)
            {
                string arg = args[i];

                if (arg.Contains('='))
                {
                    var splittedArgs = arg.Split('=');
                    resplittedArgs.Add(splittedArgs[0]);
                    resplittedArgs.Add(splittedArgs[1]);
                }
                else
                {
                    resplittedArgs.Add(arg);
                }    
            };

            #region Parse "Working Mode" Option
            var workModeOptionIndex = resplittedArgs.FindIndex(x => x.ToLower() == "-m" || x.ToLower() == "--mode");
            if (workModeOptionIndex >= 0)
            {
                var option = resplittedArgs[workModeOptionIndex];
                if (workModeOptionIndex + 1 >= resplittedArgs.Count)
                {
                    Console.WriteLine($"{"Error during parsing options:".Color(brightRed)} Value for [{option.Color(brightYellow)}] option was not found.\n");
                    TryFindOptionDescription(option, optionsDict);
                    return;
                }
                var value = resplittedArgs[workModeOptionIndex + 1];
                switch (value.ToLower())
                {
                    case "extract":
                        o_workMode.Value = WorkMode.Extract;
                        break;
                    case "export":
                        o_workMode.Value = WorkMode.Export;
                        break;
                    case "raw":
                    case "exportraw":
                        o_workMode.Value = WorkMode.ExportRaw;
                        break;
                    case "dump":
                        o_workMode.Value = WorkMode.Dump;
                        break;
                    case "info":
                        o_workMode.Value = WorkMode.Info;
                        break;
                    case "l2d":
                    case "live2d":
                        o_workMode.Value = WorkMode.Live2D;
                        o_exportAssetTypes.Value = new List<ClassIDType>
                        {
                            ClassIDType.AnimationClip,
                            ClassIDType.MonoBehaviour,
                            ClassIDType.Texture2D,
                        };
                        break;
                    case "splitobjects":
                        o_workMode.Value = WorkMode.SplitObjects;
                        o_exportAssetTypes.Value = new List<ClassIDType>
                        {
                            ClassIDType.Texture2D,
                            ClassIDType.Material,
                            ClassIDType.Mesh,
                            ClassIDType.MeshRenderer,
                            ClassIDType.MeshFilter,
                            ClassIDType.SkinnedMeshRenderer,
                        };
                        break;
                    default:
                        Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unsupported working mode: [{value.Color(brightRed)}].\n");
                        ShowOptionDescription(o_workMode);
                        return;
                }
                resplittedArgs.RemoveRange(workModeOptionIndex, 2);
            }
            #endregion

            #region Parse Flags
            for (var i = 0; i < resplittedArgs.Count; i++) 
            {
                var flag = resplittedArgs[i].ToLower();

                switch(flag)
                {
                    case "--l2d-search-by-filename":
                        if (o_workMode.Value != WorkMode.Live2D)
                        {
                            Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{flag.Color(brightYellow)}] flag. This flag is not suitable for the current working mode [{o_workMode.Value}].\n");
                            ShowOptionDescription(o_workMode);
                            return;
                        }
                        f_l2dAssetSearchByFilename.Value = true;
                        resplittedArgs.RemoveAt(i);
                        break;
                    case "--l2d-force-bezier":
                        if (o_workMode.Value != WorkMode.Live2D)
                        {
                            Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{flag.Color(brightYellow)}] flag. This flag is not suitable for the current working mode [{o_workMode.Value}].\n");
                            ShowOptionDescription(o_workMode);
                            return;
                        }
                        f_l2dForceBezier.Value = true;
                        resplittedArgs.RemoveAt(i);
                        break;
                    case "--fbx-uvs-as-diffuse":
                        if (o_workMode.Value != WorkMode.SplitObjects)
                        {
                            Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{flag.Color(brightYellow)}] flag. This flag is not suitable for the current working mode [{o_workMode.Value}].\n");
                            ShowOptionDescription(o_workMode);
                            return;
                        }
                        f_fbxUvsAsDiffuseMaps.Value = true;
                        resplittedArgs.RemoveAt(i);
                        break;
                    case "--not-restore-extension":
                        f_notRestoreExtensionName.Value = true;
                        resplittedArgs.RemoveAt(i);
                        break;
                    case "--avoid-typetree-loading":
                        f_avoidLoadingViaTypetree.Value = true;
                        resplittedArgs.RemoveAt(i);
                        break;
                    case "--load-all":
                        switch (o_workMode.Value)
                        {
                            case WorkMode.ExportRaw:
                            case WorkMode.Dump:
                            case WorkMode.Info:
                                f_loadAllAssets.Value = true;
                                resplittedArgs.RemoveAt(i);
                                break;
                            default:
                                Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{flag.Color(brightYellow)}] flag. This flag is not suitable for the current working mode [{o_workMode.Value}].\n");
                                ShowOptionDescription(f_loadAllAssets, isFlag: true);
                                return;
                        }
                        break;
                }
            }            
            #endregion

            #region Parse Options
            for (var i = 0; i < resplittedArgs.Count; i++)
            {
                var option = resplittedArgs[i].ToLower();
                try
                {
                    var value = resplittedArgs[i + 1].Replace("\"", "");
                    switch (option)
                    {
                        case "-t":
                        case "--asset-type":
                            if (o_workMode.Value == WorkMode.Live2D || o_workMode.Value == WorkMode.SplitObjects)
                            {
                                i++;
                                continue;
                            }
                            var splittedTypes = ValueSplitter(value);
                            o_exportAssetTypes.Value = new List<ClassIDType>();
                            foreach (var type in splittedTypes)
                            {
                                switch (type.ToLower())
                                {
                                    case "tex2d":
                                        o_exportAssetTypes.Value.Add(ClassIDType.Texture2D);
                                        break;
                                    case "tex2darray":
                                        o_exportAssetTypes.Value.Add(ClassIDType.Texture2DArray);
                                        break;
                                    case "audio":
                                        o_exportAssetTypes.Value.Add(ClassIDType.AudioClip);
                                        break;
                                    case "video":
                                        o_exportAssetTypes.Value.Add(ClassIDType.VideoClip);
                                        break;
                                    case "all":
                                        o_exportAssetTypes.Value = exportableAssetTypes;
                                        break;
                                    default:
                                        var isKnownType = knownAssetTypesDict.TryGetValue(type.ToLower(), out var assetType);
                                        if (isKnownType)
                                        {
                                            if (f_loadAllAssets.Value || exportableAssetTypes.Contains(assetType))
                                            {
                                                o_exportAssetTypes.Value.Add(assetType);
                                                break;
                                            }
                                        }
                                        else
                                        {
                                            Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unknown asset type specified [{type.Color(brightRed)}].\n");
                                            ShowOptionDescription(o_exportAssetTypes);
                                            return;
                                        }
                                        Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Asset type [{type.Color(brightRed)}] is not supported for exporting.\n");
                                        ShowOptionDescription(o_exportAssetTypes);
                                        return;
                                }
                            }
                            break;
                        case "-g":
                        case "--group-option":
                            switch (value.ToLower())
                            {
                                case "type":
                                    o_groupAssetsBy.Value = AssetGroupOption.TypeName;
                                    break;
                                case "container":
                                    o_groupAssetsBy.Value = AssetGroupOption.ContainerPath;
                                    break;
                                case "containerfull":
                                    o_groupAssetsBy.Value = AssetGroupOption.ContainerPathFull;
                                    break;
                                case "filename":
                                    o_groupAssetsBy.Value = AssetGroupOption.SourceFileName;
                                    break;
                                case "scenehierarchy":
                                    o_groupAssetsBy.Value = AssetGroupOption.SceneHierarchy;
                                    break;
                                case "none":
                                    o_groupAssetsBy.Value = AssetGroupOption.None;
                                    break;
                                default:
                                    Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unsupported grouping option: [{value.Color(brightRed)}].\n");
                                    ShowOptionDescription(o_groupAssetsBy);
                                    return;
                            }
                            break;
                        case "-f":
                        case "--filename-format":
                            switch (value.ToLower())
                            {
                                case "assetname":
                                    o_filenameFormat.Value = FilenameFormat.AssetName;
                                    break;
                                case "assetname_pathid":
                                    o_filenameFormat.Value = FilenameFormat.AssetName_PathID;
                                    break;
                                case "pathid":
                                    o_filenameFormat.Value = FilenameFormat.PathID;
                                    break;
                                default:
                                    Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unsupported file name format: [{value.Color(brightRed)}].\n");
                                    ShowOptionDescription(o_filenameFormat);
                                    return;
                            }
                            break;
                        case "-o":
                        case "--output":
                            try
                            {
                                value = Path.GetFullPath(value);
                                if (!Directory.Exists(value))
                                {
                                    Directory.CreateDirectory(value);
                                }
                                if (!value.EndsWith($"{Path.DirectorySeparatorChar}"))
                                {
                                    value += Path.DirectorySeparatorChar;
                                }
                                o_outputFolder.Value = value;
                            }
                            catch (Exception ex)
                            {
                                Console.WriteLine($"{"Warning:".Color(brightYellow)} Invalid output folder \"{value.Color(brightYellow)}\".\n{ex.Message}");
                                Console.WriteLine($"Working folder \"{o_outputFolder.Value.Color(brightYellow)}\" will be used as the output folder.\n");
                                Console.WriteLine("Press ESC to exit or any other key to continue...\n");
                                switch (Console.ReadKey(intercept: true).Key)
                                {
                                    case ConsoleKey.Escape:
                                        return;
                                }
                            }
                            break;
                        case "--log-level":
                            switch (value.ToLower())
                            {
                                case "verbose":
                                    o_logLevel.Value = LoggerEvent.Verbose;
                                    break;
                                case "debug":
                                    o_logLevel.Value = LoggerEvent.Debug;
                                    break;
                                case "info":
                                    o_logLevel.Value = LoggerEvent.Info;
                                    break;
                                case "warning":
                                    o_logLevel.Value = LoggerEvent.Warning;
                                    break;
                                case "error":
                                    o_logLevel.Value = LoggerEvent.Error;
                                    break;
                                default:
                                    Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unsupported log level value: [{value.Color(brightRed)}].\n");
                                    ShowOptionDescription(o_logLevel);
                                    return;
                            }
                            break;
                        case "--log-output":
                            switch (value.ToLower())
                            {
                                case "console":
                                    o_logOutput.Value = LogOutputMode.Console;
                                    break;
                                case "file":
                                    o_logOutput.Value = LogOutputMode.File;
                                    break;
                                case "both":
                                    o_logOutput.Value = LogOutputMode.Both;
                                    break;
                                default:
                                    Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unsupported log output mode: [{value.Color(brightRed)}].\n");
                                    ShowOptionDescription(o_logOutput);
                                    return;
                            }
                            break;
                        case "--image-format":
                            switch (value.ToLower())
                            {
                                case "jpg":
                                case "jpeg":
                                    o_imageFormat.Value = ImageFormat.Jpeg;
                                    break;
                                case "png":
                                    o_imageFormat.Value = ImageFormat.Png;
                                    break;
                                case "bmp":
                                    o_imageFormat.Value = ImageFormat.Bmp;
                                    break;
                                case "tga":
                                    o_imageFormat.Value = ImageFormat.Tga;
                                    break;
                                case "webp":
                                    o_imageFormat.Value = ImageFormat.Webp;
                                    break;
                                case "none":
                                    convertTexture = false;
                                    break;
                                default:
                                    Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unsupported image format: [{value.Color(brightRed)}].\n");
                                    ShowOptionDescription(o_imageFormat);
                                    return;
                            }
                            break;
                        case "--audio-format":
                            switch (value.ToLower())
                            {
                                case "wav":
                                case "wave":
                                    o_audioFormat.Value = AudioFormat.Wav;
                                    break;
                                case "none":
                                    o_audioFormat.Value = AudioFormat.None;
                                    break;
                                default:
                                    Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unsupported audio format: [{value.Color(brightRed)}].\n");
                                    ShowOptionDescription(o_audioFormat);
                                    return;
                            }
                            break;
                        case "--l2d-group-option":
                            if (o_workMode.Value != WorkMode.Live2D)
                            {
                                Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. This option is not suitable for the current working mode [{o_workMode.Value}].\n");
                                ShowOptionDescription(o_workMode);
                                return;
                            }
                            switch (value.ToLower())
                            {
                                case "container":
                                    o_l2dGroupOption.Value = CubismLive2DExtractor.Live2DModelGroupOption.ContainerPath;
                                    break;
                                case "filename":
                                    o_l2dGroupOption.Value = CubismLive2DExtractor.Live2DModelGroupOption.SourceFileName;
                                    break;
                                case "modelname":
                                    o_l2dGroupOption.Value = CubismLive2DExtractor.Live2DModelGroupOption.ModelName;
                                    break;
                                default:
                                    Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unsupported model grouping option: [{value.Color(brightRed)}].\n");
                                    ShowOptionDescription(o_l2dGroupOption);
                                    return;
                            }
                            break;
                        case "--l2d-motion-mode":
                            if (o_workMode.Value != WorkMode.Live2D)
                            {
                                Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. This option is not suitable for the current working mode [{o_workMode.Value}].\n");
                                ShowOptionDescription(o_workMode);
                                return;
                            }
                            switch (value.ToLower())
                            {
                                case "fade":
                                case "monobehaviour":
                                    o_l2dMotionMode.Value = CubismLive2DExtractor.Live2DMotionMode.MonoBehaviour;
                                    break;
                                case "animationclip":
                                case "animationclipv2":
                                    o_l2dMotionMode.Value = CubismLive2DExtractor.Live2DMotionMode.AnimationClipV2;
                                    break;
                                case "animationclipv1":
                                    o_l2dMotionMode.Value = CubismLive2DExtractor.Live2DMotionMode.AnimationClipV1;
                                    break;
                                default:
                                    Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unsupported Live2D motion mode: [{value.Color(brightRed)}].\n");
                                    ShowOptionDescription(o_l2dMotionMode);
                                    return;
                            }
                            break;
                        case "--fbx-scale-factor":
                        {
                            var isFloat = float.TryParse(value, out var floatValue);
                            if (isFloat && floatValue >= 0 && floatValue <= 100)
                            {
                                o_fbxScaleFactor.Value = floatValue;
                            }
                            else
                            {
                                Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unsupported scale factor value: [{value.Color(brightRed)}].\n");
                                ShowOptionDescription(o_fbxScaleFactor);
                                return;
                            }
                            break;
                        }
                        case "--fbx-bone-size":
                        {
                            var isInt = int.TryParse(value, out var intValue);
                            if (isInt && intValue >= 0 && intValue <= 100)
                            {
                                o_fbxBoneSize.Value = intValue;
                            }
                            else
                            {
                                Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unsupported bone size value: [{value.Color(brightRed)}].\n");
                                ShowOptionDescription(o_fbxBoneSize);
                                return;
                            }
                            break;
                        }
                        case "--custom-compression":
                            switch (value.ToLower())
                            {
                                case "zstd":
                                    o_customCompressionType.Value = CustomCompressionType.Zstd;
                                    break;
                                case "lz4":
                                case "lz4hc":
                                    o_customCompressionType.Value = CustomCompressionType.Lz4;
                                    break;
                                default:
                                    Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unsupported compression type: [{value.Color(brightRed)}].\n");
                                    ShowOptionDescription(o_customCompressionType);
                                    return;
                            }
                            break;
                        case "--max-export-tasks":
                        {
                            var processorCount = Environment.ProcessorCount;
                            if (value.ToLower() == "max")
                            {
                                o_maxParallelExportTasks.Value = processorCount - 1;
                            }
                            else
                            {
                                var isInt = int.TryParse(value, out var intValue);
                                if (isInt && intValue >= 0 && intValue <= processorCount)
                                {
                                    o_maxParallelExportTasks.Value = Math.Min(intValue, processorCount - 1);
                                }
                                else
                                {
                                    Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unsupported number of parallel tasks: [{value.Color(brightRed)}].\n");
                                    ShowOptionDescription(o_maxParallelExportTasks);
                                    return;
                                }
                            }
                            break;
                        }
                        case "--export-asset-list":
                            switch (value.ToLower())
                            {
                                case "xml":
                                    o_exportAssetList.Value = ExportListType.XML;
                                    break;
                                case "none":
                                    o_exportAssetList.Value = ExportListType.None;
                                    break;
                                default:
                                    Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Unsupported asset list export option: [{value.Color(brightRed)}].\n");
                                    ShowOptionDescription(o_exportAssetList);
                                    return;
                            }
                            break;
                        case "--filter-by-name":
                            o_filterByName.Value.AddRange(ValueSplitter(value));
                            filterBy = filterBy == FilterBy.None ? FilterBy.Name : filterBy == FilterBy.Container ? FilterBy.NameAndContainer : filterBy;
                            break;
                        case "--filter-by-container":
                            o_filterByContainer.Value.AddRange(ValueSplitter(value));
                            filterBy = filterBy == FilterBy.None ? FilterBy.Container : filterBy == FilterBy.Name ? FilterBy.NameAndContainer : filterBy;
                            break;
                        case "--filter-by-pathid":
                            o_filterByPathID.Value.AddRange(ValueSplitter(value));
                            filterBy = FilterBy.PathID;
                            break;
                        case "--filter-by-text":
                            o_filterByText.Value.AddRange(ValueSplitter(value));
                            filterBy = FilterBy.NameOrContainer;
                            break;
                        case "--assembly-folder":
                            if (Directory.Exists(value))
                            {
                                o_assemblyPath.Value = value;
                                Studio.assemblyLoader.Load(value);
                            }
                            else
                            {
                                Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option. Assembly folder [{value.Color(brightRed)}] was not found.");
                                return;
                            }
                            break;
                        case "--unity-version":
                            try
                            {
                                o_unityVersion.Value = new UnityVersion(value);
                            }
                            catch (Exception e)
                            {
                                Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option.Color(brightYellow)}] option with value [{value.Color(brightRed)}].\n{e.Message}\n");
                                ShowOptionDescription(o_unityVersion);
                                return;
                            }
                            break;
                        default:
                            Console.WriteLine($"{"Error:".Color(brightRed)} Unknown option [{option.Color(brightRed)}].\n");
                            if (!TryFindOptionDescription(option, optionsDict))
                            {
                                TryFindOptionDescription(option, flagsDict, isFlag: true);
                            }
                            return;
                    }
                    i++;
                }
                catch (ArgumentOutOfRangeException)
                {
                    if (optionsDict.Any(x => x.Key.Contains(option)))
                    {
                        Console.WriteLine($"{"Error during parsing options:".Color(brightRed)} Value for [{option.Color(brightYellow)}] option was not found.\n");
                        TryFindOptionDescription(option, optionsDict);
                    }
                    else if (flagsDict.Any(x => x.Key.Contains(option)))
                    {
                        Console.WriteLine($"{"Error:".Color(brightRed)} Unknown flag [{option.Color(brightRed)}].\n");
                        TryFindOptionDescription(option, flagsDict, isFlag: true);
                    }
                    else
                    {
                        Console.WriteLine($"{"Error:".Color(brightRed)} Unknown option [{option.Color(brightRed)}].");
                    }
                    return;
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Unknown Error.".Color(ColorConsole.Red));
                    Console.WriteLine(ex);
                    return;
                }
            }
            #endregion

            if (!Studio.assemblyLoader.Loaded)
            {
                Studio.assemblyLoader.Loaded = true;
            }
            if (o_outputFolder.Value == o_outputFolder.DefaultValue)
            {
                var defaultFolder = o_workMode.Value == WorkMode.Extract
                    ? "ASExtract"
                    : o_outputFolder.DefaultValue;
                var fullPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, defaultFolder + Path.DirectorySeparatorChar);
                if (!Directory.Exists(fullPath))
                {
                    Directory.CreateDirectory(fullPath);
                }
                o_outputFolder.Value = fullPath;
            }
            isParsed = true;
        }

        private static string[] ValueSplitter(string value)
        {
            var separator = value.Contains(';') ? ';' : ',';
            return value.Split(separator);
        }

        private static void ShowOptionDescription<T>(Option<T> option, bool isFlag = false)
        {
            var arg = isFlag ? "Flag" : "Option";
            var optionDesc = option.Description + option.Example.Color(ColorConsole.BrightCyan);
            Console.WriteLine($"{arg} description:\n{optionDesc}");
        }

        private static bool TryFindOptionDescription(string option, Dictionary<string, string> dict, bool isFlag = false)
        {
            var optionDesc = dict.Where(x => x.Key.Contains(option)).ToArray();
            if (optionDesc.Length != 0)
            {
                var arg = isFlag ? "flag" : "option";
                var rand = new Random();
                var rndOption = optionDesc.ElementAt(rand.Next(0, optionDesc.Length));
                Console.WriteLine($"Did you mean [{$"{rndOption.Key}".Color(ColorConsole.BrightYellow)}] {arg}?");
                Console.WriteLine($"Here's a description of it: \n\n{rndOption.Value}");

                return true;
            }
            return false;
        }

        public static void ShowHelp(bool showUsageOnly = false)
        {
            const int indent = 22;
            var helpMessage = new StringBuilder();
            var usage = new StringBuilder();
            var appAssembly = typeof(Program).Assembly.GetName();
            usage.Append($"{"Usage:".Color(ColorConsole.BrightYellow)} {appAssembly.Name} <input path to asset file/folder> ");

            var i = 0;
            foreach (var optionsGroup in optionGroups.Keys)
            {
                helpMessage.AppendLine($"{optionsGroup} Options:".Color(ColorConsole.BrightYellow));
                foreach (var optionDict in optionGroups[optionsGroup])
                {
                    var optionName = $"{optionDict.Key,-indent - 8}";
                    var optionDesc = optionDict.Value.Replace("\n", $"{"\n",-indent - 11}");
                    helpMessage.AppendLine($"  {optionName}{optionDesc}");

                    usage.Append($"[{optionDict.Key}] ");
                    if (i++ % 2 == 0)
                    {
                        usage.Append($"\n{"",indent}");
                    }
                }
                helpMessage.AppendLine();
            }

            if (showUsageOnly)
            {
                Console.WriteLine(usage);
            }
            else
            {
                var arch = Environment.Is64BitProcess ? "x64" : "x32";
                Console.WriteLine($"# {appAssembly.Name} [{arch}]\n# Based on AssetStudioMod v{appAssembly.Version}\n");
                Console.WriteLine($"{usage}\n\n{helpMessage}");
            }
        }

        private static string ShowCurrentFilter()
        {
            switch (filterBy)
            {
                case FilterBy.Name:
                    return $"# Filter by {filterBy}(s): \"{string.Join("\", \"", o_filterByName.Value)}\"";
                case FilterBy.Container:
                    return $"# Filter by {filterBy}(s): \"{string.Join("\", \"", o_filterByContainer.Value)}\"";
                case FilterBy.PathID:
                    return $"# Filter by {filterBy}(s): \"{string.Join("\", \"", o_filterByPathID.Value)}\"";
                case FilterBy.NameOrContainer:
                    return $"# Filter by Text: \"{string.Join("\", \"", o_filterByText.Value)}\"";
                case FilterBy.NameAndContainer:
                    return $"# Filter by Name(s): \"{string.Join("\", \"", o_filterByName.Value)}\"\n# Filter by Container(s): \"{string.Join("\", \"", o_filterByContainer.Value)}\"";
                default:
                    return $"# Filter by: {filterBy}";
            }
        }

        private static string ShowExportTypes()
        {
            switch (o_workMode.Value)
            {
                case WorkMode.ExportRaw:
                case WorkMode.Dump:
                case WorkMode.Info:
                    return f_loadAllAssets.Value && o_exportAssetTypes.Value == o_exportAssetTypes.DefaultValue
                        ? $"# Export Asset Type(s): All"
                        : $"# Export Asset Type(s): {string.Join(", ", o_exportAssetTypes.Value)}";
                default:
                    return $"# Export Asset Type(s): {string.Join(", ", o_exportAssetTypes.Value)}";
            }
        }

        public static void ShowCurrentOptions()
        {
            var unityVer = o_unityVersion.Value?.ToString();
            unityVer = string.IsNullOrEmpty(unityVer) ? "ReadFromAsset" : unityVer;
            
            var sb = new StringBuilder();
            sb.AppendLine("[Current Options]");
            sb.AppendLine($"# Working Mode: {o_workMode}");
            if (o_customCompressionType.Value != o_customCompressionType.DefaultValue)
            {
                sb.AppendLine($"# Custom Compression Type: {o_customCompressionType}");
            }
            if (o_workMode.Value != WorkMode.Extract)
            {
                sb.AppendLine($"# Parse Assets Using TypeTree: {!f_avoidLoadingViaTypetree.Value}");
            }
            sb.AppendLine($"# Input Path: \"{inputPath}\"");
            if (o_workMode.Value != WorkMode.Info)
            {
                sb.AppendLine($"# Output Path: \"{o_outputFolder}\"");
            }
            switch (o_workMode.Value)
            {
                case WorkMode.Export:
                case WorkMode.ExportRaw:
                case WorkMode.Dump:
                    if (o_workMode.Value != WorkMode.Export)
                    {
                        sb.AppendLine($"# Load All Assets: {f_loadAllAssets}");
                    }
                    sb.AppendLine(ShowExportTypes());
                    sb.AppendLine($"# Asset Group Option: {o_groupAssetsBy}");
                    sb.AppendLine($"# Filename format: {o_filenameFormat}");
                    if (o_workMode.Value == WorkMode.Export)
                    {
                        sb.AppendLine($"# Export Image Format: {o_imageFormat}");
                        sb.AppendLine($"# Export Audio Format: {o_audioFormat}");
                    }
                    sb.AppendLine($"# Log Level: {o_logLevel}");
                    sb.AppendLine($"# Log Output: {o_logOutput}");
                    sb.AppendLine($"# Export Asset List: {o_exportAssetList}");
                    sb.AppendLine(ShowCurrentFilter());
                    sb.AppendLine($"# Assembly Path: \"{o_assemblyPath}\"");
                    sb.AppendLine($"# Unity Version: {unityVer}");
                    if (o_workMode.Value == WorkMode.Export)
                    {
                        sb.AppendLine($"# Max Parallel Export Tasks: {o_maxParallelExportTasks}");
                        sb.AppendLine($"# Restore TextAsset Extension: {!f_notRestoreExtensionName.Value}");
                    }
                    break;
                case WorkMode.Info:
                    sb.AppendLine($"# Load All Assets: {f_loadAllAssets}");
                    sb.AppendLine(ShowExportTypes());
                    sb.AppendLine($"# Log Level: {o_logLevel}");
                    sb.AppendLine($"# Log Output: {o_logOutput}");
                    sb.AppendLine($"# Export Asset List: {o_exportAssetList}");
                    sb.AppendLine(ShowCurrentFilter());
                    sb.AppendLine($"# Unity Version: {unityVer}");
                    break;
                case WorkMode.Live2D:
                case WorkMode.SplitObjects:
                    sb.AppendLine($"# Log Level: {o_logLevel}");
                    sb.AppendLine($"# Log Output: {o_logOutput}");
                    sb.AppendLine($"# Export Asset List: {o_exportAssetList}");
                    if (o_workMode.Value == WorkMode.SplitObjects)
                    {
                        sb.AppendLine($"# Export Image Format: {o_imageFormat}");
                        sb.AppendLine($"# Filter by Name(s): \"{string.Join("\", \"", o_filterByName.Value)}\"");
                        sb.AppendLine($"# FBX Scale Factor: {o_fbxScaleFactor}");
                        sb.AppendLine($"# FBX Bone Size: {o_fbxBoneSize}");
                        sb.AppendLine($"# FBX UVs as Diffuse Maps: {f_fbxUvsAsDiffuseMaps}");
                    }
                    else
                    {
                        sb.AppendLine($"# Model Group Option: {o_l2dGroupOption}");
                        sb.AppendFormat("# Search Model-related Assets by: {0}\n", f_l2dAssetSearchByFilename.Value ? "FileName" : "Container");
                        sb.AppendLine($"# Motion Export Method: {o_l2dMotionMode}");
                        sb.AppendLine($"# Force Bezier: {f_l2dForceBezier }");
                        sb.AppendLine($"# Assembly Path: \"{o_assemblyPath}\"");
                    }
                    sb.AppendLine($"# Unity Version: {unityVer}");
                    break;
            }
            sb.AppendLine("======");
            Logger.Info(sb.ToString());
        }
    }
}