/******************************************************************************/
/* SlunkCrypt, by LoRd_MuldeR <MuldeR2@GMX.de>                                */
/* This work has been released under the CC0 1.0 Universal license!           */
/******************************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Threading;

using com.muldersoft.slunkcrypt.gui.utils;

namespace com.muldersoft.slunkcrypt.gui.process
{
    internal class SlunkCryptRunner : ProcessRunner
    {
        public enum Mode { Encrypt, Decrypt }

        public struct SlunkOptions
        {
            public SlunkOptions(bool keepIncompleteFiles, int threadCount, bool enableLegacyCompat)
            {
                this.keepIncompleteFiles = keepIncompleteFiles;
                this.threadCount = threadCount;
                this.enableLegacyCompat = enableLegacyCompat;
            }
            public readonly bool keepIncompleteFiles, enableLegacyCompat;
            public readonly int threadCount;
        }

        private const string COMMAND_ENCRYPT = "-e";
        private const string COMMAND_DECRYPT = "-d";

#if DEBUG
        private const bool ENABLE_DEBUG_LOGGING = true;
#else
        private const bool ENABLE_DEBUG_LOGGING = false;
#endif

        private static readonly Regex RX_PROGRESS = new Regex(@"(\d+)\.(\d)%", RegexOptions.Compiled);

        private readonly FileStream m_executableFile;

        // =============================================================================
        // Constructor
        // =============================================================================

        public SlunkCryptRunner(Dispatcher dispatcher, ProcessPriorityClass? priorityClass = null) : base(dispatcher, priorityClass)
        {
            m_executableFile = ExecutableHelper.GetExecutableFile();
        }

        // =============================================================================
        // Public methods
        // =============================================================================

        public async Task<int> ExecuteAsync(Mode mode, string inputFile, string outputFile, string password, SlunkOptions? options = null)
        {
            if ((!EnumHelper.IsDefined(mode)) || string.IsNullOrWhiteSpace(inputFile) || string.IsNullOrWhiteSpace(outputFile) || string.IsNullOrWhiteSpace(password))
            {
                throw new ArgumentException("Invalid SlunkCrypt parameters!");
            }
            Dictionary<string, string> environmentVariables = new Dictionary<string, string>();
            environmentVariables.Add("SLUNK_PASSPHRASE", password);
            environmentVariables.Add("SLUNK_KEEP_INCOMPLETE", Convert.ToString(Convert.ToInt32(options.HasValue ? options.Value.keepIncompleteFiles : false)));
            environmentVariables.Add("SLUNK_THREADS", Convert.ToString(Math.Max(0, Math.Min(32, options.HasValue ? options.Value.threadCount : 0))));
            environmentVariables.Add("SLUNK_LEGACY_COMPAT", Convert.ToString(Convert.ToInt32(options.HasValue ? options.Value.enableLegacyCompat : false)));
            environmentVariables.Add("SLUNK_DEBUG_LOGGING", Convert.ToString(Convert.ToInt32(ENABLE_DEBUG_LOGGING)));
            return await ExecAsnyc(m_executableFile.Name, new string[] { GetCommandString(mode), inputFile, outputFile }, Path.GetDirectoryName(outputFile), environmentVariables);
        }

        public override void Dispose()
        {
            base.Dispose();
            try
            {
                m_executableFile.Dispose();
            }
            catch { }
        }

        // =============================================================================
        // Internal methods
        // =============================================================================

        protected override double ParseProgressString(ref string currentLine, bool stream)
        {
            double temp, result = double.NaN;
            int index = int.MaxValue;
            Match match = RX_PROGRESS.Match(currentLine);
            while (match.Success)
            {
                if (TryParseProgressValue(match, out temp))
                {
                    result = temp;
                }
                index = Math.Min(index, match.Index);
                match = RX_PROGRESS.Match(currentLine, match.Index + match.Length);
            }
            if (index != int.MaxValue)
            {
                currentLine = (index > 0) ? currentLine.Substring(0, index - 1).TrimEnd() : string.Empty;
            }
            return result;
        }

        private static bool TryParseProgressValue(Match match, out double progress)
        {
            uint intPart, fractPart;
            if (uint.TryParse(match.Groups[1].Value, out intPart) && uint.TryParse(match.Groups[2].Value, out fractPart))
            {
                progress = ((intPart * 10) + fractPart) / 1000.0;
                return true;
            }
            progress = double.NaN;
            return false;
        }

        private static string GetCommandString(Mode mode)
        {
            switch(mode)
            {
                case Mode.Encrypt:
                    return COMMAND_ENCRYPT;
                case Mode.Decrypt:
                    return COMMAND_DECRYPT;
                default:
                    throw new ArgumentException("Invalid mode!");
            }
        }
    }
}