# encoding: UTF-8
require 'log4r'
require 'net/http'
require 'net/https'
require 'uri'

module Mauve

  # Interface to the Bytemark calendar.
  #
  class CalendarInterface 
    
    class << self

      # @return [Log4r::Logger]
      def logger
        @logger ||= Log4r::Logger.new(self.to_s)
      end

      def get_attendees(klass, at=Time.now)
        #
        # Returns nil if no calendar_url has been set.
        #
        return [] unless Configuration.current.bytemark_calendar_url
  
        url = Configuration.current.bytemark_calendar_url.dup

        url.merge!(File.join(url.path, "/api/attendees/#{klass}/#{at.strftime("%Y-%m-%dT%H:%M:00")}"))
        ans = do_get(url)

        return [] unless ans.is_a?(Array)

        ans.select{|x| x.is_a?(String)}
      end

      #
      # This should return a list of dates of forthcoming bank holidays
      #
      def get_bank_holiday_list(at = Time.now)
        return [] unless Configuration.current.bytemark_calendar_url

        url = Configuration.current.bytemark_calendar_url.dup
        url.merge!(File.join(url.path, "/api/bank_holidays/#{at.strftime("%Y-%m-%d")}"))
        ans = do_get(url)

        return [] unless ans.is_a?(Array)
        ans.select{|x| x.is_a?(Date)}
      end

      # Check to see if the user is on holiday.
      #
      # Class method.
      #
      # @param [String] usr User single sign on.
      # 
      # @return [Boolean] True if on holiday, false otherwise.
      def is_user_on_holiday?(usr, at=Time.now)
        get_attendees("staff_holiday").include?(usr)
      end

      # Check to see if the user is on holiday.
      #
      # Class method.
      #
      # @param [String] usr User single sign on.
      # 
      # @return [Boolean] True if on holiday, false otherwise.
      def is_user_off_sick?(usr, at=Time.now)
        get_attendees("sick_period", at).include?(usr)
      end

      private

      # Grab a URL from the wide web.
      #
      # @todo boot this in its own class since list of ips will need it too.
      #
      # @param [String] uri -- a URL
      # @return [String or nil] -- the contents of the URI or nil if an error has been encountered.
      #
      def do_get (uri, limit = 11)

        if 0 > limit
          logger.warn("HTTP redirect too deep for #{uri}.")
          return nil
        end
        
        begin
          uri = URI.parse(uri) unless uri.is_a?(URI::HTTP)

          raise ArgumentError, "#{uri_str.inspect} doesn't look like an HTTP uri" unless uri.is_a?(URI::HTTP)

          http = Net::HTTP.new(uri.host, uri.port)

          #
          # Five second timeouts.
          #
          http.open_timeout = http.read_timeout = Configuration.current.remote_http_timeout || 5
 
          if (uri.scheme == "https")
            http.use_ssl     = true
            http.ca_path     = "/etc/ssl/certs/" if File.directory?("/etc/ssl/certs")
            http.verify_mode = Configuration.current.remote_https_verify_mode || OpenSSL::SSL::VERIFY_NONE
          end

          response = http.start { http.get(uri.request_uri()) }

          if response.is_a?(Net::HTTPOK)
            #
            # Parse the string as YAML.
            #
            result = if response.body.is_a?(String)
              begin
                YAML.load(response.body)
              rescue YAML::Error => err
                logger.error "Caught #{ex.class.to_s} (#{ex.to_s}) whilst querying #{url.to_s}."
                logger.debug err.backtrace.join("\n")
                nil
              end
            else
              nil
            end

            return result
          elsif response.is_a?(Net::HTTPRedirection) and response.key?('Location')
            location = response['Location']
            
            #
            # Bodge locations..
            #
            if location =~ /^\//
              location = uri.class.build([uri.userinfo, uri.host, uri.port, nil, nil, nil]).to_s + location
            end

            return do_get(location, limit-1) 

          else
            logger.warn("Request to #{uri.to_s} returned #{response.code} #{response.message}.")
            return nil

          end

        rescue Timeout::Error => ex
          logger.error("Timeout caught during fetch of #{uri.to_s}.")

        rescue StandardError => ex
          logger.error("#{ex.class} caught during fetch of #{uri.to_s}: #{ex.to_s}.")
          logger.debug(ex.backtrace.join("\n"))

        end

        return nil
      end

      # This does HTTP fetches with a 5 minute cache
      #
      # @param [String] url
      # @param [Time] cache_until
      #
      # @return [String or nil]
      def do_get_with_cache(url, cache_until = Time.now + 5.minutes)
        @cache ||= {}

        if @cache.has_key?(url)
          result, cached_until = @cache[url]

          return result if cached_until > Time.now and not result.nil?
        end

        result = do_get(url)
        @cache[url] = [result, cache_until] unless result.nil?

        return result
      end

      #
      # This should get called periodically.
      #
      def clean_cache

        @cache.keys.select do |url|
          result, cached_until = @cache[url]
          @cache.delete(url) if !cached_until.is_a?(Time) or cached_until <= Time.now
        end

        @cache 
      end

    end

  end

end