module Antrapol
  module ToolRack
    
    module ArgUtils
      include Antrapol::ToolRack::ConditionUtils

      class ArgParsingException < StandardError; end
      class RequiredFieldEmpty < ArgParsingException; end
      class DuplicatedField < ArgParsingException; end
      class InvalidKey < ArgParsingException; end
     
      module ClassMethods
        include Antrapol::ToolRack::ConditionUtils
       
        def arg_spec(&block)
          class_eval(&block) 
        end

        def opt(key, desc, opts = {}, &block)
          raise RequiredFieldEmpty, "Key field cannot be empty" if is_empty?(key)
          raise DuplicatedField, "Given key '#{key}' already exist" if arg_options.keys.include?(key)
          raise RequiredFieldEmpty, "Block is require" if not block

          arg_options[key] = { key: key, desc: desc, arg_options: opts, callback: block }
          required << key if opts[:required] == true
        end

        def opt_alias(existing, new)
          raise InvalidKey, "Existing key '#{existing}'to map to new key '#{new}' not found" if not arg_options.keys.include?(existing)
          aliases[new] = existing 
        end

        def is_all_required_satisfy?(arr)
          if arr.is_a?(Array)
            res = true
            required.each do |f|
              res = arr.include?(f) 
              if not res
                al = aliases.invert[f]
                res = arr.include?(al)
                raise RequiredFieldEmpty, "Required parameter '#{f}' is not given" if not res
              end
            end
            res

          else
            raise ArgParsingException, "Given array to check for required fields is not an array. Got #{arr}"
            if arr.is_a?(ArgParsingException)
              STDERR.puts "Error throwing into the method was : #{arr.message}"
            end

          end
        end

        def callback(evt, opts = {}, &block)
          raise ArgParsingException, "Event should not be nil" if evt.nil? 
          callbacks[evt] = { opts: opts, cb: block }
        end

        def required
          if @_req.nil?
            @_req = []
          end
          @_req
        end

        def arg_options
          if @_arg_options.nil?
            @_arg_options = {}
          end
          @_arg_options
        end

        def aliases
          if @_aliases.nil?
            @_aliases = {}
          end
          @_aliases
        end

        def callbacks
          if @_cb.nil?
            @_cb = {}
          end
          @_cb
        end

        #def value_separator=(val)
        #  logger.debug "Setting value separator #{val}"
        #  @_valSep = val
        #end
        def set_value_separator(val)
          @_valSep = val
        end

        def value_separator
          if @_valSep.nil?
            @_valSep = " "
          end
          @_valSep
        end

        def logger
          Antrapol::ToolRack.logger(:c_arg_utils)
        end

      end # module ClassMethods
      def self.included(klass)
        klass.extend(ClassMethods)
      end

      def parse_argv(argv, &block)
        cb = self.class.callbacks[:pre_processing]
        if not_empty?(cb) and cb[:cb]
          #logger.debug "Calling pre-processing for class #{self}"
          @parse_argv_block = block
          # here will engage the DSL method of the included class
          update_argv, val = instance_exec(argv, &cb[:cb])
          #logger.debug "Preprocessing return update flag : #{update_argv} and list #{val} for class #{self}"
          argv = val if update_argv
        end

        # split the key and value if given
        # > app -n:john
        if self.class.value_separator == " " and argv.length > 0
          parse_argv_space(argv)
        else
          parse_argv_key_value_mixed(argv)
        end

        cbp = self.class.callbacks[:post_processing]
        if not_empty?(cbp) and cbp[:cb]
          #logger.debug "Post processing got #{argv}"
          instance_exec(argv, &cbp[:cb]) 
        end
      end 

      private
      def parse_argv_space(argv)
        logger.debug "got argv (space) : #{argv}"
        cls = self.class
        clear = cls.is_all_required_satisfy?(argv)
        logger.debug "All required fields are there? : #{clear}"
        if clear

          go = true
          i = 0
          while i < argv.length
            a = argv[i]
            logger.debug "Processing : #{a}"

            key = a
            conf = cls.arg_options[key]
            if is_empty?(conf)
              al = cls.aliases[key]
              conf = cls.arg_options[al] if not_empty?(al)
            end

            if is_empty?(conf)
              logger.warn "Given key to select ('#{key}') has not defined before"
              # ignore if the given parameter not recognized
              i += 1
              next
            end

            if not conf[:callback].nil? 
              # expecting parameter
              paramsCount = conf[:callback].arity
              if paramsCount > 0
                # look ahead token to load the parameters
                val = []
                (0...paramsCount).each do |ii|
                  val << argv[i+1+ii]
                end
                val.delete_if { |e| e.nil? }
                logger.debug "Parameter for callback : #{val}"
                raise RequiredFieldEmpty, "Key '#{a}' requires #{paramsCount} parameter(s) but got #{val.length}" if paramsCount != val.length 
                instance_exec(*val, &conf[:callback])
                i += paramsCount+1
              else
                instance_eval(&conf[:callback])
                i += 1
              end

            else
              i += 1

            end

          end # argv.each

        end # if all required satisfy
        
      end

      def parse_argv_key_value_mixed(argv)
        logger.debug "got argv : #{argv}"
        cls = self.class
        if cls.is_all_required_satisfy?(argv)

          argv.each do |a|
            logger.debug "Processing : #{a}"

            key = a
            val = []
            #p a 
            #p cls.value_separator
            #p a =~ /#{cls.value_separator}/
            if (a =~ /#{cls.value_separator}/) != nil
              keys = a.split(cls.value_separator)
              key = keys.first
              val = keys[1..-1]
            end

            logger.debug "After separation : #{key}"

            conf = cls.arg_options[key]
            if is_empty?(conf)
              al = cls.aliases[key]
              conf = cls.arg_options[al] if not_empty?(al)
            end

            if is_empty?(conf)
              logger.warn "Given key to select ('#{key}') has not defined before"
              # ignore if the given parameter not recognized
              next
            end

            if not conf[:callback].nil? 
              # expecting parameter
              paramsCount = conf[:callback].arity
              if paramsCount > 0
                raise RequiredFieldEmpty, "Key '#{conf[:key]}' requires #{paramsCount} value(s) to be given but got #{val.length}." if val.length != paramsCount
                instance_exec(*val, &conf[:callback])
              else
                instance_eval(&conf[:callback])
              end
            end

          end # argv.each

        end # if all required satisfy
      end

      def logger
        Antrapol::ToolRack.logger(:arg_utils) 
      end

    end

  end
end