
module Sprout

  ##
  # Sprout Libraries provide support for sharing and versioning raw or 
  # pre-compiled source code across projects.
  #
  # Sprout Libraries give us the ability to include raw (or pre-compiled) source
  # code directly within a Ruby Gem, or to refer to an archive somewhere on
  # the internet.
  #
  # A Sprout Library is made up of two components:
  #
  # * Specification: The description of the library
  # * Source Code: Raw or pre-compiled (swc, jar, abc)
  #
  # = Specification
  #
  # Libraries can be added to local or remote file targets in a 
  # Sprout::Specification. When calling add_library, one must provide the 
  # library name (symbol that will be used from Rake) and a relative path (or 
  # Array of paths) from the Specification to the library file or directory.
  #
  # Following is an example of a Sprout::Specification that registers a SWC 
  # that will be distributed directly within a Ruby Gem:
  #
  #   Sprout::Specification.new do |s|
  #     s.name = "asunit4"
  #     s.version = "4.4.2"
  #     s.add_file_target do |f|
  #       f.add_library :swc, File.join('bin', "AsUnit-4.4.2.swc")
  #     end
  #   end
  #
  # Following is an example of a Sprout::Specification that registers a ZIP
  # archive that will be distributed separately from the Ruby Gem:
  #
  #   Sprout::Specification.new do |s|
  #     s.name = "asunit3"
  #     s.version = "3.0.0"
  #     s.add_remote_file_target do |f|
  #       f.url = "https://github.com/patternpark/asunit/tree/3.0.0"
  #       f.md5 = "abcdefghijklmnopqrstuvwxyz"
  #       f.add_library :swc, "bin/AsUnit-3.0.1.zip"
  #     end
  #   end
  #
  # Libraries can be consumed from any Rakefile without concern for how
  # the source code is distributed. Following is an example Rake task
  # that uses the AsUnit 4.0 Library:
  #
  #   # Define the library Rake::Task:
  #   library :asunit4
  #
  #   # Add the library as a dependency from another task
  #   # that should know how to properly associate the files:
  #   mxmlc 'bin/SomeProject.swf' => :asunit4 do |t|
  #     t.input = 'src/SomeProject.as'
  #   end
  #
  # When the library task is executed, the library should be resolved and 
  # expanded into the project. When the mxmlc task is executed, the installed 
  # library should be associated with the compilation command.
  #
  # Some library Specifications include multiple targets within the same Specification.
  #
  # One good example of such a Spec, is the FlashSDK :flex4 Specification.
  #
  # Following is a truncated version of that Spec:
  #
  #   Sprout::Specification.new do |s|
  #     s.name    = 'flex4'
  #     s.version = '4.1.0.16076'
  #     s.add_remote_file_target do |t|
  #       t.platform     = :universal
  #       t.archive_type = :zip
  #       t.url          = "http://fpdownload.adobe.com/pub/flex/sdk/builds/flex4/flex_sdk_4.1.0.16076.zip"
  #       t.md5          = "4c5f3d3fa4e1f5be244679210cd852c0"
  #       t.add_library :flex,         "frameworks/libs/flex.swc"
  #       t.add_library :flex4,        "frameworks/libs/flex4.swc"
  #       t.add_library :f_textlayout, "frameworks/libs/framework_textLayout.swc"
  #       t.add_library :framework,    "frameworks/libs/framework.swc"
  #       t.add_library :rpc,          "frameworks/libs/rpc.swc"
  #       t.add_library :sparkskins,   "frameworks/libs/sparkskins.swc"
  #       t.add_library :textlayout,   "frameworks/libs/textLayout.swc"
  #       t.add_library :utilities,    "frameworks/libs/utilities.swc"
  #     end
  #   end
  #
  # Because this library Specification is included in the flashsdk Rubygem, you
  # would register the spec by loading the flashsdk gem from your Gemfile like:
  #
  #   gem 'flashsdk', '>= 1.0.0.pre'
  #
  # Once the Rubygem is installed, you can load and register this library with
  # the following library task in your rakefile:
  #
  #   library :flex4
  #
  # Once the library is registered, you can refer to particular SWC files within the library like:
  #
  #   mxmlc "bin/Foo.swf" => [:utilities, :textlayout] do |t|
  #     ...
  #   end
  #
  # ---
  # 
  # Previous Topic: {Sprout::Generator}
  #
  # Next Topic: {Sprout::Executable}
  #
  # ---
  # 
  # @see Sprout::Generator
  # @see Sprout::Executable
  # @see Sprout::Specification
  # @see Sprout::RubyFeature
  # @see Sprout::System
  #
  class Library
    include RubyFeature

    TASK_NAME = :resolve_sprout_libraries

    attr_accessor :file_target
    attr_accessor :installed_project_path
    attr_accessor :name
    attr_accessor :path
    attr_accessor :pkg_name
    attr_accessor :pkg_version
    attr_accessor :platform

    class << self
      ##
      # Set the path within a project
      # where libraries should be loaded.
      #
      # From top of your Rakefile, you can 
      # set this value with:
      #
      #   Sprout::Library.project_path = 'libs'
      #
      def project_path=(path)
        @project_path = path
      end

      ##
      # The path within a project where 
      # libraries should be added.
      #
      # Defaults to 'lib'
      #
      # From anywhere in your Rakefile, you can output
      # this value with:
      #
      #   puts ">> Library Project Path: #{Sprout::Library.project_path}"
      #
      def project_path
        @project_path ||= 'lib'
      end

      ##
      # Create Rake tasks that will load and install
      # a particular library into the current project.
      #
      # This method is usually accessed from the global
      # library helper.
      #
      #   library :asunit4
      #
      def define_task name=nil, pkg_name=nil, pkg_version=nil
        library = Sprout::Library.load name, pkg_name, pkg_version
        library.create_installation_tasks
      end
    end

    def initialize params=nil
      params.each {|key,value| self.send("#{key}=", value)} unless params.nil?
      super()
    end

    ##
    # Returns the outer Rake::Task which is invokable.
    def create_installation_tasks
      define_lib_dir_task_if_necessary project_path
      create_project_tasks
      create_outer_task
    end

    def create_outer_task
      t = Rake::Task.define_task pkg_name
      # This helps executable rake tasks decide if they
      # want to do something special for library tasks.
      t.sprout_entity = self
      class << t
        def sprout_library?
          !sprout_entity.nil? &&
            sprout_entity.is_a?(Sprout::Library)
        end
      end
      t
    end

    private

    def project_path
      Sprout::Library.project_path
    end

    def create_project_tasks
      if path.is_a?(Array)
        # TODO: Need to add support for merging these directories
        # rather than simply clobbering...
        @instaled_project_path = path.collect { |single_path| define_project_path_task pkg_name, single_path }
      else
        @installed_project_path = define_project_path_task pkg_name, path
      end
    end

    def define_lib_dir_task_if_necessary dir
      if !File.exists?(dir)
        define_file_task dir do
          FileUtils.mkdir_p dir
          Sprout.stdout.puts ">> Created directory at: #{dir}"
        end
      end
    end

    def define_project_path_task pkg_name, path
      install_path = "#{project_path}/#{pkg_name}"
      if File.directory?(path)
        define_directory_copy_task path, install_path
      else
        define_file_copy_task path, install_path
      end
    end

    def define_directory_copy_task path, install_path
      define_file_task install_path do
        FileUtils.cp_r path, install_path
        Sprout.stdout.puts ">> Copied files from: (#{path}) to: (#{install_path})"
      end
      install_path
    end

    def define_file_copy_task path, install_path
      task_name = "#{install_path}/#{File.basename(path)}"
      define_file_task task_name do
        FileUtils.mkdir_p install_path
        FileUtils.cp path, "#{install_path}/"
        Sprout.stdout.puts ">> Copied file from: (#{path}) to: (#{task_name})"
      end
      task_name
    end

    def define_file_task name
      Rake::FileTask.define_task name do |t|
        yield if block_given?
      end
      Rake::Task.define_task Sprout::Library::TASK_NAME => name
    end
  end

end

# From within a Rakefile, you can load libraries
# by calling this method:
#
#   library :asunit4
#
# Or, if you would like to specify which registered
# library to pull from the identified package (by name):
#
#   library :asunit4, :src
#
# Or, if you'd like to specify version requirements:
#
#   library :asunit4, :swc, '>= 4.2.pre'
#
# It's important to note that libraries must also
# be defined in your Gemfile like:
#
#   gem "asunit4", ">= 4.2.pre"
#
# Libraries are generally then added to compiler tasks
# as Rake dependencies like:
#
#   mxmlc 'bin/SomeRunner.swf' => [:asunit4] do |t|
#     t.input = 'src/SomeRunner.as'
#     t.source_path << 'test'
#   end
#
def library pkg_name, name=nil, version=nil
  Sprout::Library.define_task name, pkg_name, version
end

