using System.Collections;
using System.ComponentModel;
using System.Reflection;

namespace Nerfed.Builder;


public class ArgsParser<TArgs> where TArgs : new()
{
    private enum ArgType
    {
        None,
        Key,
        Value
    }

    public TArgs Arguments { get; }

    private readonly string[] args;
    private readonly Dictionary<string, PropertyInfo> argKeyPropertyMap = new Dictionary<string, PropertyInfo>();

    public ArgsParser(string[] args)
    {
        this.args = args;
        Arguments = new TArgs();
        PropertyInfo[] properties = Arguments.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
        foreach (PropertyInfo property in properties)
        {
            ArgumentAttribute argAttribute = property.GetCustomAttribute<ArgumentAttribute>();
            if (argAttribute == null || string.IsNullOrEmpty(argAttribute.ArgKey))
            {
                continue;
            }

            argKeyPropertyMap.Add(argAttribute.ArgKey, property);
        }
    }

    public bool Parse()
    {
        PropertyInfo property = null;
        ArgType lastArgType = ArgType.None;

        for (int i = 0; i < args.Length; i++)
        {
            string arg = args[i];
            if (arg[0] == '-')
            {
                if (!argKeyPropertyMap.TryGetValue(arg, out property))
                {
                    Console.Error.WriteLine($"Invalid argument: {arg}, no such argument key exists");
                    return false;
                }

                // Boolean arguments require no value, set to true immidiately.
                if (property.PropertyType == typeof(bool))
                {
                    property.SetValue(Arguments, true);
                    lastArgType = ArgType.Value;
                }
                else
                {
                    lastArgType = ArgType.Key;
                }
            }
            else
            {
                if (lastArgType == ArgType.None)
                {
                    Console.Error.WriteLine($"Invalid argument: {arg}, no argument key was provided");
                    return false;
                }

                Type propertyType = property.PropertyType;
                if (propertyType.IsArray)
                {
                    throw new InvalidOperationException("Arrays are not supported, use List<T> instead");
                }

                bool propertyTypeIsList = propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(List<>);
                if (propertyTypeIsList)
                {
                    propertyType = propertyType.GenericTypeArguments[0];
                }

                TypeConverter typeConverter = TypeDescriptor.GetConverter(propertyType);
                object value = typeConverter.ConvertFromString(arg);

                if (value is string stringValue)
                {
                    if (!string.IsNullOrEmpty(stringValue))
                    {
                        if (stringValue[0] == '"')
                        {
                            stringValue = stringValue.Substring(1, stringValue.Length - 1);
                        }

                        if (stringValue[^1] == '"')
                        {
                            stringValue = stringValue.Substring(0, stringValue.Length - 1);
                        }

                        value = stringValue;
                    }
                }

                if (propertyTypeIsList)
                {
                    IList list = (IList)property.GetValue(Arguments);
                    if (list == null)
                    {
                        list = (IList)Activator.CreateInstance(property.PropertyType);
                        property.SetValue(Arguments, list);
                    }

                    list.Add(value);
                }
                else
                {
                    property.SetValue(Arguments, value);
                }

                lastArgType = ArgType.Value;
            }
        }

        return true;
    }
}
