require "optparse"
require 'forwardable'
require_relative 'option_value_recognizer'

module Options
  module Helpers
    class DevopsOptionsParser
      extend Forwardable
      attr_reader :parsed_options
      attr_accessor :resource_name, :command_name

      # leave this duplication for a while
      TABLE_FORMAT = "table"
      JSON_FORMAT = "json"
      CSV_FORMAT = "csv"
      OUTPUT_FROMATS = [TABLE_FORMAT, JSON_FORMAT, CSV_FORMAT]

      def_delegators :@parser, :banner, :separator, :on, :parse!

      def initialize(default_options, &block)
        @parser = OptionParser.new
        @default_options = default_options
        @parsed_options = {}

        banner_usage
        if block
          @parser.separator(I18n.t("options.options") + ":\n")
          block.call(self, @parsed_options)
        end

        common_options_separator
        recognize_help
        recognize_assume_yes
        recognize_config
        recognize_version
        recognize_host
        recognize_api
        recognize_prefix
        recognize_username
        recognize_format
        recognize_completion

        @parser
      end


      # it is used to set options values without later quiz.
      # Arguments description:
      #   option_name - name of option;
      #   attrs - hash with following options:
      #     :resource_name - used for description lookup. Could be set with attr_accessor. Lookup path is "options.descriptions.#{resource_name}.#{option_name}".
      #     :command_name - Also could be set with attr_accessor. Changes description lookup to "options.descriptions.#{resource_name}.#{command_name}.#{option_name}"
      #     :type - could be one of following values:
      #       :required (default)
      #       :optional
      #       :switch
      #     :default - default value. nil by default
      #     :switch_value - set this value, if type is :switch and option've been recognized.
      #     :option_key - key in result_options hash. Default - option_name.to_sym
      #     :variable - default - option_name.upcase
      #     :description - default - I18n.t("options.descriptions.#{resource_name}.#{option_name}")
      #     :short - short option name
      #
      # EXAMPLES:
      # 1)
      #      parser.resource_name = :stack
      #      parser.recognize_option_value(:provider)
      #   is equal to
      #      opts.on("--provider provider", I18n.t("options.descriptions.stack.provider)) do |provider|
      #        options[:provider] = provider
      #      end
      #
      #   Also, you could pass resource name in attributes without need to use attr_accessor:
      #     parser.recognize_option_value(:provider, resource_name: :stack)
      #
      # 2)
      #      parser.recognize_option_value(:provider, type: :optional, default: 'openstack')
      #   is equal to
      #      options[:provider] = 'openstack'
      #      opts.on("--provider [provider]", I18n.t("options.descriptions.stack.provider)) do |provider|
      #        options[:provider] = provider
      #      end
      #
      # 3)
      #      parser.recognize_option_value(:no_template, type: :switch, default: false, switch_value: true)
      #   is equal to
      #      options[:no_template] = false
      #      opts.on("--no_template", I18n.t("options.descriptions.image.no_template)) do
      #        options[:no_template] = true
      #      end
      #
      # 4)
      #     parser.recognize_option_value(:parameters) do |parameters|
      #       options[:parameters] = JSON.parse(parameters)
      #     end
      #   is equal to
      #      opts.on("--parameters parameters", I18n.t("options.descriptions.stack.parameters)) do |parameters|
      #        options[:parameters] = JSON.parse(parameters)
      #      end
      def recognize_option_value(option_name, attrs={}, &block)
        scope = i18n_scope(attrs.delete(:resource_name), attrs.delete(:command_name), option_name)
        recognizer = OptionValueRecognizer.new(option_name, scope, attrs)
        recognizer.recognize(@parser, @parsed_options, &block)
      end

      private

      def i18n_scope(specified_resource_name, specified_command_name, option_name)
        resource = specified_resource_name || resource_name
        raise "Resource name isn't specified. Use parser.resource= or :resource_name attribute" unless resource
        segments = [:options, :descriptions, resource, specified_command_name || command_name, option_name]
        segments.compact.join('.')
      end

      def banner_usage
        @parser.banner = "\n" + I18n.t("options.usage", :cmd => $0) + "\n\n" + I18n.t("options.commands") + ":\n"
      end

      def common_options_separator
        @parser.separator("\n" + I18n.t("options.common_options") + ":\n")
      end

      def recognize_help
        @parser.on("-h", "--help", I18n.t("options.common.help")) do
          @parser.banner << "\n"
          puts @parser
          exit
        end
      end

      def recognize_assume_yes
        @parsed_options[:no_ask] = false
        @parser.on("-y", "--assumeyes", I18n.t("options.common.confirmation")) do
          @parsed_options[:no_ask] = true;
        end
      end

      def recognize_config
        #Not used, just for banner purposes. This should be fixed when we find how to deal with options separetely
        @parser.on("-c", "--config CONFIG", I18n.t("options.common.config", :file => DevopsClient.config_file)) do
          puts "Not implemented yet"
          exit
        end
      end

      def recognize_version
        @parser.on("-v", "--version", I18n.t("options.common.version")) do
          puts I18n.t("options.common.version") + ": #{DevopsClient::VERSION}"
          exit
        end
      end

      def recognize_host
        @parser.on("--host HOST", I18n.t("options.common.host", :host => @default_options[:host])) do |h|
          @parsed_options[:host] = h
        end
      end

      def recognize_api
        @parsed_options[:api] = @default_options[:api]
        @parser.on("--api VER", I18n.t("options.common.api", :api => @parsed_options[:api])) do |a|
          @parsed_options[:api] = a
        end
      end

      def recognize_prefix
        @parsed_options[:prefix] = @default_options[:prefix]
        @parser.on("--prefix PREFIX", I18n.t("options.common.prefix", :prefix => @parsed_options[:prefix])) do |p|
          @parsed_options[:prefix] = p
        end
      end

      def recognize_username
        @parsed_options[:username] = @default_options[:username]
        @parser.on("--user USERNAME", I18n.t("options.common.username", :username => @parsed_options[:username])) do |u|
          @parsed_options[:username] = u.strip
          print I18n.t("handler.user.password_for", :user => @parsed_options[:username])
          begin
            system("stty -echo")
            @parsed_options[:password] = STDIN.gets.strip
          ensure
            system("stty echo")
          end
          puts
        end
      end

      def recognize_format
        @parsed_options[:format] = TABLE_FORMAT
        @parser.on("--format FORMAT", I18n.t("options.common.format", :formats => OUTPUT_FROMATS.join("', '"), :format => TABLE_FORMAT)) do |f|
          @parsed_options[:format] = f if OUTPUT_FROMATS.include?(f)
        end
      end

      def recognize_completion
        # should be handled in lib/devops-client.rb
        @parser.on("", "--completion", I18n.t("options.common.completion"))
      end

    end
  end
end