using System;

using System.Collections.Generic;

namespace Wayne.Lib
{
    /// <summary>
    /// Argument support for parameters passed to the application's Main method.
    /// It handles either flags, such as -a /a or just a.
    /// Or it handles arguments on the form key=value.
    /// </summary>
    public class AppArgSupport
    {
        private readonly Dictionary<string, string> argDict = new Dictionary<string, string>();
        private readonly List<string> flagList = new List<string>();

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="args"></param>
        public AppArgSupport(string[] args)
        {
            foreach (string arg in args)
            {
                int equalSignIndex = arg.IndexOf('=');
                if (equalSignIndex > -1)
                    argDict[arg.Substring(0, equalSignIndex).ToUpper()] = arg.Substring(equalSignIndex + 1);
                else
                {
                    string flagName = arg;
                    if (!flagList.Exists(s => s.Equals(flagName, StringComparison.CurrentCultureIgnoreCase)))
                        flagList.Add(arg);
                }
            }
        }

        /// <summary>
        /// Checks if the specified flag exists
        /// </summary>
        /// <param name="flag"></param>
        /// <returns></returns>
        public bool HasFlag(string flag)
        {
            return flagList.Exists(s => s.Equals(flag, StringComparison.CurrentCultureIgnoreCase));
        }

        /// <summary>
        /// Tries to get the specified parameter.
        /// </summary>
        /// <param name="name">name of the parameter</param>
        /// <param name="value">value of the parameter</param>
        /// <returns>True if the parameter exists</returns>
        public bool TryGetArgStringValue(string name, out string value)
        {
            return argDict.TryGetValue(name.ToUpper(), out value);
        }

        /// <summary>
        /// Gets the argument value as a string
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public string GetArgStringValue(string name)
        {
            string value;
            if (argDict.TryGetValue(name.ToUpper(), out value))
                return value;
            throw new ArgumentException(string.Concat("Missing argument \"", name, "\""));
        }

        /// <summary>
        /// Gets the argument value, providing a default value to use in case the parameter is not specified.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        public string GetArgStringValue(string name, string defaultValue)
        {
            string value;
            if (!argDict.TryGetValue(name.ToUpper(), out value))
                value = defaultValue;
            return value;
        }

        /// <summary>
        /// Tries to get an integer argument value.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns>True if success.</returns>
        public bool TryGetArgIntValue(string name, out int value)
        {
            try
            {
                string stringValue;
                if (TryGetArgStringValue(name, out stringValue))
                {
                    value = int.Parse(stringValue);
                    return true;
                }
            }
            catch (Exception) { }
            value = 0;
            return false;
        }

        /// <summary>
        /// Gets an integer argument value 
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentException">If value does not exist</exception>
        /// <exception cref="ArgumentException">If value is not integer</exception>
        public int GetArgIntValue(string name)
        {
            string stringValue = GetArgStringValue(name);
            try
            {
                return int.Parse(stringValue);
            }
            catch (Exception)
            {
                throw new ArgumentException(string.Concat("Bad int-value in argument \"", name, "\": \"", stringValue, "\""));
            }
        }

        /// <summary>
        /// Gets an integer argument value if exists, otherwise returns the default value.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        public int GetArgIntValue(string name, int defaultValue)
        {
            try
            {
                string value = GetArgStringValue(name, defaultValue.ToString());
                return int.Parse(value);
            }
            catch (Exception)
            {
                return defaultValue;
            }
        }

        /// <summary>
        /// Gets an enumeration argument value.
        /// </summary>
        /// <typeparam name="TEnum"></typeparam>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns>True if success.</returns>
        public bool TryGetArgEnumValue<TEnum>(string name, out TEnum value) where TEnum : struct
        {
            try
            {
                string stringValue;
                if (TryGetArgStringValue(name, out stringValue))
                {
                    value = (TEnum)Enum.Parse(typeof(TEnum), stringValue, true);
                    return true;
                }
            }
            catch (Exception) { }
            value = default(TEnum);
            return false;
        }

        /// <summary>
        /// Gets an enumeration argument value.
        /// </summary>
        /// <typeparam name="TEnum"></typeparam>
        /// <param name="name"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentException">If value does not exist</exception>
        /// <exception cref="ArgumentException">If value is not one of the enumerated values</exception>
        public TEnum GetArgEnumValue<TEnum>(string name)
        {
            string stringValue = GetArgStringValue(name);
            try
            {
                return (TEnum)Enum.Parse(typeof(TEnum), stringValue, true);
            }
            catch (Exception)
            {
                throw new ArgumentException(string.Concat("Bad enum-value in argument \"", name, "\": \"", stringValue, "\""));
            }
        }

        /// <summary>
        /// Gets an enumeration argument value if exists, otherwise returns the default value.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        public TEnum GetArgEnumValue<TEnum>(string name, TEnum defaultValue)
        {
            try
            {
                string stringValue = GetArgStringValue(name, defaultValue.ToString());
                return EnumSupport.Parse(stringValue, true, defaultValue);
            }
            catch (Exception)
            {
                return defaultValue;
            }
        }
    }
}