using anbs_cp.Classes;

using FileSplitter.Shared.Classes;
using FileSplitter.Shared.Enums;

using static System.Console;

namespace FileSplitter.Console.Classes;

/// <summary>
/// Класс для выполнения разбиения, сборки, интерактивного режима
/// </summary>
internal static class Executor
{
    /// <summary>
    /// Вывод прогресса
    /// </summary>
    /// <param name="sender">Вызывающий объект</param>
    /// <param name="args"><see cref="FileSplitterOnProgress"/></param>
    private static void OnProgress (object sender, FileSplitterOnProgress args)
    {
        //Вывод сообщения в консоль
        WriteLine(args.Message);
    }

    /// <summary>
    /// Получение параметров разбиения
    /// </summary>
    /// <param name="params">Парсер параметров</param>
    /// <returns>Список параметров <see cref="FileSplitOptions"/></returns>
    public static List<FileSplitOptions> GetSplitOptions (ConsoleParamsParser @params)
    {
        //Создаю результат
        List<FileSplitOptions> result = new();

        //Проверяю параметры разбиения
        //- отключена ли проверка хэша
        if (@params.HasParam("/NoCheckHash"))
            result.Add(FileSplitOptions.fsoNoCheckHash);
        //- нужно ли удалять исходный файл
        if (@params.HasParam("/DeleteSource"))
            result.Add(FileSplitOptions.fsoDeleteSourceFileAfterSplit);
        //- нужно ли шифровать файл информации
        if (@params.HasParam("/EncryptInfoFile"))
            result.Add(FileSplitOptions.fsoEncryptInfoFile);

        //Вывожу результат
        return result;
    }

    /// <summary>
    /// Получение параметров разбиения
    /// </summary>
    /// <param name="params">Массив параметров</param>
    /// <returns>Список параметров <see cref="FileSplitOptions"/></returns>
    public static List<FileSplitOptions> GetSplitOptions (string[] @params)
    {
        ConsoleParamsParser parser = new(@params);
        return GetSplitOptions(parser);
    }

    /// <summary>
    /// Получение параметров соединения
    /// </summary>
    /// <param name="params">Парсер параметров</param>
    /// <returns>Список параметров <see cref="FileJoinOptions"/></returns>
    public static List<FileJoinOptions> GetJoinOptions (ConsoleParamsParser @params)
    {
        //Создаю результат
        List<FileJoinOptions> result = new();

        //Проверяю параметры разбиения
        //- отключена ли проверка хэша
        if (@params.HasParam("/NoCheckHash"))
            result.Add(FileJoinOptions.fjoNoCheckHash);
        //- нужно ли удалять исходные файлы
        if (@params.HasParam("/DeleteSource"))
            result.Add(FileJoinOptions.fjoDeleteSourceFilesAfterJoin);
        //- нужно ли дешифровать файл информации
        if (@params.HasParam("/DecryptInfoFile"))
            result.Add(FileJoinOptions.fjoInfoFileIsEncrypted);
        //- нужно ли отключить проверку версии файла информации
        if (@params.HasParam("/IgnoreVersion"))
            result.Add(FileJoinOptions.fjoIgnoreSupportedScriptVersion);
        //- нужно ли отключить проверку объединённого файла на корректность
        if (@params.HasParam("/SkipCheck"))
            result.Add(FileJoinOptions.fjoSkipCheckJoinedFile);

        //Вывожу результат
        return result;
    }

    /// <summary>
    /// Получение параметров соединения
    /// </summary>
    /// <param name="params">Массив параметров</param>
    /// <returns>Список параметров <see cref="FileJoinOptions"/></returns>
    public static List<FileJoinOptions> GetJoinOptions (string[] @params)
    {
        ConsoleParamsParser parser = new(@params);
        return GetJoinOptions(parser);
    }

