require 'mauve/source_list'
require 'mauve/people_list'
require 'mauve/mauve_time'

module Mauve

  # Configuration object for Mauve.  This is used as the context in
  # Mauve::ConfigurationBuilder.
  #
  class Configuration

    class << self
      # The current configuration
      # @param  [Mauve::Configuration]
      # @return [Mauve::Configuration]
      attr_accessor :current
    end

    # The Server instance
    # @return [Mauve::Server]
    attr_accessor :server

    # Notification methods
    # @return [Hash]
    attr_reader   :notification_methods

    # People
    # @return [Hash]
    attr_reader   :people

    # Alert groups
    # @return [Array]
    attr_reader   :alert_groups

    # The source lists
    # @return [Hash]
    attr_reader   :source_lists

    # Various further configuration items
    #
    attr_reader   :bytemark_auth_url, :bytemark_calendar_url, :remote_http_timeout, :remote_https_verify_mode, :failed_login_delay
    attr_reader   :max_acknowledgement_time, :working_hours, :dead_zone, :daytime_hours
    attr_reader   :minimal_dns_lookups


    #
    # Set up a base config.
    #
    def initialize
      @server = nil
      @notification_methods = {}
      @people = {}
      @source_lists = Hash.new{|h,k| h[k] = Mauve::SourceList.new(k)}
      @alert_groups = []

      #
      # Set the auth/calendar URLs
      #
      @bytemark_auth_url     = nil
      @bytemark_calendar_url = nil

      #
      # Set a couple of params for remote HTTP requests.
      #
      self.remote_http_timeout = 5
      self.remote_https_verify_mode = "peer"

      #
      # Rate limit login attempts to limit the success of brute-forcing.
      #
      self.failed_login_delay = 1

      #
      # Reduce the amount of DNS lookups when matching alerts to groups.
      #
      self.minimal_dns_lookups = false

      #
      # Maximum amount of time to acknowledge for
      #
      self.max_acknowledgement_time = 15.days

      #
      # Working hours
      #
      self.dead_zone     = 3.0...7.0
      self.daytime_hours = 8.0...22.0
      self.working_hours = 9.5...17.5
    end

    # Set the calendar URL.
    #
    # @param [String] arg
    # @return [URI]
    def bytemark_calendar_url=(arg)
      raise ArgumentError, "bytemark_calendar_url must be a string" unless arg.is_a?(String)

      @bytemark_calendar_url = URI.parse(arg)

      #
      # Make sure we get an HTTP URL.
      #
      raise ArgumentError, "bytemark_calendar_url must be an HTTP(S) URL." unless %w(http https).include?(@bytemark_calendar_url.scheme)

      #
      # Set a default request path, if none was given
      #
      @bytemark_calendar_url.normalize!

      @bytemark_calendar_url
    end

    # Set the Bytemark Authentication URL
    #
    # @param [String] arg
    # @return [URI]
    def bytemark_auth_url=(arg)
      raise ArgumentError, "bytemark_auth_url must be a string" unless arg.is_a?(String)

      @bytemark_auth_url = URI.parse(arg)
      #
      # Make sure we get an HTTP URL.
      #
      raise ArgumentError, "bytemark_auth_url must be an HTTP(S) URL." unless %w(http https).include?(@bytemark_auth_url.scheme)

      #
      # Set a default request path, if none was given
      #
      @bytemark_auth_url.normalize!

      @bytemark_auth_url
    end

    # Sets the timeout when making remote HTTP requests
    #
    # @param [Integer] arg
    # @return [Integer]
    def remote_http_timeout=(arg)
      raise ArgumentError, "remote_http_timeout must be an integer" unless arg.is_a?(Integer)
      @remote_http_timeout = arg
    end

    # Sets the SSL verification mode when makeing remote HTTPS requests
    #
    # @param [String] arg must be one of "none" or "peer"
    # @return [Constant]
    def remote_https_verify_mode=(arg)
      @remote_https_verify_mode = case arg
      when "peer"
        OpenSSL::SSL::VERIFY_PEER
      when "none"
        OpenSSL::SSL::VERIFY_NONE
      else
        raise ArgumentError, "remote_https_verify_mode must be either 'peer' or 'none'"
      end
    end

    # Set the delay added following a failed login attempt.
    #
    # @param [Numeric] arg Number of seconds to delay following a failed login attempt
    # @return [Numeric]
    #
    def failed_login_delay=(arg)
      raise ArgumentError, "failed_login_delay must be numeric" unless arg.is_a?(Numeric)
      @failed_login_delay = arg
    end

    # Set the maximum amount of time alerts can be ack'd for
    #
    #
    def max_acknowledgement_time=(arg)
      raise ArgumentError, "max_acknowledgement_time must be numeric" unless arg.is_a?(Numeric)
      @max_acknowledgement_time = arg
    end

    def calendar=(x)
      lambda{|at| CalendarInterface.get_attendees(x,at)}
    end

    def working_hours=(arg)
      @working_hours = self.class.parse_range(arg)
    end

    def daytime_hours=(arg)
      @daytime_hours = self.class.parse_range(arg)
    end

    def dead_zone=(arg)
      @dead_zone = self.class.parse_range(arg)
    end

    # This method takes a range, and wraps it within the specs defined by
    # allowed_range.
    # 
    # It can take an array of Numerics, Strings, Ranges etc
    #
    # @param 
    #
    def self.parse_range(arg, allowed_range = (0...24))
      args = [arg].flatten

      #
      # Tidy up our allowed ranges
      #
      min = allowed_range.first
      max = allowed_range.last

      #
      # If we've been given a numeric range, make sure they're all floats.
      #
      min = min.to_f if min.is_a?(Numeric)
      max = max.to_f if max.is_a?(Numeric)
      
      ranges = []

      args.each do |arg|
        case arg
          when Range
            from = arg.first
            to = arg.last
            exclude_end = arg.exclude_end?
          else
            from = arg
            to = arg
        end

        from = min unless allowed_range.include?(from)

        #
        # In the case of integers, we want to match up until, but not including
        # the next integer.
        #
        if to.is_a?(Integer)
          to = (exclude_end ? to : to.succ)
          exclude_end = true
        end

        to = max unless allowed_range.include?(to)

        from = from.to_f if from.is_a?(Numeric)
        to   = to.to_f   if to.is_a?(Numeric)

        if from > to or (from >= to and exclude_end)
          ranges << Range.new(from, max, allowed_range.exclude_end?)
          ranges << Range.new(min, to, exclude_end)
        else
          ranges << Range.new(from, to, exclude_end)
        end

      end

      ranges
    end


    def minimal_dns_lookups=(bool)
      if bool.is_a?(TrueClass) or bool.to_s.strip =~ /^(1|y(es)?|t(rue))/
        @minimal_dns_lookups = true
      else
        @minimal_dns_lookups = false
      end
    end

  end


end