#------------------------------------------------------------------------------
# lib/seeker/dice_response.rb
#------------------------------------------------------------------------------
module Seeker  
  ##
  # DiceResponse
  #
  # Parse the results returned from the job search results on Dice.com 
  # generated by scraping the website with the mechanize gem. Return a 
  # standard format response message.
  #
  class DiceResponse
    attr_reader :response, :msg
    
    ##
    # Initialize the object
    #
    def initialize(response)
      @response       = response
      @msg            = parse_and_map_response
    end
    
    ##
    # Verify the http request was successful
    #
    def http_error?
      return false  if Integer(@response.code) >= 200 && Integer(@response.code) < 300
      return true
    end
    
    ##
    # Verify the Dice response is OK
    #
    def ok?
      return false  if http_error?
      return true
    end
    
    ##
    # Return the error message
    #
    def error_msg
      if http_error?
        return  "HTTP Error, code=[#{@msg[:header][:http][:code]}], " + 
                "msg=[#{@msg[:header][:http][:msg]}]"
      else
        return  "Dice Success, code=[#{@msg[:header][:error][:code]}], " + 
                "msg=[#{@msg[:header][:error][:msg]}]"
      end
    end
  
    #----------------------------------------------------------------------------
    # Private
    #----------------------------------------------------------------------------
    private  
      ##
      # Parse the returned job search results and build the output message
      #
      def parse_and_map_response
        message = {}
      
        if http_error?
          message = {
            header: {
              site_name:      Seeker::Dice.site_name,
              http:           {
                                code: Integer(@response.code),
                                msg:  @response.message
                              },
              error:          {
                                code: 1000 + Integer(@response.code),
                                msg:  "HTTP Error - #{@response.message}"
                              }
            },
            body: {
              jobs:           []
            }
          }
        elsif get_total_jobs == 0
          #
          # Search returned zero jobs, Dice shows an error page so have to 
          # manually process the results
          #
          message = {
            header: {
              site_name:      Seeker::Dice.site_name,
              http:           {
                                code: Integer(@response.code),
                                msg:  "OK"
                              },
              error:          {
                                code: 0,
                                msg:  "OK"
                              },
              total_results:  0,
              description:    @response.form_with(id: "searchJob")["q"],
              location:       @response.form_with(id: "searchJob")["l"],
              radius:         0,
              start:          0,
              end:            0,
              page:           0
            },
            body: {
              jobs:           []
            }
          }
        else
          # Successfully made API call, ok to convert response to json
          message = {
            header: {
              site_name:      Seeker::Dice.site_name,
              http:           {
                                code: Integer(@response.code),
                                msg:  "OK"
                              },
              error:          {
                                code: 0,
                                msg:  "OK"
                              },
              total_results:  get_total_jobs,
              description:    @response.form_with(id: "searchJob")["q"],
              location:       @response.form_with(id: "searchJob")["l"],
              radius:         get_radius,
              start:          get_start_pos,
              end:            get_last_pos,
              page:           @response.search("#pageNo")[0]["value"]
            },
            body: {
              jobs:           get_jobs
            }
          }  
        end
      
        return message
      
      end # end of parse_and_map_response
      
      ##
      # Parse out the radius, if it is nil return blank string
      #
      def get_radius
        radius_hidden_field = @response.search("#radius")
        
        if radius_hidden_field.nil? || radius_hidden_field.empty?
          return ""
        end
        
        return radius_hidden_field[0]["value"]
      end
      
      ##
      # Parse out the jobs returned in the response and build an array of
      # jobs matching dogpatch format that will be added
      # to the message body.
      #
      def get_jobs
        job_list_node = @response.search("#search-results-control").search(".serp-result-content")
        
        return [] if zero_jobs?(job_list_node)
        
        jobs = job_list_node.map do |job|
          {
            title:                get_title(          job ),
            company:              get_company(        job ), 
            job_url:              get_job_url(        job ),
            formatted_location:   get_location(       job ),
            posted:               get_posted_date(    job ),
            snippet:              get_snippet(        job ),
            key:                  get_key(            job ),
            uuid:                 get_uuid(           job ),
            search_site_job_id:   get_search_site_id( job ),
            job_search_site:      "dice"
          }
        end
        
        return jobs
      end
      
      ##
      # Parse out the job fields from the HTML page
      #
      def get_title(job)
        job.search("h3").text.strip
      end
      
      def get_company(job)
        job.search("li.employer span.hidden-xs").text
      end
      
      def get_job_url(job)
        job.search("h3").search("a")[0].attributes["href"].value
      end
      
      def get_location(job)
        job.search("li.location").text.strip
      end
      
      def get_posted_date(job)
        parse_posted(job.search("li.posted").text.strip)
      end
      
      def get_snippet(job)
        job.search(".shortdesc").text.strip
      end

      ##
      # Create unique job key from the job posting url
      #
      def get_key(job)
        job_url = get_job_url(job)
        key     = "dice_" + Base64.urlsafe_encode64(CGI.unescape(job_url)).slice(0..249)
        
        return  key
      end
      
      ##
      # Generate a uuid using the job title, company, and location, which will be used
      # to search for duplicate jobs
      #
      def get_uuid(job)
        title     = get_title(    job )
        company   = get_company(  job )
        location  = get_location( job )
        
        uuid      = Seeker::Utils.uuid(title, company, location)
      end
      
      ##
      # ID for each job scraped from Dice. Typically it is the ID returned by the API
      # to identify a job, but since we scrape Dice I am creating one from the job
      # posting url
      #
      def get_search_site_id(job)
        job_url = get_job_url(job)
        id      = Base64.urlsafe_encode64(CGI.unescape(job_url)).slice(0..254)
        
        return  id
      end
      
      ##
      # Return true if the search returned zero jobs, otherwise return false
      #
      def zero_jobs?(job_list_node)
        return true   if job_list_node.length == 0
        return false
      end
      
      ##
      # Parse the date strings such as "1 day ago, 35 minutes ago, ..." to a 
      # date
      #
      def parse_posted(date_str)
        posted = 0
  
        if date_str    =~ /moments/
          posted    =  0
        elsif date_str =~ /ago/
          n         =  Integer(date_str.split[0].strip)
          time_ago  =  date_str.split[1].strip.to_sym
          posted    =  n.send(time_ago)
        end
  
        return (Time.now - posted)
      end 
    
      ##
      # Utility method that returns the <h4> that contains the total number of
      # jobs and the starting and ending positions. If the method does not find
      # the node then it return an empty array, []
      #
      def get_total_jobs_header
        @response.search(".jobs-page-header").search(".hidden-xs").search("h4.posiCount")        
      end
    
      ##
      # Parse out the total number of jobs from the header
      #
      def get_total_jobs
        header = get_total_jobs_header
         
        return Integer(header.search("span").last.text.strip) unless header.empty?
        return 0
      end
    
      ##
      # Parse out the position of the first job in response
      #
      def get_start_pos
        get_total_jobs_header.search("span").first.text.split('-')[0].strip
      end
    
      ##
      # Parse out position of last job returned in response
      #
      def get_last_pos
        get_total_jobs_header.search("span").first.text.split('-')[-1].strip
      end
  
  end # end of class DiceResponse
  
end # end of module Seeker