# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with this
# work for additional information regarding copyright ownership.  The ASF
# licenses this file to you 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.

autoload :Hpricot, 'hpricot'

module Buildr #:nodoc:

  # Search best artifact version from remote repositories
  module ArtifactSearch
    extend self

    def include(method = nil)
      (@includes ||= []).tap { push method if method }
    end

    def exclude(method = nil)
      (@excludes ||= []).tap { push method if method }
    end

    # TODO: return the url for best matching repo
    def best_version(spec, *methods)
      spec = Artifact.to_hash(spec)
      spec[:version] = requirement = VersionRequirement.create(spec[:version])
      select = lambda do |candidates|
        candidates.find { |candidate| requirement.satisfied_by?(candidate) }
      end
      result = nil
      methods = search_methods if methods.empty?
      if requirement.composed?
        until result || methods.empty?
          method = methods.shift
          type = method.keys.first
          from = method[type]
          if (include.empty? || !(include & [:all, type, from]).empty?) &&
              (exclude & [:all, type, from]).empty?
            if from.respond_to?(:call)
              versions = from.call(spec.dup)
            else
              versions = send("#{type}_versions", spec.dup, *from)
            end
            result = select[versions]
          end
        end
      end
      result ||= requirement.default
      raise "Could not find #{Artifact.to_spec(spec)}"  +
        "\n You may need to use an specific version instead of a requirement" unless result
      spec.merge :version => result
    end

    def requirement?(spec)
      VersionRequirement.requirement?(spec[:version])
    end

    private
    def search_methods
      [].tap do
        push :runtime => [Artifact.list]
        push :local => Buildr.repositories.local
        Buildr.repositories.remote.each { |remote| push :remote => remote }
        push :mvnrepository => []
      end
    end

    def depend_version(spec)
      spec[:version][/[\w\.]+/]
    end

    def runtime_versions(spec, artifacts)
      spec_classif = spec.values_at(:group, :id, :type)
      artifacts.inject([]) do |in_memory, str|
        candidate = Artifact.to_hash(str)
        if spec_classif == candidate.values_at(:group, :id, :type)
          in_memory << candidate[:version]
        end
        in_memory
      end
    end

    def local_versions(spec, repo)
      path = (spec[:group].split(/\./) + [spec[:id]]).flatten.join('/')
      Dir[File.expand_path(path + "/*", repo)].map { |d| d.pathmap("%f") }.sort.reverse
    end

    def remote_versions(art, base, from = :metadata, fallback = true)
      path = (art[:group].split(/\./) + [art[:id]]).flatten.join('/')
      base ||= "https://repo1.maven.org/maven2"
      uris = {:metadata => "#{base}/#{path}/maven-metadata.xml"}
      uris[:listing] = "#{base}/#{path}/" if base =~ /^https?:/
        xml = nil
      until xml || uris.empty?
        begin
          xml = URI.read(uris.delete(from))
        rescue URI::NotFoundError => e
          from = fallback ? uris.keys.first : nil
        end
      end
      return [] unless xml
      doc = Hpricot(xml)
      case from
      when :metadata then
        doc.search("versions/version").map(&:innerHTML).reverse
      when :listing then
        doc.search("a[@href]").inject([]) { |vers, a|
          vers << a.innerHTML.chop if a.innerHTML[-1..-1] == '/'
          vers
        }.sort.reverse
      else
        fail "Don't know how to parse #{from}: \n#{xml.inspect}"
      end
    end

    def mvnrepository_versions(art)
      uri = "http://www.mvnrepository.com/artifact/#{art[:group]}/#{art[:id]}"
      xml = begin
              URI.read(uri)
            rescue URI::NotFoundError => e
              puts e.class, e
              return []
            end
      doc = Hpricot(xml)
      doc.search("table.grid/tr/td[1]/a").map(&:innerHTML)
    end

  end # ArtifactSearch
end
