require 'whois'


module Whois
  class ParserExtensions

    module WhoisRecord
      def self.included(base)
        base.extend ClassMethods
      end

      # Checks if this class respond to given method.
      #
      # Overrides the default implementation to add support
      # for {Parser::PROPERTIES} and {Parser::METHODS}.
      #
      # @return [Boolean]
      def respond_to?(symbol, include_private = false)
        respond_to_parser_method?(symbol) || super
      end


      # Lazy-loads and returns the parser proxy for current record.
      #
      # @return [Whois::Record::Parser]
      def parser
        @parser ||= Parser.new(self)
      end

      # Returns a Hash containing all supported properties for this record
      # along with corresponding values.
      #
      # @return [{ Symbol => Object }]
      # @raise  [Whois::AttributeNotSupported, Whois::AttributeNotImplemented]
      def properties
        warn("#{self.class}#properties is deprecated")
        hash = {}
        Parser::PROPERTIES.each { |property| hash[property] = send(property) }
        hash
      end

      # Shortcut for <tt>#registrant_contacts.first</tt>.
      #
      # @see Whois::Record#registrant_contacts
      #
      # @return [Whois::Record::Contact]
      #         If the property is supported and a contact exists.
      # @return [nil]
      #         If the the contact doesn't exist.
      # @raise  [Whois::AttributeNotSupported, Whois::AttributeNotImplemented]
      def registrant_contact
        parser.registrant_contacts.first
      end

      # Shortcut for <tt>#admin_contacts.first</tt>.
      #
      # @see Whois::Record#admin_contacts
      #
      # @return [Whois::Record::Contact]
      #         If the property is supported and a contact exists.
      # @return [nil]
      #         If the contact doesn't exist.
      # @raise  [Whois::AttributeNotSupported, Whois::AttributeNotImplemented]
      def admin_contact
        parser.admin_contacts.first
      end

      # Shortcut for <tt>#technical_contacts.first</tt>.
      #
      # @see Whois::Record#technical_contacts
      #
      # @return [Whois::Record::Contact]
      #         If the property is supported and a contact exists.
      # @return [nil]
      #         If the contact doesn't exist.
      # @raise  [Whois::AttributeNotSupported, Whois::AttributeNotImplemented]
      def technical_contact
        parser.technical_contacts.first
      end

      # Collects and returns all the contacts.
      #
      # @see Whois::Parser#contacts
      #
      # @return [Array<Whois::Record::Contact>]
      def contacts
        warn("#{self.class}#contacts is deprecated")
        parser.contacts
      end


      # Checks whether this {Whois::Record} is different than +other+.
      #
      # Comparing the {Whois::Record} content is not as trivial as you may think.
      # WHOIS servers can inject into the WHOIS response strings that changes at every request,
      # such as the timestamp the request was generated or the number of requests left
      # for your current IP.
      #
      # These strings causes a simple equal comparison to fail even if
      # the registry data is the same.
      #
      # This method should provide a bulletproof way to detect whether this record
      # changed compared with +other+.
      #
      # @see Whois::Parser#changed?
      #
      # @param  [Whois::Record] other The other record instance to compare.
      # @return [Boolean]
      def changed?(other)
        !unchanged?(other)
      end

      # The opposite of {#changed?}.
      #
      # @see Whois::Parser#unchanged?
      #
      # @param  [Whois::Record] other The other record instance to compare.
      # @return [Boolean]
      def unchanged?(other)
        unless other.is_a?(self.class)
          raise(ArgumentError, "Can't compare `#{self.class}' with `#{other.class}'")
        end

        equal?(other) || parser.unchanged?(other.parser)
      end

      # Checks whether this is an incomplete response.
      #
      # @deprecated
      # @see Whois::Parser#response_incomplete?
      #
      # @return [Boolean]
      def response_incomplete?
        warn("#{self.class}#response_incomplete? is deprecated. Use parser.response_incomplete?")
        parser.response_incomplete?
      end

      # Checks whether this is a throttle response.
      #
      # @deprecated
      # @see Whois::Parser#response_throttled?
      #
      # @return [Boolean]
      def response_throttled?
        warn("#{self.class}#response_throttled? is deprecated. Use parser.response_throttled?")
        parser.response_throttled?
      end

      # Checks whether this is an unavailable response.
      #
      # @deprecated
      # @see Whois::Parser#response_unavailable?
      #
      # @return [Boolean]
      def response_unavailable?
        warn("#{self.class}#response_unavailable? is deprecated. Use parser.response_unavailable?")
        parser.response_unavailable?
      end


      # @deprecated
      def property_any_supported?(property)
        warn("#{self.class}#property_any_supported? is deprecated and has no effect. Use Whois::Parser.property_any_supported? if you need it.")
      end


      private

      # @api private
      def respond_to_parser_method?(symbol)
        name = symbol.to_s =~ /\?$/ ? symbol.to_s[0..-2] : symbol
        Parser::PROPERTIES.include?(name.to_sym) || Parser::METHODS.include?(name.to_sym)
      end

      # Delegates all method calls to the internal parser.
      def method_missing(method, *args, &block)
        if Parser::PROPERTIES.include?(method)
          self.class.define_property_method(method)
          send(method, *args, &block)
        elsif Parser::METHODS.include?(method)
          self.class.define_method_method(method)
          send(method, *args, &block)
        elsif method.to_s =~ /([a-z_]+)\?/ and (Parser::PROPERTIES + Parser::METHODS).include?($1.to_sym)
          self.class.define_question_method($1)
          send(method)
        else
          super
        end
      end

      module ClassMethods
        # @api private
        def define_property_method(method)
          class_eval <<-RUBY, __FILE__, __LINE__ + 1
            def #{method}(*args, &block)
              parser.#{method}(*args, &block)
            end
          RUBY
        end

        # @api private
        def define_method_method(method)
          class_eval <<-RUBY, __FILE__, __LINE__ + 1
            def #{method}(*args, &block)
              parser.#{method}(*args, &block)
            end
          RUBY
        end

        # @api private
        def define_question_method(method)
          class_eval <<-RUBY, __FILE__, __LINE__ + 1
            def #{method}?
              !#{method}.nil?
            end
          RUBY
        end
      end
    end

  end
end