require 'json'
require 'open3'
require 'stringio'
require 'logger'

# Optionally can specify the command to continue from
# Exit on first failure
#
def time_diff_milli(start, finish)
   (finish - start) * 1000.0
end

def handleIO(stillOpen, ioArray, io, log)
  if ioArray.include?(io)
    begin
      log.write(io.readpartial(4096))
    rescue EOFError
      stillOpen.delete_if{|s| s == io}
    end
  end
end


class Audit
    def start (id, cmd)
        self.log( id, cmd, "start", "", 0, "")
    end

    def complete (id, cmd, status, msecs, result)
	    self.log( id, cmd, "complete", status, msecs, result)
    end

    def status (id, cmd, status, msecs, result)
        data = {
            "message" => {
                "id" => id,
                "cmd" => cmd,
                "task" => "status",
                "status" => status,
                "elapsed" => msecs,
                "result" => result
            }
        }
        puts data.to_json
        File.open(Canzea::config[:logging_root] + '/audit.log', 'a') { |file| file.puts(data.to_json) }
    end

    def log (id, cmd, task, status, msecs, result)
        data = {
            "message" => {
                "id" => id,
                "cmd" => cmd,
                "task" => task,
                "status" => status,
                "elapsed" => msecs,
                "result" => result
            }
        }
        summary = {
            "message" => {
                "id" => id,
                "cmd" => cmd,
                "task" => task,
                "status" => status,
                "elapsed" => msecs
            }
        }
        puts summary.to_json
        File.open(Canzea::config[:logging_root] + '/audit.log', 'a') { |file| file.puts(data.to_json) }
    end
end

class ManagedError < StandardError
end

class Worker
    @test = false


    def test (t)
        @test = t
    end

    def run(command, start, lines, statusIndicator = false )
        audit = Audit.new

	    log = Logger.new(Canzea::config[:logging_root] + '/plans.log')

        log.info("HANDLING: " + command)

        File.foreach(command) { |l|
            if ( l.start_with?('## ') )
                log.info "#{lines + 1} Label: " + l
            elsif ( l.start_with?('#') )
            elsif ( /\S/ !~ l )
            elsif ( l.chomp.end_with?(".sh") && !l.chomp.end_with?(".atomic.sh") && !l.include?("cp ") && !l.include?("chmod") )
                log.info("#{lines + 1} RECURSE: " + l)

                lines = run l.chomp, start, lines
            else

                if ( (lines + 1) < start )
                    log.info("Skipping : #{lines + 1} #{l}")
                    lines += 1
                    next
                end


                audit.start( "#{lines + 1 }", l.chomp)

                log.info("#{lines + 1} FOUND: " + l)

                if @test == false
                    t1 = Time.now

                    begin
                        puts "-- Executing: #{l}"
                        Open3.popen3(l) {|stdin, stdout, stderr, wait_thr|
                          pid = wait_thr.pid # pid of the started process.

                          log_w = StringIO.new

                          stillOpen = [stdout, stderr]
                          while !stillOpen.empty?
                            fhs = select(stillOpen, nil, nil, nil)
                            handleIO(stillOpen, fhs[0], stdout, log_w)
                            handleIO(stillOpen, fhs[0], stderr, log_w)
                          end

                          exit_status = wait_thr.value # wait for it to finish

                          log_w.close_write

                          output = log_w.string

                          output.split(/\n/).each do | line |
                              puts "-- #{line}"
                          end
                          puts "-- Exit Status = #{exit_status}"

                          log.info("STDOUT #{output}")

                          log.info("Exit Status = #{exit_status}")

                          # if exit status is failure then exit right away

                          t2 = Time.now

                          msecs = time_diff_milli t1, t2

                          audit.complete("#{lines + 1}", l.chomp, exit_status.exitstatus, msecs, output)

                          if (statusIndicator)
                              audit.status("#{lines + 1}", l.chomp, exit_status.exitstatus, msecs, output)
                          end

                          if exit_status.exitstatus != 0
                            abort()
                          end
                        }
                    rescue => exception
                        msecs = time_diff_milli t1, Time.now
                        audit.complete("#{lines + 1}", l.chomp, "-1", msecs, exception.to_s)
                        raise
                    end
                else
                    puts "-- TEST -- [#{lines + 1}] FOUND: " + l
                end

            end

            lines += 1
        }
        return lines

    end
end