module AppDrone

Param = Struct.new('Param',:name,:type,:options)

class Drone
  # align: set up variables, pass off to other scripts
  # execute: actual install process

  # New
  def initialize(template,*params)
    @template = template
    @params = (params.first || {}).merge({options: {}})
    setup
  end

  def param(sym)
    param_value = @params[sym]
    default_value = self.class.param_named(sym).options[:default]
    return param_value || default_value
  end

  # DSL
  def ^; @template end # This is never used, not even sure if it works (something about a misplaced '.' error)
  def >>(klass); @template.hook(klass); end

  def method_missing(meth, *args, &block)
    if Drone.drones.include?(meth)
      klass = meth.to_app_drone_class
      return (self >> klass)
    else
      super
    end
  end

  def pair?(drone_symbol)
    drone_klass = drone_symbol.to_app_drone_class
    return @template.hook?(drone_klass)
  end

  def notify!(notice)
    @template.notify! notice, self
  end

  # Expected implementations
  def align; end
  def execute; end

  # Optional implementations
  def setup; end

  def render(partial, opts={})
    class_name = self.class.to_sym
    template_path = "/drones/#{class_name}/#{partial}.erb"
    full_path = File.dirname(__FILE__) + template_path
    snippet = ERB.new File.read(full_path)
    output = snippet.result(binding)
    output = "# --- \n# #{self.class.to_s}\n# ---\n" + output unless opts[:skip_stamp]
    return output
  end

  def do!(partial)
    @template.do! render(partial), self
  end

  def do_finally!(partial)
    @template.do_finally! render(partial), self
  end

  # DSL: Integration-specific options
  attr_accessor :params
  class << self  
    def param_with(drone_klass, name, type, *options)
      param(name,type,options.first.merge({ with: drone_klass }))
    end

    def param(name, type, *options)
      (@params ||= []) << Param.new(name, type, options.first)
    end
    def params; @params || [] end
    def param_named(n); params.find { |p| p.name == n } end

    def desc(d=nil)
      return @description if d.nil?
      @description = d
    end

    def category(c=nil)
      return @category if c.nil?
      @category = c
    end

    def depends_on(*klass_symbols); @dependencies = klass_symbols end
    def dependencies
      (@dependencies || []).map(&:to_app_drone_class)
    end

    def dependants
      self.superclass.drones.select { |d| d.to_app_drone_class.dependencies.include?(self) }.map(&:to_app_drone_class)
    end

    def pairs_with(*klass_symbols); @pairs = klass_symbols end
    def pairs
      (@pairs || []).map(&:to_app_drone_class)
    end

    def run_after(*klass_symbols); @precursors = klass_symbols end
    def precursors
      (@precursors || []).map(&:to_app_drone_class)
    end

    def owns_generator_method(m); @generator_method = m end
    def generator_method; @generator_method end

    def drones
      self.descendants.map(&:to_sym)
    end

    def categories
      self.descendants.map(&:category).uniq
    end

    def to_sym
      self.to_s.split('::').last.underscore.to_sym
    end

    def human_name
      self.to_sym.to_s.humanize.titleize
    end

  end

end

end
