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

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


        public 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("-") == true && arg.StartsWith("--") == false && arg.Length > 2)
                {
                    for (int i = 1; i < arg.Length; i++)
                    {
                        PredefinedOption parameter = new PredefinedOption();
                        parameter.Value = "-" + arg[i].ToString();
                        parameter.NoParametersAllowed = true;
                        parameter.Violation = arg;
                        this.PredefinedArgs.Add(parameter);
                    }
                }
                else
                {
                    PredefinedOption parameter = new PredefinedOption();
                    parameter.Value = arg;
                    this.PredefinedArgs.Add(parameter);
                }
            }
        }

  
        private void CheckUnhandledArgs()
        {
            foreach (PredefinedOption parameter in this.PredefinedArgs)
            {
                if (parameter.Value.StartsWith("-") == 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();
            this.HelpOption.Explanation     = "Show help options";
            this.HelpOption.ShortName       = Parser.SHORT_HELP_OPTION;
            this.HelpOption.LongName        = Parser.LONG_HELP_OPTION;
            this.HelpOption.CloseProgramAfterParsing = true;
            this.HelpOption.ParsePriority   = Int32.MinValue;
            this.HelpOption.Parser          = this.ShowMenuParser;

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

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

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

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

            this.Quiet = new Option<bool>();
            this.Quiet.Explanation  = Catalog.GetString("Disables output to console");
            this.Quiet.ShortName    = "-q";
            this.Quiet.LongName     = "--quiet";
            this.Quiet.HasParameter = false;
            this.Quiet.Parser       = StandardParser.SwitchOn;
            this.Quiet.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;}
    }
}
