// Created with MonoDevelop
//
//    Copyright (C) 2018 Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.


using System;
using System.Collections.Generic;
using System.Reflection;

using Hiperscan.Unix;


namespace Hiperscan.Common
{
    public class Option : Option<object>
    {
        public Option()
        {
            this.HasParameter = false;
        }
    }

    public class Option<T> : IOption
    {
        public void WriteMenu()
        {
            if (this.Undocumented == true)
                return;
            
            IndentTextWriter.Write(IndentTextWriter.FirstIndent, this.ShortName);

            string text = this.LongName;

            if (this.HasParameter == true)
            {
                if (String.IsNullOrEmpty(text) == true)
                    text = this.ParameterName;
                else
                    text += " " + this.ParameterName;
            }

            IndentTextWriter.Write(IndentTextWriter.SecondIndent, text);
            IndentTextWriter.Write(IndentTextWriter.ThirdIndent, this.Explanation);
        }


        public void Parse(List<PredefinedOption> args)
        {
            List<string> parsing_args = new List<string>();
            List<int> removing_indicies = new List<int>();

            string option_name = null;

            for (int i = 0; i < args.Count; i++)
            {
                if (args[i].Value == this.ShortName || args[i].Value == this.LongName)
                {
                    if (i >= args.Count - 1 && this.HasParameter == true)
                        throw new ArgumentException(String.Format(Catalog.GetString("Option {0} has no parameter"),
                                                                        args[i].Value));

                    if (args[i].NoParametersAllowed == true && this.HasParameter)
                        throw new ArgumentException(String.Format(Catalog.GetString("This option {0} requires a parameter and must not combined with other options ('{1}') at " +
                                                                 "the program call"),
                                                                  args[i].Value,
                                                                  args[i].Violation));

                    removing_indicies.Add(i);
                    option_name = args[i].Value;

                    if (this.HasParameter == true)
                    {
                        removing_indicies.Add(++i);

                        if (args[i].Value.StartsWith("-") == true)
                            throw new ArgumentException(String.Format(Catalog.GetString("Option {0} has invalid parameter {1}"),
                                                                             option_name,
                                                                            args[i].Value));

                        string[] values = args[i].Value.Split(new char[]{','}, StringSplitOptions.RemoveEmptyEntries);
                        for (int j = 0; j < values.Length; j++)
                            values[j] = values[j].Trim();
    
                        parsing_args.AddRange(values);
                    }
                    else
                    {
                        parsing_args.Add(null);
                    }
                }
            }

            if (String.IsNullOrEmpty(option_name) == false)
            {
                int number_of_calls = this.HasParameter ? removing_indicies.Count / 2 : removing_indicies.Count;
                removing_indicies.Reverse();
                foreach (int index in removing_indicies)
                    args.RemoveAt(index);

                try
                {
                    this.Value = (T) this.Parser(parsing_args, this.GetValueType(), number_of_calls);
                }
                catch (Exception ex)
                {
                    string message = String.Format(Catalog.GetString("Cannot parse option {0}: {1}"), 
                                                   option_name,
                                                   ex.Message);

                    throw new ArgumentException(message, ex);
                }

                if (this.CloseProgramAfterParsing == true)
                    Environment.Exit(Option.ExitCode);
            }
        }

        private Type GetValueType()
        {
            PropertyInfo[] properties = this.GetType().GetProperties(BindingFlags.Public | 
                                                                     BindingFlags.NonPublic |
                                                                     BindingFlags.Instance);
            foreach (PropertyInfo property in properties)
            {
                if (property.Name != "Value")
                    continue;

                return property.PropertyType; 
            }

            return null;
        }

        public void AddEnumerationToPredefinedValues(Type type)
        {
            this.PredefinedValues.AddRange(Enum.GetNames(type));
        }

        public string ShortName                 {get; set;} 
        public string LongName                  {get; set;} 
        public  List<string> PredefinedValues   {get; set;} = new List<string>();
        public string ParameterName             {get; set;}
        public string Explanation               {get; set;}

        public ParameterHandler Parser           {get; set;}
        public bool CloseProgramAfterParsing    {get; set;} = false;
        public bool HasParameter                {get; set;} = true;
        public int ParsePriority                {get; set;} = -1;

        public bool Undocumented                {get; set;} = false;
        public string Identifier                {get; set;}
        public bool Disabled                    {get; set;} = false;

        public T Value                          {get; set;}

        public static implicit operator T (Option<T> arg)
        {
            return arg.Value;
        }

        public static int ExitCode { get; set; } = 0;
    }
}