require 'net/http'
require 'uri'
require 'set'

# Akismet
#
# Author::    David Czarnecki
# Copyright:: Copyright (c) 2005 - David Czarnecki
# License::   BSD
#
# Rewritten to be more agnostic
module Viking
  class Akismet < Base
    class << self
      attr_accessor :valid_responses, :normal_responses, :standard_headers, 
                    :host, :port
    end

    self.host             = 'rest.akismet.com'
    self.port             = 80
    self.valid_responses  = Set.new(['false', ''])
    self.normal_responses = valid_responses.dup << 'true'
    self.standard_headers = {
      'User-Agent'   => "Viking (Ruby Gem) v#{Viking::VERSION::STRING}",
      'Content-Type' => 'application/x-www-form-urlencoded'
    }
  
    # Create a new instance of the Akismet class
    #
    # ==== Arguments
    # Arguments are provided in the form of a Hash with the following keys 
    # (as Symbols) available: 
    # 
    # +api_key+::    your Akismet API key
    # +blog+::       the blog associated with your api key
    # 
    # The following keys are available and are entirely optional. They are 
    # available incase communication with Akismet's servers requires a 
    # proxy port and/or host:
    # 
    # * +proxy_port+
    # * +proxy_host+
    def initialize(options)
      super
      self.verified_key = false
    end

    # Returns +true+ if the API key has been verified, +false+ otherwise
    def verified?
      (@verified_key ||= verify_api_key) != :false
    end

    # This is basically the core of everything. This call takes a number of 
    # arguments and characteristics about the submitted content and then 
    # returns a thumbs up or thumbs down. Almost everything is optional, but 
    # performance can drop dramatically if you exclude certain elements.
    #
    # ==== Arguments
    # +options+ <Hash>:: describes the comment being verified
    # 
    # The following keys are available for the +options+ hash:
    # 
    # +user_ip+ (*required*):: 
    #   IP address of the comment submitter.
    # +user_agent+ (*required*):: 
    #   user agent information.
    # +referrer+ (<i>note spelling</i>):: 
    #   the content of the HTTP_REFERER header should be sent here.
    # +permalink+:: 
    #   permanent location of the entry the comment was submitted to
    # +comment_type+::
    #   may be blank, comment, trackback, pingback, or a made up value like 
    #   "registration".
    # +comment_author+::
    #   submitted name with the comment
    # +comment_author_email+::
    #   submitted email address
    # +comment_author_url+::
    #   commenter URL
    # +comment_content+::
    #   the content that was submitted
    # Other server enviroment variables::
    #   In PHP there is an array of enviroment variables called <tt>_SERVER</tt> 
    #   which contains information about the web server itself as well as a 
    #   key/value for every HTTP header sent with the request. This data is 
    #   highly useful to Akismet as how the submited content interacts with 
    #   the server can be very telling, so please include as much information 
    #   as possible.
    def check_comment(options={})
      return false if invalid_options?
      message = call_akismet('comment-check', options)
      {:spam => !self.class.valid_responses.include?(message), :message => message}
    end
  
    # This call is for submitting comments that weren't marked as spam but 
    # should have been (i.e. false negatives). It takes identical arguments as 
    # +check_comment+. 
    def mark_as_spam(options={})
      return false if invalid_options?
      {:message => call_akismet('submit-spam', options)}
    end
  
    # This call is intended for the marking of false positives, things that 
    # were incorrectly marked as spam. It takes identical arguments as 
    # +check_comment+ and +mark_as_spam+.
    def mark_as_ham(options={})
      return false if invalid_options?
      {:message => call_akismet('submit-ham', options)}
    end
    
    # Returns the URL for an Akismet request
    # 
    # ==== Arguments
    # +action+ <~to_s>:: a valid Akismet function name
    # 
    # ==== Returns
    # String
    def self.url(action)
      "/1.1/#{action}"
    end

    protected
      # Internal call to Akismet. Prepares the data for posting to the Akismet 
      # service.
      #
      # ==== Arguments
      # +akismet_function+ <String>::
      #   the Akismet function that should be called
      # 
      # The following keys are available to configure a given call to Akismet: 
      # 
      # +user_ip+ (*required*)::
      #   IP address of the comment submitter.
      # +user_agent+ (*required*)::
      #   user agent information.
      # +referrer+ (<i>note spelling</i>)::
      #   the content of the HTTP_REFERER header should be sent here.
      # +permalink+::
      #   the permanent location of the entry the comment was submitted to.
      # +comment_type+::
      #   may be blank, comment, trackback, pingback, or a made up value like 
      #   "registration".
      # +comment_author+::
      #   submitted name with the comment
      # +comment_author_email+::
      #   submitted email address
      # +comment_author_url+::
      #   commenter URL
      # +comment_content+::
      #   the content that was submitted
      # Other server enviroment variables::
      #   In PHP there is an array of enviroment variables called <tt>_SERVER</tt> 
      #   which contains information about the web server itself as well as a 
      #   key/value for every HTTP header sent with the request. This data is 
      #   highly useful to Akismet as how the submited content interacts with 
      #   the server can be very telling, so please include as much information 
      #   as possible.
      def call_akismet(akismet_function, options={})
        http_post(
          Net::HTTP.new([options[:api_key], self.class.host].join("."), options[:proxy_host], options[:proxy_port]), 
          akismet_function, 
          options.update(:blog => options[:blog]).to_query
        )
      end

      # Call to check and verify your API key. You may then call the 
      # <tt>verified?</tt> method to see if your key has been validated
      def verify_api_key
        return :false if invalid_options?
        value = http_post(
          Net::HTTP.new(self.class.host, self.class.port, options[:proxy_host], options[:proxy_port]), 
          'verify-key', 
          { 
            :key  => options[:api_key], 
            :blog => options[:blog] 
          }.to_query
        )
        self.verified_key = (value == "valid") ? true : :false
      end
      
      def http_post(http, action, data)
        resp = http.post(self.url(action), data, self.class.standard_headers)
        log_request(self.url(action), data, resp)
        resp.body
      end
      
      def url(action)
        "/1.1/#{action}"
      end
      
    private
      attr_accessor :verified_key
  end
end