    /// <summary>
    /// Выполняю разбиение
    /// </summary>
    /// <param name="fileName">Имя файла</param>
    /// <param name="targetDir">Целевая папка с файлами разбиения</param>
    /// <param name="partSize">Размер части</param>
    /// <param name="silent">Тихий режим (без запросов)</param>
    /// <param name="options">Параметры разбиения</param>
    /// <returns>Код возврата</returns>
    public static FsExitCode DoSplit (string fileName, string targetDir, long partSize, bool silent, List<FileSplitOptions> options)
    {
        //Функция прогресса
#pragma warning disable CS8622
        FileSplitterClass.OnProgress += OnProgress;
#pragma warning restore CS8622

        //Если не выбран тихий режим
        if (!silent)
        {
            //- получаю информацию о разбиении
            TargetFileInfo fileInfo = FileSplitterClass.GetSplitFileInfo(fileName, partSize);

            //- спрашиваю пользователя
            WriteLine(Localization.MessageSplitAsk, fileInfo.FileName,
                new FileSizeConverter(Localization.FileSizeStrings).Convert(fileInfo.FileSize),
                BitConverter.ToString(fileInfo.FileHash).Replace("-", "").ToLowerInvariant(),
                fileInfo.SplitCount);

            //- получаю ответ
            string userInput = ReadInput("");

            //- проверяю согласие
            if (userInput != "y")
                //-- и прерываю, если не получил
                return FsExitCode.ecUserAborted;
        }

        //Выполняю разбиение
        return FileSplitterClass.SplitFile(fileName, targetDir, partSize, options.AsReadOnly());
    }

    /// <summary>
    /// Выполняю сборку
    /// </summary>
    /// <param name="infoFileName">Файл информации о разбиении</param>
    /// <param name="targetFileName">Целевой файл</param>
    /// <param name="silent">Тихий режим (без запросов)</param>
    /// <param name="options">Параметры сборки</param>
    /// <returns>Код возврата</returns>
    public static FsExitCode DoJoin (string infoFileName, string targetFileName, bool silent, List<FileJoinOptions> options)
    {
        //Функция прогресса
#pragma warning disable CS8622
        FileSplitterClass.OnProgress += OnProgress;
#pragma warning restore CS8622

        //Если не выбран тихий режим
        if (!silent)
        {
            //- спрашиваю пользователя
            WriteLine(Localization.MessageJoinAsk, targetFileName);

            //- получаю ответ
            string userInput = ReadInput("");

            //- проверяю согласие
            if (userInput != "y")
                //-- и прерываю, если не получил
                return FsExitCode.ecUserAborted;
        }

        //Выполняю объединение
        return FileSplitterClass.JoinFile(infoFileName, targetFileName, options.AsReadOnly());
    }

    public static FsExitCode DoInteractive ()
    {
        //Пользовательский ввод
        string userInput;

        //Цикл получения информации
        do
        {
            //Вывожу приветственный запрос на выбор операции
            WriteLine(Localization.InteractiveStartAsk);

            //Получаю ответ
            userInput = ReadInput(string.Empty);

            //Обработано ли решение
            bool handled = false;

            //Обрабатываем выбор пользователя
            switch (userInput)
            {
                //Если пользователь выбрал разбиение
                case "s":
                    {
                        //- то запускаем разбиение
                        FsExitCode splitExitCode = DoSplitInteractive();

                        //- вывожу сообщение
                        WriteLine(Localization.InteractiveOperationFinish, Localization.ExitCodeNames.First(eCode => eCode.Item1 == splitExitCode).Item2);

                        //- указываем на перехват
                        handled = true;
                        break;
                    }

                //Если пользователь выбрал соединение
                case "j":
                    {
                        //- то запускаем соединение
                        FsExitCode joinExitCode = DoJoinInteractive();

                        //- вывожу сообщение
                        WriteLine(Localization.InteractiveOperationFinish, Localization.ExitCodeNames.First(eCode => eCode.Item1 == joinExitCode).Item2);

                        //- указываем на перехват
                        handled = true;
                        break;
                    }

                //Если пользователь выбрал опции
                case "o":
                    {
                        //- то запускаем опции
                        FsExitCode optionsExitCode = DoOptionsInteractive();

                        //- вывожу сообщение
                        WriteLine(Localization.InteractiveOperationFinish, Localization.ExitCodeNames.First(eCode => eCode.Item1 == optionsExitCode).Item2);

                        //- указываем на перехват
                        handled = true;
                        break;
                    }

                //Если пользователь указал на выход
                case "e":
                    //- то указываем на перехват
                    handled = true;
                    break;
            }

            //Если решение не обработано
            if (!handled)
                //- то вывожу сообщение
                WriteLine(Localization.InteractiveWrongInput);
        } while (userInput.ToLower() != "e");

        //Закрываю интерактивную операцию
        return FsExitCode.ecAllDone;
    }

