// 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.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;

using Hiperscan.Unix;


namespace Hiperscan.Common.CommandLineArgsParser
{
    public abstract class Parser
    {
        public const string SHORT_HELP_OPTION = "-?";
        public const string LONG_HELP_OPTION  = "--help";


        protected Parser(string program_name, string[] args)
        {
            Parser.ProgramName = program_name;
            this.CheckProgramName();

            this.AllArgs        = new List<string>(args);
            this.PredefinedArgs = new List<PredefinedOption>();
            this.UnhandledArgs  = new List<string>();
            this.PrepareArgs();


            this.InitPredefinedOptions();
            this.InitializeOptions();

            this.ProgramCallSection = new TextSection();
            this.OptionSection      = new OptionSection(this);
            this.ExampleSection     = new TextSection();
            this.InitializeSections();

            this.OptionSection.Parse(this.PredefinedArgs);
            this.CheckUnhandledArgs();
            this.CheckOrderUnhandledArgs();
        }

        private void CheckProgramName()
        {
            Regex name_regex = new Regex(@"^[a-z]+(-[a-z0-9]+)*$");

            if (name_regex.Match(Parser.ProgramName).Success == false)
            {
                string message = string.Format(Catalog.GetString("Program name '{0}' has invalid format"), Parser.ProgramName);
                throw new Exception(message);
            }
        }

        private void PrepareArgs()
        {
            foreach (string arg in this.AllArgs)
            {
                if (arg.StartsWith("-", StringComparison.InvariantCulture) == true &&
                    arg.StartsWith("--", StringComparison.InvariantCulture) == false && arg.Length > 2)
                {
                    for (int i = 1; i < arg.Length; i++)
                    {
                        PredefinedOption parameter = new PredefinedOption
                        {
                            Value = "-" + arg[i].ToString(),
                            NoParametersAllowed = true,
                            Violation = arg
                        };
                        this.PredefinedArgs.Add(parameter);
                    }
                }
                else
                {
                    PredefinedOption parameter = new PredefinedOption
                    {
                        Value = arg
                    };
                    this.PredefinedArgs.Add(parameter);
                }
            }
        }
  
        private void CheckUnhandledArgs()
        {
            foreach (PredefinedOption parameter in this.PredefinedArgs)
            {
                if (parameter.Value.StartsWith("-", StringComparison.InvariantCulture) == true)
                {
                    string message = string.Format(Catalog.GetString("Unkonwn option {0}"), parameter.Value);
                    throw new ArgumentException(message);
                }
                else
                {
                    this.UnhandledArgs.Add(parameter.Value);
                }
            }
        }

        private void CheckOrderUnhandledArgs()
        {
            for (int i =  0; i < this.UnhandledArgs.Count; i++)
            {
                int all_args_index = this.AllArgs.Count - this.UnhandledArgs.Count + i;
                if (this.UnhandledArgs[i] != this.AllArgs[all_args_index])
                {
                    string message = string.Format(Catalog.GetString("Invalid program call - first list options and then parameters"));
                    throw new ArgumentException(message);
                }
            }
        }


        protected abstract void InitializeSections();
        protected abstract void InitializeOptions();

        private void InitPredefinedOptions()
        {
            this.HelpOption = new Option
            {
                Explanation = "Show help options",
                ShortName = Parser.SHORT_HELP_OPTION,
                LongName = Parser.LONG_HELP_OPTION,
                CloseProgramAfterParsing = true,
                ParsePriority = Int32.MinValue,
                Parser = this.ShowMenuParser
            };

            this.Version = new Option
            {
                Explanation = Catalog.GetString("Shows the version number of the program"),
                LongName = "--version",
                CloseProgramAfterParsing = true,
                ParsePriority = Int32.MinValue + 1,
                Parser = this.ShowVersionParser
            };

            this.AutoCompletionDir = new Option<string>
            {
                Explanation = Catalog.GetString("Creates an autocomletion file in given DIR"),
                LongName = "--create-auto-completion",
                ParameterName = Catalog.GetString("DIR"),
                CloseProgramAfterParsing = true,
                ParsePriority = Int32.MinValue + 2,
                Parser = this.CreateAutoCompletionFile,
                Undocumented = true
            };

            this.Debug = new Option<bool>
            {
                Explanation = Catalog.GetString("Shows debug information"),
                LongName = "--debug",
                HasParameter = false,
                ParsePriority = Int32.MinValue + 3,
                Parser = StandardParser.SwitchOn
            };

            this.LogFile = new Option<string>
            {
                Explanation = Catalog.GetString("Writes log infos in given FILE"),
                ShortName = "-l",
                LongName = "--log",
                ParameterName = Catalog.GetString("FILE"),
                ParsePriority = Int32.MinValue + 5,
                Parser = StandardParser.FileStringParser
            };

            this.Quiet = new Option<bool>
            {
                Explanation = Catalog.GetString("Disables output to console"),
                ShortName = "-q",
                LongName = "--quiet",
                HasParameter = false,
                Parser = StandardParser.SwitchOn,
                ParsePriority = this.LogFile.ParsePriority - 1
            };
        }

        public static void ShowHelpMenu()
        {
            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.BaseType == typeof(Parser))
                    {
                        object[] args = new object[2];
                        args[0] = Parser.ProgramName;
                        args[1] = new string[] { "-?" };
                        assembly.CreateInstance(type.FullName,
                                                false,
                                                BindingFlags.CreateInstance,
                                                null,
                                                args,
                                                CultureInfo.CurrentCulture,
                                                null);
                    }
                }
            }
        }

        public virtual void ShowMenu()
        {
            this.ShowMenuParser(null, null, 1);
        }

        private  object ShowMenuParser(List<string> values, Type type, int number_of_calls)
        {
            this.ProgramCallSection.ShowMenu();
            this.OptionSection.ShowMenu();
            this.ExampleSection.ShowMenu();

            return null;
        }

        private object CreateAutoCompletionFile(List<string> values, Type type, int number_of_calls)
        {
            string file = values[values.Count - 1];
            AutoCompletion.CreateAutoCompletionFile(this, file);
            return file;
        }

        private object ShowVersionParser(List<string> values, Type type, int number_of_calls)
        {
            Console.WriteLine(Assembly.GetEntryAssembly().GetName().ToString());
            return null;
        }

        protected TextSection ProgramCallSection      { get; set; }
        public OptionSection OptionSection            { get; protected set; }
        protected TextSection ExampleSection          { get; set; }

        protected Option HelpOption                   { get; set; }
        public Option<string> AutoCompletionDir       { get; private set; } 
        public Option<bool> Debug                     { get; private set; }
        protected Option Version                      { get; set; }
        public Option<bool> Quiet                     { get; private set; }
        public Option<string> LogFile                 { get; private set; }

        public static string ProgramName              { get; private set; }
        public List<string> AllArgs                   { get; private set; }
        private List<PredefinedOption> PredefinedArgs { get; set; }
        public List<string> UnhandledArgs             { get; private set; }
    }
}