# 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:
  module Run
    include Extension

    class << self
      def runners
        @runners ||= []
      end

      def select_by_lang(lang)
        fail 'Unable to define run task for nil language' if lang.nil?
        runners.detect { |e| e.languages.nil? ? false : e.languages.include?(lang.to_sym) }
      end

      alias_method :select, :select_by_lang

      def select_by_name(name)
        fail 'Unable to define run task for nil' if name.nil?
        runners.detect { |e| e.to_sym == name.to_sym }
      end

    end

    # Base class for any run provider.  Defines most
    # common functionality (things like @lang@, @build?@ and friends).
    class Base
      attr_reader :project

      class << self
        attr_accessor :runner_name, :languages

        def specify(options)
          @runner_name ||= options[:name]
          @languages ||= options[:languages]
        end

        def to_sym
          @runner_name || name.split('::').last.downcase.to_sym
        end
      end

      def initialize(project)
        @project = project
      end

      def build?
        true
      end

      def launch
        fail 'Not implemented'
      end
    end

    class RunTask < Rake::Task
      # Classpath dependencies.
      attr_accessor :classpath

      # Returns the run options.
      attr_reader :options

      # Returns file dependencies
      attr_reader :files

      attr_reader :project # :nodoc:

      def initialize(*args) # :nodoc:
        super
        @options = {}
        @classpath = []
        @files = FileList[]
      end

      # :call-seq:
      #   with(*artifacts) => self
      #
      # Adds files and artifacts as classpath dependencies, and returns self.
      def with(*specs)
        @classpath |= Buildr.artifacts(specs.flatten).uniq
        self
      end

      # :call-seq:
      #   using(options) => self
      #
      # Sets the run options from a hash and returns self.
      #
      # For example:
      #   run.using :main=>'org.example.Main'
      def using(*args)
        args.pop.each { |key, value| @options[key.to_sym] = value } if Hash === args.last

        until args.empty?
          new_runner = Run.select_by_name(args.pop)
          @runner = new_runner.new(project) unless new_runner.nil?
        end

        self
      end

      # :call-seq:
      #   requires(*files) => self
      #
      # Adds additional files and directories as dependencies to the task and returns self.
      # When specifying a directory, includes all files in that directory.
      def requires(*files)
        @files.include *files.flatten.compact
        self
      end

      def runner
        @runner ||= guess_runner
      end

      def runner?
        @runner ||= begin
          guess_runner if project.compile.language
        rescue
          nil
        end
      end

      def run
        runner.run(self) if runner?
      end

      def prerequisites #:nodoc:
        super + @files + classpath
      end

    private

      def guess_runner
        runner = Run.select project.compile.language
        fail 'Unable to guess runner for project.' unless runner
        runner.new project
      end

      def associate_with(project)
        @project ||= project
      end
    end

    first_time do
      Project.local_task 'run'
    end

    before_define(:run => :test) do |project|
      RunTask.define_task('run').tap do |run|
        run.send(:associate_with, project)
        run.enhance([project.compile, project.test]) do |t|
          # double-enhance to execute the runner last
          run.enhance { |t| t.run }
        end
      end
    end

    after_define(:run => :test) do |project|
      project.run.with project.compile.dependencies
      project.run.with project.resources.target if project.resources.target
      project.run.with project.compile.target if project.compile.target
    end

    # :call-seq:
    #   run(&block) => RunTask
    #
    # This method returns the project's run task. It also accepts a block to be executed
    # when the run task is invoked.
    def run(&block)
      task('run').tap do |t|
        t.enhance &block if block
      end
    end

  end

  class Project
    include Run
  end
end
