module Readability

  class ::String
    def remove_extra_spaces
      self.gsub(/[[:space:]]+/,' ').chomp
    end
    def remove_controls
      self.gsub(/[[:cntrl:]]/,'').chomp
    end
  end

  class Analyzer

    attr_reader :options, :html

    def initialize(opts = {})
      @options, @html = opts[:options], opts[:html]
    end

    # ToDo: replace with reduce
    #def identifier_weight(elem)
    #  {POSITIVE_IDENTIFIERS => 25, NEGATIVE_IDENTIFIERS => -25}.reduce(0) do |weight, pair|
    #    regex, score = pair
    #    (weight += score if "#{elem['class']}+#{elem['id']}" =~ regex) or weight
    #  end
    #end
    def weight_for_attr(attr, score)
      weight = 0
      if attr && attr != ""
        if attr =~ REGEXES[:negativeRe]
          weight = -score
        end

        matches = attr.scan(REGEXES[:positiveRe]).flatten.compact
        unless matches.empty?
          weight = score * matches.count
        end

        matches = attr.scan(REGEXES[:unlikelyCandidatesRe]).flatten.compact
        unless matches.empty?
          weight = -(score * matches.count)
        end
      end
      weight
    end

    def score_elem_attrs(elem)
      weight = 0
      return weight unless @weight_classes
      {id: 25, class: 25, name: 25, summary: 25, href: 25, src: 25}.each do |attr, score|
        weight += weight_for_attr(elem[attr], score)
      end
      weight
    end

    def score_node(elem)
      content_score = score_elem_attrs(elem)
      link_density = get_link_density(elem)
      link_content_weight = get_link_content_weight(elem)
      #img & a jpeg|png - score
      # attrs = elem.xpath("//a[contains(@href,'photo') or contains(@href,'pic') or contains(@href,'image') or contains(@href,'.jpg') or contains(@href,'.jpeg') or contains(@href,'.png')]").map(&:attributes)
      # attrs = elem.xpath("//img[contains(@href,'.jpg') or contains(@href,'.jpeg') or contains(@href,'.png')]").map(&:attributes)
      case elem.name.downcase
        when "div"
          content_score += 5
          content_score += 2*elem.css('span').count
          content_score += 5 if link_density < 0.01
        when "blockquote"
          content_score += 3
        when "form"
          content_score -= 3
        when "tr"
          content_score += 5
          children = elem.children
          content_score += 5 if (children.css('dt').count<=3 || (children.css('th').present? && children.css('td').present?))
        when "td"
          content_score += 5
        when "table"
          content_score += 5
          content_score += 2*elem.children.css('tr').count
          content_score += 10 if link_density < 0.01
          #if elem.content.length < options[:min_text_length] || elem.css('tr').count < 3
          #    content_score = -5*elem.children.css('tr').count
          #end
        when "ul"
          content_score += 5
          # content_score -= 5*elem.children.css('a').count
          content_score += 10 if link_density < 0.01
        when "li"
          content_score += 5
        when "span"
          content_score += 5
      end
      {content_score: content_score, elem: elem, link_density: link_density, link_content_weight: link_content_weight}
    end

# def score_paragraphs
    def score_nodes_candidates
      candidates = {}
      @html.css(options[:score_nodes]).each do |elem|
        parent_node = elem.parent
        grand_parent_node = parent_node.respond_to?(:parent) ? parent_node.parent : nil
        # grand_grand_parent_node = grand_parent_node.respond_to?(:parent) ? grand_parent_node.parent : nil
        inner_text = elem.text

        # If this paragraph is less than 25 characters, don't even count it.
        next if (inner_text.length < options[:min_text_length])

        candidates[parent_node] ||= score_node(parent_node)
        candidates[grand_parent_node] ||= score_node(grand_parent_node) if grand_parent_node
        # candidates[grand_grand_parent_node] ||= score_node(grand_grand_parent_node) if grand_grand_parent_node

        collect_attrs(candidates, parent_node)
        collect_attrs(candidates, grand_parent_node)
        # collect_attrs(candidates, grand_grand_parent_node)

        content_score = 1
        content_score += inner_text.split(options[:score_nodes_text_splitters]).length
        # content_score += [(inner_text.length / 100).to_i, 3].min

        candidates[parent_node][:content_score] += content_score
        candidates[grand_parent_node][:content_score] += content_score*0.75+score_elem_attrs(grand_parent_node) if grand_parent_node
        # candidates[grand_grand_parent_node][:content_score] += content_score*0.5+score_elem_attrs(grand_grand_parent_node) if grand_grand_parent_node

        candidates[parent_node][:link_density] += get_link_density(parent_node)
        candidates[grand_parent_node][:link_density] += get_link_density(grand_parent_node) / grand_parent_node.children.count if grand_parent_node
        # candidates[grand_grand_parent_node][:link_density] += get_link_density(grand_grand_parent_node) / grand_grand_parent_node.children.count if grand_grand_parent_node

        candidates[parent_node][:link_content_weight] += get_link_content_weight(parent_node)
        candidates[grand_parent_node][:link_content_weight] += get_link_content_weight(grand_parent_node) / grand_parent_node.children.count if grand_parent_node
        # candidates[grand_grand_parent_node][:link_content_weight] += get_link_content_weight(grand_grand_parent_node) / grand_grand_parent_node.children.count if grand_grand_parent_node

      end

      # Scale the final candidates score based on link density. Good content should have a
      # relatively small link density (5% or less) and be mostly unaffected by this operation.
      candidates.each do |elem, candidate|
        candidate[:content_score] = candidate[:content_score] * (1 - get_link_density(elem))
        candidate[:content_score] = candidate[:content_score] * (1 - get_link_content_weight(elem))
      end

      candidates
    end

    def collect_attrs(candidates, elem)
      return if elem.nil?
      if elem.respond_to?(:attributes)
        candidates[elem][:attrs] ||= elem.attributes.select{|a| a if options[:score_nodes_attrs].include? a}
      else
        candidates[elem][:attrs] ||= 'Docum'
      end
    end

    def get_link_density(elem)
      links_text_length = elem.css("a").map(&:text).join('').remove_extra_spaces.length
      links_qty = elem.css("a").count
      text_length = elem.text.remove_extra_spaces.length
      (links_qty + 1e-3) / (text_length.to_f - links_text_length + 1e-3)
    rescue
      1e-3
    end

    def get_link_content_weight(elem)
      links_text_length = elem.css("a").map(&:text).join('').remove_extra_spaces.length
      text_length = elem.text.remove_extra_spaces.length
      (links_text_length + 1e-3) / (text_length.to_f + 1e-3)
    rescue
      1e-3
    end


  end
end
