=begin
  ASF-REST-Adapter
  Copyright 2010 Raymond Gao @ http://are4.us

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
=end

require 'net/https'
require 'net/http'
require 'active_resource'
require 'httparty'
require 'rforce'


module Salesforce
  module Rest
    # This is the mother class of all Salesforce REST objects
    # all subclasses need to set the collection name. In ActiveResource convention,
    # pluralized elements has the ending 's'; whereas, in Force.com REST, that 's'
    # is not there.
    # e.g.
    # set_collection_name "User"
    #
    # TODO cannot do "SObject.find(:all)" due to a defect in the ActiveResource framework,
    # see -> ActiveResource::Base line # 885
    #  def instantiate_collection(collection, prefix_options = {})
    #        collection.collect! { |record| instantiate_record(record, prefix_options) }
    #  end
    # As Ruby Hash has not collect! method, only Array,
    # We we get back from Salesforce is a hash
    # <sobject><objectDescribe><.....></objectDescribe><recentItems>...</recentItems></sobject>
    class AsfRest < ActiveResource::Base
      include HTTParty

      # default REST API server for HTTParty
      base_uri "https://na7.salesforce.com"
      default_params :output => 'json'
      format :json

      #ActiveResource setting
      self.site = "https://na7.salesforce.com/services/data/v21.0/sobjects"

      # set header for httparty
      def self.set_headers (auth_setting)
        headers (auth_setting)
      end

      # Loading the Authenticate module
      require File.dirname(__FILE__) + '/asf_rest_authenticate.rb'
      include Authenticate

      # Loading the Call Remote module
      require File.dirname(__FILE__) + '/asf_rest_call_rest_svr.rb'
      include CallRemote

      # Loading the OrgModel module
      require File.dirname(__FILE__) + '/asf_rest_org_model.rb'
      include OrgModel

      # Loading the CachedCalls module
      require File.dirname(__FILE__) + '/asf_rest_cached_calls.rb'
      include CachedCalls

      # We are mocking OAuth type authentication. In our case, we use the
      # SessionID obtained from the initial SOAP Web Services call - 'login()'
      # OAuth2 is geared toward website to website authentication.
      # In our case, we are the background data interchange between RoR app and
      # Force.com database. Therefore, we use security id.
      # example:
      # connection.set_header("Authorization",  'OAuth 00DA0000000XwIQ!AQIAQD_BX.pdxMz0YBKdkz45PijY0gMxH65JwvV6Yj4.hf44WJYqO9ug7DfhNbnxuO9buhbftiX9Qv5DyBLHauaJhqTh79vi')
      #
      # self.abstract_class = true
      #
      # Setup the adapter
      def self.setup(oauth_token, rest_svr, api_version)
        @@oauth_token = oauth_token
        @@rest_svr = rest_svr
        @@api_version = api_version ? api_version : "v21.0"  #take a dynamic api server version
        @@rest_svr_url = rest_svr + "/services/data/#{api_version}/sobjects"
        @@ssl_port = 443  # TODO, right SF use port 443 for all HTTPS traffic.

        #ActiveResource setting
        #self.site = "https://" +  @@rest_svr_url
        self.site = @@rest_svr_url
        connection.set_header("Authorization", "OAuth " + @@oauth_token)

        # To be used by HTTParty
        @@auth_header = {
          "Authorization" => "OAuth " + @@oauth_token,
          "content-Type" => 'application/json'
        }
        # either application/xml or application/json
        base_uri rest_svr
        self.format = :json

        return self
      end

      #Save the Object, Note: there is an inconsistency between the Salesforce REST
      #JSON create object, which is just {"Name1":"value1","Name2":"value2"}
      #where as the 'save' method of the ActiveResource produces a JSON of
      #{"Object Name":{"Name1":"value1","Name2":"value2"}}.
      #The Extra/missing 'Object Name' causes this to break.
      #When this consistency is resolved, this method should be removed.
      # header = {
      #    "Authorization" => "OAuth " + @@oauth_token,
      #    "content-Type" => 'application/json'
      #  }
      # rest_svr = 'https://na7.salesforce.com'
      # api_version = 'v21.0' with v prefix
      def save(header=Salesforce::Rest::AsfRest.send(:class_variable_get, "@@auth_header"),
          rest_svr=Salesforce::Rest::AsfRest.send(:class_variable_get, "@@rest_svr"),
          api_version=Salesforce::Rest::AsfRest.send(:class_variable_get, "@@api_version"))
        class_name = self.class.name.gsub(/\S+::/mi, "")
        path = "/services/data/#{api_version}/sobjects/#{class_name}/"
        target = rest_svr + path
        data = ActiveSupport::JSON::encode(attributes)

        resp = call_rest_svr("POST", target, header, data)

        # HTTP code 201 means it was successfully saved.
        if resp.code != 201
          message = ActiveSupport::JSON.decode(resp.body)[0]["message"]
          Salesforce::Rest::ErrorManager.raise_error("HTTP code " + resp.code.to_s + ": " + message, resp.code.to_s)
        else
          return resp
        end
      end

      #Again the delete feature from ActiveResource does not work out of the box.
      #Using custom delete function
      def self.delete(id, header=Salesforce::Rest::AsfRest.send(:class_variable_get, "@@auth_header"),
          rest_svr=Salesforce::Rest::AsfRest.send(:class_variable_get, "@@rest_svr"),
          api_version=Salesforce::Rest::AsfRest.send(:class_variable_get, "@@api_version"))
        class_name = self.name.gsub(/\S+::/mi, "")
        path = "/services/data/#{api_version}/sobjects/#{class_name}/#{id}"
        target = rest_svr + path
        resp = call_rest_svr("DELETE", target, header)

        # HTTP code 204 means it was successfully deleted.
        if resp.code != 204
          message = ActiveSupport::JSON.decode(resp.body)[0]["message"]
          Salesforce::Rest::ErrorManager.raise_error("HTTP code " + resp.code.to_s + ": " + message, resp.code.to_s)
        else
          return resp
        end
      end

      #Custom object with PATCH method
      class TrackRequest < Net::HTTPRequest
        METHOD = 'PATCH'
        REQUEST_HAS_BODY = true
        RESPONSE_HAS_BODY = true
      end

      #Update an object # TODO to use the call_rest_svr method
      def self.update(id, serialized_data_json, header=Salesforce::Rest::AsfRest.send(:class_variable_get, "@@auth_header"),
          rest_svr=Salesforce::Rest::AsfRest.send(:class_variable_get, "@@rest_svr"),
          api_version=Salesforce::Rest::AsfRest.send(:class_variable_get, "@@api_version"))

        #Again the delete feature from ActiveResource does not work out of the box.
        #Providing a custom update function
        svr_url_4_http = rest_svr.gsub(/https:\/\//mi, "" )  #strip http:// prefix from the url. Otherwise, it will fail.
        http = Net::HTTP.new(svr_url_4_http, @@ssl_port)
        http.use_ssl = true
        class_name = self.name.gsub(/\S+::/mi, "")
        path = "/services/data/#{api_version}/sobjects/#{class_name}/#{id}"
        code = serialized_data_json
        #   format -> Net::HTTPGenericRequest.new(m, reqbody, resbody, path, initheader)
        req = Net::HTTPGenericRequest.new("PATCH", true, true, path, header)
        resp = http.request(req, code) { |response|  }

        # HTTP code 204 means it was successfully updated. 204 for httparty, '204' for Net::HTTP
        if resp.code != '204'
          message = ActiveSupport::JSON.decode(resp.body)[0]["message"]
          Salesforce::Rest::ErrorManager.raise_error("HTTP code " + resp.code.to_s + ": " + message, resp.code.to_s)
        else
          return resp
        end
      end

      # Run SOQL, automatically CGI::escape the query for you.
      def self.run_soql(query, header=Salesforce::Rest::AsfRest.send(:class_variable_get, "@@auth_header"),
          rest_svr=Salesforce::Rest::AsfRest.send(:class_variable_get, "@@rest_svr"),
          api_version=Salesforce::Rest::AsfRest.send(:class_variable_get, "@@api_version"))
        class_name = self.name.gsub(/\S+::/mi, "")
        safe_query = CGI::escape(query)
        path = "/services/data/#{api_version}/query?q=#{safe_query}"
        target = rest_svr+path
        resp = call_rest_svr("GET", target, header)
        #resp = get(path, options)
        if (resp.code != 200) || !resp.success?
          message = ActiveSupport::JSON.decode(resp.body)[0]["message"]
          Salesforce::Rest::ErrorManager.raise_error("HTTP code " + resp.code.to_s + ": " + message, resp.code.to_s)
        end
        return resp
      end

      # Run SOQL, automatically CGI::escape the query for you.
      # This is with given credentials -> query, security_token, rest_svr, version
      # the path with appropriate api_version, CGI escaping the query string is
      # included in this method.
      def self.run_soql_with_credential(query, security_token, rest_svr, api_version)
        header = { "Authorization" => "OAuth " + security_token, "content-Type" => 'application/json' }
        #set the path with appropriate api_version, include CGI escaping the query string
        safe_query = CGI::escape(query)
        path = "/services/data/#{api_version}/query?q=#{safe_query}"
        target = rest_svr + path
        #get the result
        resp = call_rest_svr("GET", target, header)
        if (resp.code != 200) || !resp.success?
          message = ActiveSupport::JSON.decode(resp.body)[0]["message"]
          Salesforce::Rest::ErrorManager.raise_error("HTTP code " + resp.code.to_s + ": " + message, resp.code.to_s)
        end
        return resp
      end


      # Run SOSL, do not use CGI::escape -> SF will complain about missing {braces}
      def self.run_sosl(search, header=Salesforce::Rest::AsfRest.send(:class_variable_get, "@@auth_header"),
          rest_svr=Salesforce::Rest::AsfRest.send(:class_variable_get, "@@rest_svr"),
          api_version=Salesforce::Rest::AsfRest.send(:class_variable_get, "@@api_version"))
        options = { :query => {:q => search}}
        class_name = self.name.gsub(/\S+::/mi, "")
        path = URI.escape("/services/data/#{api_version}/search/?q=#{search}")
        target = rest_svr + path
        resp = call_rest_svr("GET", target, header)
        if (resp.code != 200) || !resp.success?
          message = ActiveSupport::JSON.decode(resp.body)[0]["message"]
          Salesforce::Rest::ErrorManager.raise_error("HTTP code " + resp.code.to_s + ": " + message, resp.code.to_s)
        end
        return resp
      end

      # Run SOSL, do not use CGI::escape -> SF will complain about missing {braces}
      # This is with given credentials -> Search_query, security_token, rest_svr, version
      def self.run_sosl_with_credential(search, security_token, rest_svr, api_version)
        header = { "Authorization" => "OAuth " + security_token, "content-Type" => 'application/json' }
        #set the path with appropriate api_version, with the search string
        path = URI.escape("/services/data/#{api_version}/search/?q=#{search}")
        target = rest_svr + path
        #get the result
        resp = call_rest_svr("GET", target, header)
        if (resp.code != 200) || !resp.success?
          message = ActiveSupport::JSON.decode(resp.body)[0]["message"]
          Salesforce::Rest::ErrorManager.raise_error("HTTP code " + resp.code.to_s + ": " + message, resp.code.to_s)
        end
        return resp
      end

      # Used for removing the .xml and .json extensions at the end of the URL link.
      class << self
        # removing http://....../UID.xml
        def element_path(id, prefix_options = {}, query_options = nil)
          prefix_options, query_options = split_options(prefix_options) if query_options.nil?
          "#{prefix(prefix_options)}#{collection_name}/#{id}#{query_string(query_options)}"
        end
        # removing http://....../UID.json
        def collection_path(prefix_options = {}, query_options = nil)
          prefix_options, query_options = split_options(prefix_options) if query_options.nil?
          "#{prefix(prefix_options)}#{collection_name}#{query_string(query_options)}"
        end
      end
    end
  end
end