#!/usr/bin/env ruby

require 'rbbt/util/simpleopt'
require 'rbbt/workflow'
require 'rbbt/workflow/usage'

YAML::ENGINE.yamler = 'syck' if defined? YAML::ENGINE and YAML::ENGINE.respond_to? :yamler

def usage(workflow = nil, task = nil, error = nil)
  puts SOPT.doc
  puts "## WORKFLOW"
  puts
  if workflow.nil?
    puts "No workflow specified"
    exit -1 
  end

  if task.nil?
    workflow.load_tasks if workflow.respond_to? :load_tasks
    workflow.doc
  else
    puts workflow.to_s
    puts "=" * workflow.to_s.length
    puts
    puts workflow.workflow_description
    puts
    workflow.doc(task)
    if error
        puts
        puts "Error: " << error 
    end
  end

  exit 0
end

def SOPT_options(workflow, task)
  sopt_options = []
  workflow.rec_inputs(task.name).each do |name|
    short = name.to_s.chars.first
    boolean = workflow.rec_input_types(task.name)[name].to_sym == :boolean
    
    sopt_options << "-#{short}--#{name}*"
  end

  sopt_options * ":"
end

def fix_options(workflow, task, job_options)
  option_types = workflow.rec_input_types(task.name)

  job_options_cleaned = {}

  job_options.each do |name, value|
    value = case option_types[name].to_sym
            when :boolean
              %w(true TRUE T yes).include? value
            when :float
              value.to_f
            when :integer
              value.to_i
            when :string, :text
              case
              when value == '-'
                STDIN.read
              when (String === value and File.exists?(value) and not File.directory?(value))
                Open.read(value)
              else
                value
              end
            when :array
              if Array === value
                value
              else
                str = case
                when value == '-'
                  STDIN.read
                when (String === value and File.exists?(value))
                  Open.read(value)
                else
                  value
                end
                
                if $array_separator
                  str.split(/#{$array_separator}/)
                else
                  str.split(/[,|\s]/)
                end
              end
            when :tsv
              case value
              when TSV
                value
              when '-'
                TSV.open(STDIN)
              else
                TSV.open(value)
              end
            else
              value
            end
    job_options_cleaned[name] = value
  end

  job_options_cleaned
end

options = SOPT.get <<EOF
-h--help Show this help:
-as--array_separator* Change the character that separates elements of Arrays, ',', '|', or '\\n' by default:
-cl--clean Clean the last step of the job so that it gets recomputed:
-rcl--recursive_clean Clean the last step and its dependencies to recompute the job completely:
-jn--jobname* Job name to use. The name 'Default' is used by default:
-pn--printname Print the name of the job and exit without starting it:
EOF

workflow = ARGV.shift
usage if workflow.nil?

task     = ARGV.shift

# Set log, fork, clean, recursive_clean and help
help = !!options.delete(:help)
do_fork = !!options.delete(:fork)
do_exec = !!options.delete(:exec)
clean = !!options.delete(:clean)
recursive_clean = !!options.delete(:recursive_clean)
$array_separator = options.delete(:array_separator)

# Get workflow

if Rbbt.etc.remote_workflows.exists?
  remote_workflows = Rbbt.etc.remote_workflows.yaml
else
  remote_workflows = {}
end

if remote_workflows.include? workflow
  require 'rbbt/rest/client'
  workflow = WorkflowRESTClient.new remote_workflows[workflow], workflow
else
  Workflow.require_workflow workflow
  workflow = Workflow.workflows.select{|mod| Misc.snake_case(mod.to_s) == Misc.snake_case(workflow)}.first
  workflow = Workflow.workflows.last if workflow.nil?
end

# Set task
namespace = nil, nil

case 
when task.nil?
  usage workflow
when (task =~ /\./)
  namespace, task = options.delete(:task).split('.')
  namespace = Misc.string2const(namespace)
else
  task_name = task.to_sym
  task = workflow.tasks[task_name]
  raise "Task not found: #{ task_name }" if task.nil?
end

usage workflow, task if help

name = options.delete(:jobname) || "Default"

# get job args
sopt_option_string = SOPT_options(workflow, task)
job_options = SOPT.get sopt_option_string
job_options = fix_options(workflow, task, job_options)

#- get job

job = workflow.job(task.name, name, job_options)

# clean job
if clean and job.done? != false
  job.clean 
  sleep 1
  job = workflow.job(task.name, name, job_options)
end

if recursive_clean and job.done?
  job.recursive_clean 
  sleep 1
  job = workflow.job(task.name, name, job_options)
end

# run
begin
if do_exec
  res = job.exec
  case
  when Array === res
    puts res * "\n"
  when TSV === res
    puts res
  when Hash === res
    puts res.to_yaml
  else
    puts res
  end
  exit 0
end

if do_fork
  job.fork
  while not job.done?
    Log.debug "#{job.step}: #{job.messages.last}"
    sleep 2
  end
  raise job.messages.last if job.error?
else
  res = job.run(true)
end

if options.delete(:printname)
  puts job.name
  exit 0
else
  Log.low "Job name: #{job.name}" 
end
rescue ParameterException
    usage(workflow, task, $!.message)
end

if Step === res
  puts Open.read(res.path) if File.exists? res.path
else
  puts res
end

exit 0