#!/usr/bin/env ruby

require 'rbbt/workflow'

require 'rbbt-util'
require 'fileutils'
require 'rbbt/util/simpleopt'
require 'rbbt/workflow/step'
require 'rbbt/util/misc'

require 'rbbt-util'
require 'rbbt/util/simpleopt'

$0 = "rbbt #{$previous_commands*""} #{ File.basename(__FILE__) }" if $previous_commands

options = SOPT.setup <<EOF
Examine the provenance of a job result

$ rbbt workflow prov <job-result>

-h--help Help
-p--plot* draw the dependency plot into <file.png>
-i--inputs* List of inputs to print
-if--info_fields* List of info fields to print
EOF

SOPT.usage if options[:help]

$inputs = (options[:inputs] || "").split(",")
$info_fields = (options[:info_fields] || "").split(",")

file = ARGV.shift

$seen = []
def get_step(file)
  file = File.expand_path(file)
  file = file.sub(/\.(info|files)/,'')
  $seen << file
  Workflow.load_step file
end

def status_msg(status)
  color = case status.to_sym
          when :error, :aborted, :missing, :dead
            :red
          when :streaming, :started
            :cyan
          when :done
            :green
          when :noinfo
            :blue
          when :dependencies, :waiting, :setyp
            :yellow
          else
            if status.to_s.index ">"
              :cyan
            else
              :cyan
            end
          end
  Log.color(color, status.to_s)
end

def report_msg(status, name, path, info = nil)
  parts = path.sub(/\{.*/,'').sub(/#{Regexp.quote(name)}$/,'').split "/"

  task = Log.color(:yellow, parts.pop)
  workflow = Log.color(:magenta, parts.pop)
  if status.to_s == 'noinfo' and parts.last != 'jobs'
    task, status, workflow = Log.color(:yellow, info[:task_name]), Log.color(:blue, "file"), Log.color(:magenta, "-")
  end

  path_mtime = begin
                   Open.mtime(path)
               rescue
                   nil
               end
  str = if not Open.remote?(path) and (Open.exists?(path) and $main_mtime and path_mtime and ($main_mtime - path_mtime) < 0)
    status_msg(status.to_s) << " " << [workflow, task, path].compact * " " << " (#{Log.color(:red, "Mtime out of sync") })"
  else
    status_msg(status.to_s) << " " << [workflow, task, path].compact * " " 
  end

  if $inputs and $inputs.any? 
    job_inputs = Workflow.load_step(path).recursive_inputs.to_hash
    IndiferentHash.setup(job_inputs)

    $inputs.each do |input|
      value = job_inputs[input]
      next if  value.nil?
      value_str = Misc.fingerprint(value)
      str << "\t#{Log.color :magenta, input}=#{value_str}"
    end
  end

  if $info_fields and $info_fields.any?
    $info_fields.each do |field|
      IndiferentHash.setup(info)
      value = info[field]
      next if value.nil?
      value_str = Misc.fingerprint(value)
      str << "\t#{Log.color :magenta, field}=#{value_str}"
    end
  end

  str << "\n"
end

def report(step, offset = 0, task = nil)
  info = step.info  || {}
  info[:task_name] = task
  path  = step.path
  status = info[:status] || :missing
  status = "remote" if Open.remote?(path)
  name = info[:name] || File.basename(path)
  status = :unsync if status == :done and not Open.exist? path
  str = " " * offset
  str << report_msg(status, name, path, info)
  step.dependencies.each do |dep|
    path = dep.path
    new = ! $seen.include?(path)
    if new
      $seen << path
      str << report(dep, offset + 1, task)
    else
      str << Log.color(:blue, Log.uncolor(report(dep, offset+1, task)))
    end
  end if step.dependencies
  str
end

step = get_step file
$main_mtime = Open.exist?(step.path) ? Open.mtime(step.path) : nil

def adjacency(step)

  info = step.info || {}
  path  = step.path
  status = info[:status] || :missing
  status = "remote" if Open.remote?(path)
  if status == 'remote'
    workflow, task, name = path.sub(/\{.*/,'').split("/")[-3..-1]
  else
    workflow, task, name = path.split("/")[-3..-1]
  end
  name = name.split(":").last
  status = :unsync if status == :done and not Open.exist? path
  shapes = Hash.new "circle"
  edge_info = {:status =>  status, :workflow => workflow, :task => task, :name => name, :label => task, :shape => shapes[workflow], :color => status == 'remote' ? 'blue' : 'green'}
  id = Misc.digest(path)
  edges = []
  node_info = {}
  node_info[id] = edge_info
  if info[:dependencies]
    info[:dependencies].each do |task,name,path|
      dep = get_step path
      _id, _edges, _node_info = adjacency(dep)
      edges << [_id, id] 
      edges.concat _edges
      node_info.merge!(_node_info)
    end 
  end

  [id, edges, node_info]
end

if options[:plot]
  id, edges, node_info = adjacency(step)
  node_info[id][:color] = 'red'
  TmpFile.with_file do |edge_file|
    Open.write(edge_file) do |f|
      f.puts "from,to"
      edges.uniq.each do |from,to|
        f.puts [from,to]*","
      end
    end
    TmpFile.with_file do |node_info_file|
      Open.write(node_info_file) do |f|
        fields = node_info.first.last.keys
        f.puts "id," + fields * ","
        node_info.each do |id,info|
          f.puts ([id] + info.values_at(*fields)) * ","
        end
      end

      require 'rbbt/util/R'

      R.run <<-EOF
      nodes <- read.csv("#{node_info_file}", header=T, as.is=T)
      links <- read.csv("#{edge_file}", header=T, as.is=T)

      library(igraph)

      net <- graph.data.frame(links, nodes, directed=T)
      net <- simplify(net, remove.multiple = F, remove.loops = T)

      png("#{options[:plot]}", width=1000, height=1000)
      plot(net, edge.arrow.size=0.4, vertex.label=net$label, vertex.color=net$color)
      dev.off()
      EOF
    end
  end

else
  puts report(step).strip
end