module Workflow
  module Draw

    # Generates a `dot` graph of the workflow.
    # Prerequisite: the `dot` binary. (Download from http://www.graphviz.org/)
    # You can use this method in your own Rakefile like this:
    #
    #     namespace :doc do
    #       desc "Generate a workflow graph for a model passed e.g. as 'MODEL=Order'."
    #       task :workflow => :environment do
    #         require 'workflow/draw'
    #         Workflow::Draw::workflow_diagram(ENV['MODEL'].constantize)
    #       end
    #     end
    #
    # You can influence the placement of nodes by specifying
    # additional meta information in your states and transition descriptions.
    # You can assign higher `weight` value to the typical transitions
    # in your workflow. All other states and transitions will be arranged
    # around that main line. See also `weight` in the graphviz documentation.
    # Example:
    #
    #     state :new do
    #       event :approve, :transitions_to => :approved, :meta => {:weight => 8}
    #     end
    #
    #
    # @param klass A class with the Workflow mixin, for which you wish the graphical workflow representation
    # @param [String] target_dir Directory, where to save the dot and the pdf files
    # @param [String] graph_options You can change graph orientation, size etc. See graphviz documentation
    def self.workflow_diagram(klass, options={})
      # TODO: find some replacement for ActiveSupport::Inflector.tableize
      # or make it usage optional
      options = {
        # :name => "#{klass.name.tableize}_workflow".gsub('/', '_'),
        :name => "#{klass.name}_workflow".gsub('/', '_'),
        :path => '.',
        :orientation => "landscape",
        :ratio => "fill",
        :format => 'png',
        :font => 'Helvetica'
      }.merge options

      require 'ruby-graphviz'
      graph = ::GraphViz.new('G', :rankdir => options[:orientation] == 'landscape' ? 'LR' : 'TB', :ratio => options[:ratio])

      # Add nodes
      klass.workflow_spec.states.each do |_, state|
        node = state.draw(graph)
        node.fontname = options[:font]

        state.events.flat.each do |event|
          edge = event.draw(graph, state)
          edge.fontname = options[:font]
        end
      end

      # Generate the graph
      filename = File.join(options[:path], "#{options[:name]}.#{options[:format]}")

      graph.output options[:format] => filename

      puts "
      Please run the following to open the generated file:

      open '#{filename}'
      "
      graph
    end
  end
end