    /// <summary>
    /// Интерактивное разбиение файла
    /// </summary>
    /// <returns><see cref="FsExitCode"/></returns>
    public static FsExitCode DoSplitInteractive ()
    {
        //Класс локализации
        string userInput;

        //Обработан ли ввод
        bool isHandled = false;

        /* Имя файла */
        //Выбор имени файла
        do
        {
            //Запрос имени файла
            WriteLine(Localization.InteractiveSplitFileNameAsk);

            //Считываю имя файла
            userInput = ReadInput(string.Empty);

            //Если файл не существует
            if (!File.Exists(userInput))
            {
                //- то запрашиваю действие
                WriteLine(Localization.InteractiveFileNameNotExist);

                //- считываю ответ
                userInput = ReadInput(string.Empty);

                //- если выбрано не повторять
                if (userInput.ToLower() != "y")
                    //-- то прерываю операцию
                    return FsExitCode.ecUserAborted;
            }
            else
            {
                //В противном случае, считаю ввод корректным
                isHandled = true;
            }
        } while (!isHandled);

        //Ввожу переменную с именем файла
        string fileName = userInput;

        //Вывожу информацию о выборе файла
        WriteLine(Localization.InteractiveSplitFileNameResult, fileName);

        /* Целевая папка */

        //Выбираю целевую папку
        WriteLine(Localization.InteractiveSplitTargetDirAsk);

        //Считываю данные
        userInput = ReadInput(string.Empty);

        //Если вместо папки пустое место
        if (string.IsNullOrWhiteSpace(userInput))
            //- то задаю папку имени файла
            userInput = Path.GetDirectoryName(fileName) ?? string.Empty;

        //Ввожу переменную целевой папки
        string targetDir = LikeDelphi.IncludeTrailingBackslash(userInput);

        //Вывожу информацию о выборе целевой папки
        WriteLine(Localization.InteractiveSplitTargetDirResult, targetDir);

        /* Размер части */
        //Ввожу переменную размера части
        long partSize;

        //Создаю конвертер
        FileSizeConverter converter = new(Localization.FileSizeStrings, 1);

        //Устанавливаю перехват в false
        isHandled = false;

        //Выбор размер части
        do
        {
            //Получаю размер исходного файла
            long fileSize = FileExtension.FileSize(fileName);

            //Запрос размера части
            WriteLine(Localization.InteractiveSplitPartSizeAsk);

            //Считываю размер части
            partSize = ReadInput(fileSize);

            //Запрашиваю согласие на размер части
            WriteLine(Localization.InteractiveSplitPartSizeAsk2, converter.Convert(partSize));

            //Считываю ответ
            userInput = ReadInput(string.Empty);

            //Если согласен, то выхожу из цикла
            if (userInput.ToLower() == "y")
                isHandled = true;
        } while (!isHandled);

        //Вывожу информацию о размере части
        WriteLine(Localization.InteractiveSplitPartSizeResult, converter.Convert(partSize));

        /* Опции разбиения */
        //Запрос опций разбиения
        WriteLine(Localization.InteractiveSplitOptionsAsk);

        //Считываю опции разбиения
        userInput = ReadInput(string.Empty);

        //Ввожу переменную опций разбиения
        List<FileSplitOptions> options = GetSplitOptions(userInput.Split(' '));

        //Вывожу информацию о выборе параметров
        WriteLine(Localization.InteractiveSplitOptionsResult, userInput);

        /* Выполнение разбиения */
        //Вывожу информацию о начале работы
        WriteLine(Localization.MessagePleaseWaitToReady);

        //Выполняю операцию
        return DoSplit(fileName, targetDir, partSize, false, options);
    }

