# 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.

module Buildr #:nodoc:

  # :call-seq:
  #   struct(hash) => Struct
  #
  # Convenience method for creating an anonymous Struct.
  #
  # For example:
  #   COMMONS             = struct(
  #     :collections      =>'commons-collections:commons-collections:jar:3.1',
  #     :lang             =>'commons-lang:commons-lang:jar:2.1',
  #     :logging          =>'commons-logging:commons-logging:jar:1.0.3',
  #   )
  #
  #   compile.with COMMONS.logging
  def struct(hash)
    Struct.new(nil, *hash.keys).new(*hash.values)
  end

  # :call-seq:
  #   write(name, content)
  #   write(name) { ... }
  #
  # Write the contents into a file. The second form calls the block and writes the result.
  #
  # For example:
  #   write 'TIMESTAMP', Time.now
  #   write('TIMESTAMP') { Time.now }
  #
  # Yields to the block before writing the file, so you can chain read and write together.
  # For example:
  #   write('README') { read('README').sub("${build}", Time.now) }
  def write(name, content = nil)
    filename = name.to_s
    mkpath File.dirname(filename)
    content = yield if block_given?
    File.open(filename, 'wb') { |file| file.write content.to_s }
    content.to_s
  end

  # :call-seq:
  #   read(args) => string
  #   read(args) { |string| ... } => result
  #
  # Reads and returns the contents of a file. The second form yields to the block and returns
  # the result of the block. The args passed to read are passed on to File.open.
  #
  # For example:
  #   puts read('README')
  #   read('README') { |text| puts text }
  def read(*args)
    args[0] = args[0].to_s
    contents = File.open(*args) { |f| f.read }
    if block_given?
      yield contents
    else
      contents
    end
  end

  # :call-seq:
  #    download(url_or_uri) => task
  #    download(path=>url_or_uri) =>task
  #
  # Create a task that will download a file from a URL.
  #
  # Takes a single argument, a hash with one pair. The key is the file being
  # created, the value if the URL to download. The task executes only if the
  # file does not exist; the URL is not checked for updates.
  #
  # The task will show download progress on the console; if there are MD5/SHA1
  # checksums on the server it will verify the download before saving it.
  #
  # For example:
  #   download 'image.jpg'=>'http://example.com/theme/image.jpg'
  def download(args)
    args = URI.parse(args) if String === args
    if URI === args
      # Given only a download URL, download into a temporary file.
      # You can infer the file from task name.
      temp = Tempfile.open(File.basename(args.to_s))
      file(temp.path).tap do |task|
        # Since temporary file exists, force a download.
        class << task ; def needed? ; true ; end ; end
        task.sources << args
        task.enhance { args.download temp }
      end
    else
      # Download to a file created by the task.
      fail unless args.keys.size == 1
      uri = URI.parse(args.values.first.to_s)
      key = args.keys.first
      if key.is_a?(Buildr::Artifact)
        if RUBY_VERSION < '1.9.0'
          class << key
            def singleton_class
              class << self
                self
              end
            end

            def define_singleton_method(name, &block)
              self.singleton_class.send(:define_method, name, &block)
            end
          end
        end

        key.define_singleton_method(:source) do
          uri
        end
        key.define_singleton_method(:download) do
          trace "Downloading #{to_spec}"
          begin
            download_artifact(uri)
            true
          rescue URI::NotFoundError
            false
          rescue Exception => error
            info error
            trace error.backtrace.join("\n")
            false
          end || fail_download([])
        end
        key
      else
        file(key.to_s).tap do |task|
          task.sources << uri
          task.enhance { uri.download task.name }
        end
      end
    end
  end

  # A file task that concatenates all its prerequisites to create a new file.
  #
  # For example:
  #   concat("master.sql"=>["users.sql", "orders.sql", reports.sql"]
  #
  # See also Buildr#concat.
  class ConcatTask < Rake::FileTask
    def initialize(*args) #:nodoc:
      super
      enhance do |task|
        content = prerequisites.inject("") do |content, prereq|
          content << File.read(prereq.to_s) if File.exists?(prereq) && !File.directory?(prereq)
          content
        end
        File.open(task.name, "wb") { |file| file.write content }
      end
    end
  end

  # :call-seq:
  #    concat(target=>files) => task
  #
  # Creates and returns a file task that concatenates all its prerequisites to create
  # a new file. See #ConcatTask.
  #
  # For example:
  #   concat("master.sql"=>["users.sql", "orders.sql", reports.sql"]
  def concat(args)
    file, arg_names, deps = Buildr.application.resolve_args([args])
    ConcatTask.define_task(File.expand_path(file)=>deps)
  end

end