    /// <summary>
    /// Интерактивное соединение файла
    /// </summary>
    /// <returns><see cref="FsExitCode"/></returns>
    public static FsExitCode DoJoinInteractive ()
    {
        //Класс локализации
        string userInput;

        //Обработан ли ввод
        bool isHandled = false;

        /* Имя файла информации о разбиении */
        //Выбор имени файла
        do
        {
            //Запрос имени файла
            WriteLine(Localization.InteractiveJoinFileNameAsk);

            //Считываю имя файла
            userInput = ReadInput(string.Empty);

            //Если файл не существует
            if (!File.Exists(userInput))
            {
                //- то запрашиваю действие
                WriteLine(Localization.InteractiveFileNameNotExist);

                //- считываю ответ
                userInput = ReadInput(string.Empty);

                //- если выбрано не повторять
                if (userInput.ToLower() != "y")
                    //-- то прерываю операцию
                    return FsExitCode.ecUserAborted;
            }
            else
            {
                //В противном случае, считаю ввод корректным
                isHandled = true;
            }
        } while (!isHandled);

        //Ввожу переменную с именем файла
        string fileName = userInput;

        //Вывожу информацию о выборе файла
        WriteLine(Localization.InteractiveJoinFileNameResult, fileName);

        /* Целевой файл */

        //Выбор целевого файла
        do
        {
            //Запрос имени файла
            WriteLine(Localization.InteractiveJoinTargetFileNameAsk);

            //Считываю имя файла
            userInput = ReadInput(string.Empty);

            //Если файл существует
            if (File.Exists(userInput))
            {
                //- то запрашиваю действие
                WriteLine(Localization.InteractiveFileNameExist);

                //- считываю ответ
                userInput = ReadInput(string.Empty);

                //- если выбрано не повторять
                if (userInput.ToLower() != "y")
                    //-- то прерываю операцию
                    return FsExitCode.ecUserAborted;
            }
            else
            {
                //В противном случае, считаю ввод корректным
                isHandled = true;
            }
        } while (!isHandled);

        //Ввожу переменную с именем файла
        string targetFileName = userInput;

        //Вывожу информацию о выборе целевого файла
        WriteLine(Localization.InteractiveJoinTargetFileNameResult, fileName);

        /* Опции соединения */
        //Запрос опций соединения
        WriteLine(Localization.InteractiveJoinOptionsAsk);

        //Считываю опции соединения
        userInput = ReadInput(string.Empty);

        //Ввожу переменную опций соединения
        List<FileJoinOptions> options = GetJoinOptions(userInput.Split(' '));

        //Вывожу информацию о выборе параметров
        WriteLine(Localization.InteractiveJoinOptionsResult, userInput);

        /* Выполнение соединения */
        //Вывожу информацию о начале работы
        WriteLine(Localization.MessagePleaseWaitToReady);

        //Выполняю операцию
        return DoJoin(fileName, targetFileName, false, options);
    }

    /// <summary>
    /// Интерактивные опции
    /// </summary>
    /// <returns><see cref="FsExitCode"/></returns>
    public static FsExitCode DoOptionsInteractive ()
    {
        //Класс локализации
        FileSplitterConsoleOptions options = new();

        //Загружаю опции
        options.Load();

        //Вывожу запрос на изменение настроек
        WriteLine(Localization.InteractiveOptionsAsk);

        //Считываю опции
        string userInput = ReadInput(string.Empty);

        //Создаю парсер опций
        ConsoleParamsParser parser = new(userInput.Split(' '));

        //Сохраняю опции
        //- язык
        if (parser.HasParam("language"))
        {
            //-- получаю значение языка
            string language = (parser.GetValue("language") ?? "russian").Trim(new []{'"'});

            //-- и если файл существует
            if (File.Exists($"languages\\{language}.json"))
                //--- то устанавливаю его
                options.Language = language;
        }

        //Вывожу запрос на подтверждение
        WriteLine(Localization.InteractiveOptionsConfirm, userInput);

        //Считываю опции
        userInput = ReadInput(string.Empty);

        //Если не подтверждено
        if (userInput != "y")
            //- то прерываю
            return FsExitCode.ecUserAborted;

        //Сохраняю опции
        options.Save();

        //Вывожу информацию об успешном сохранении
        WriteLine(Localization.InteractiveOptionsResult);

        //Вывожу результат
        return FsExitCode.ecAllDone;
    }

    /// <summary>
    /// Чтение строкового значения
    /// </summary>
    /// <param name="defaultValue">Значение по умолчанию</param>
    /// <returns>Ввод пользователя в формате <see cref="string"/></returns>
    private static string ReadInput (string defaultValue = "") => (ReadLine() ?? defaultValue).Trim(new[] { '"' }).ToLower();

    /// <summary>
    /// Чтение численного значения
    /// </summary>
    /// <param name="defaultValue">Значение по умолчанию</param>
    /// <returns>Ввод пользователя в формате <see cref="long"/></returns>
    private static long ReadInput (long defaultValue = 0) => TypeConverter.StrToInt64(ReadLine() ?? defaultValue.ToString(), defaultValue);
}