func (j *Job) ThenRunT(jt drmaa2interface.JobTemplate) *Job {
	j.begin(j.ctx, "ThenRunT()")
	return j.Wait().RunT(jt)
}
ThenRunT waits until the previous task is terminated and then executes
a new task based on the given JobTemplate.
package wfl
import (
	"context"
	"errors"
	"fmt"
	"reflect"
	"runtime"
	"time"
	"github.com/dgruber/drmaa2interface"
	"github.com/mitchellh/copystructure"
)
type task struct {
	job                             drmaa2interface.Job
	template                        drmaa2interface.JobTemplate
	jobinfo                         drmaa2interface.JobInfo
	terminated                      bool
	submitError                     error
	terminationError                error
	jobinfoError                    error
	retry                           int
	waitForEndStateCollectedJobInfo bool
	isJobArray                      bool
	jobArray                        drmaa2interface.ArrayJob
}
type Job struct {
	wfl       *Workflow
	tasklist  []*task
	tag       string
	lastError error
	ctx       context.Context  
}
func NewJob(wfl *Workflow) *Job {
	return &Job{
		wfl:      wfl,
		tasklist: make([]*task, 0, 32),
		ctx:      context.Background(),
	}
}
func EmptyJob() *Job {
	return &Job{}
}
func (j *Job) TagWith(tag string) *Job {
	j.begin(j.ctx, fmt.Sprintf("TagWith(%s)", tag))
	j.tag = tag
	return j
}
func (j *Job) Tag() string {
	return j.tag
}
func (j *Job) Template() *drmaa2interface.JobTemplate {
	j.begin(j.ctx, "Template()")
	j.lastError = nil
	if job, jobArray, err := j.jobCheck(); err != nil {
		j.lastError = err
	} else if job != nil {
		template, errTmp := job.GetJobTemplate()
		if errTmp != nil {
			j.errorf(j.ctx, "Template() [JobID: %s]: GetJobTemplate() failed with %s",
				j.JobID(), errTmp.Error())
			j.lastError = errTmp
		} else {
			return &template
		}
	} else if jobArray != nil {
		template := jobArray.GetJobTemplate()
		return &template
	}
	return nil
}
func (j *Job) State() drmaa2interface.JobState {
	j.begin(j.ctx, "State()")
	task := j.lastJob()
	if task != nil && task.waitForEndStateCollectedJobInfo && task.jobinfoError == nil {
		return task.jobinfo.State
	}
	job, jobArray, err := j.jobCheck()
	if err != nil {
		j.lastError = err
		return drmaa2interface.Undetermined
	}
	if job != nil {
		return job.GetState()
	}
	return jobArrayState(jobArray, false)
}
func (j *Job) JobID() string {
	j.begin(j.ctx, "JobID()")
	job, jobArray, err := j.jobCheck()
	if err != nil {
		j.lastError = err
		return ""
	}
	if job != nil {
		return job.GetID()
	}
	return jobArray.GetID()
}
func (j *Job) JobInfo() drmaa2interface.JobInfo {
	j.begin(j.ctx, "JobInfo()")
	job, _, err := j.jobCheck()
	if err != nil {
		j.lastError = err
		return drmaa2interface.JobInfo{}
	}
	task := j.lastJob()
	if task != nil && task.waitForEndStateCollectedJobInfo &&
		task.jobinfoError == nil {
		return task.jobinfo
	}
	ji, errJI := job.GetJobInfo()
	if errJI != nil {
		j.errorf(j.ctx, "JobInfo() [JobID: %s]: GetJobInfo() failed with: %s",
			j.JobID(), errJI.Error())
		j.lastError = errJI
		return drmaa2interface.JobInfo{}
	}
	return ji
}
func (j *Job) JobInfos() []drmaa2interface.JobInfo {
	j.begin(j.ctx, "JobInfos()")
	jis := make([]drmaa2interface.JobInfo, 0, len(j.tasklist))
	for _, task := range j.tasklist {
		if task.job != nil {
			ji, err := task.job.GetJobInfo()
			if err != nil {
				j.warningf(j.ctx,
					"task returned error when calling GetJobInfo(): %s",
					err.Error())
				continue
			}
			jis = append(jis, ji)
		}
	}
	return jis
}
func (j *Job) Run(cmd string, args ...string) *Job {
	j.begin(j.ctx, fmt.Sprintf("Run(%s, %v)", cmd, args))
	jt := drmaa2interface.JobTemplate{RemoteCommand: cmd, Args: args}
	return j.RunT(jt)
}
func (j *Job) RunT(jt drmaa2interface.JobTemplate) *Job {
	j.begin(j.ctx, fmt.Sprintf("RunT(%s, %v)", jt.RemoteCommand, jt.Args))
	if err := j.checkCtx(); err != nil {
		j.lastError = err
		return j
	}
	jt = mergeJobTemplateWithDefaultTemplate(jt, j.wfl.ctx.DefaultTemplate)
	if jt.JobCategory == "" {
		jt.JobCategory = j.wfl.ctx.DefaultDockerImage
	}
	if j.wfl.js == nil {
		j.lastError = errors.New("JobSession is nil")
		return j
	}
	job, err := j.wfl.js.RunJob(jt)
	j.lastError = err
	jobTemplate, _ := copystructure.Copy(jt)
	j.tasklist = append(j.tasklist, &task{job: job, submitError: err,
		template: jobTemplate.(drmaa2interface.JobTemplate)})
	return j
}
func (j *Job) RunArray(begin, end, step, maxParallel int, cmd string, args ...string) *Job {
	j.begin(j.ctx, fmt.Sprintf("RunArray(%d, %d, %d, %d, %s, %v)", begin, end, step, maxParallel, cmd, args))
	if err := j.checkCtx(); err != nil {
		j.lastError = err
		return j
	}
	jt := drmaa2interface.JobTemplate{RemoteCommand: cmd, Args: args}
	job, err := j.wfl.js.RunBulkJobs(jt, begin, end, step, maxParallel)
	j.lastError = err
	jobTemplate, _ := copystructure.Copy(jt)
	j.tasklist = append(j.tasklist, &task{jobArray: job, isJobArray: true, submitError: err,
		template: jobTemplate.(drmaa2interface.JobTemplate)})
	return j
}
func (j *Job) RunArrayT(begin, end, step, maxParallel int, jt drmaa2interface.JobTemplate) *Job {
	j.begin(j.ctx, fmt.Sprintf("RunArrayT(%d, %d, %d, %d, %v)", begin, end, step, maxParallel, jt))
	if err := j.checkCtx(); err != nil {
		j.lastError = err
		return j
	}
	job, err := j.wfl.js.RunBulkJobs(jt, begin, end, step, maxParallel)
	j.lastError = err
	jobTemplate, _ := copystructure.Copy(jt)
	j.tasklist = append(j.tasklist, &task{jobArray: job, isJobArray: true, submitError: err,
		template: jobTemplate.(drmaa2interface.JobTemplate)})
	return j
}
func (j *Job) Do(f func(job drmaa2interface.Job)) *Job {
	j.begin(j.ctx, fmt.Sprintf("Do(%s)",
		runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()),
	)
	job, _, err := j.jobCheck()
	if err == nil && job != nil {
		f(job)
	} else {
		j.errorf(j.ctx,
			"Do(): Function (%s) is not executed as task is nil",
			runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name(),
		)
	}
	return j
}
func (j *Job) Suspend() *Job {
	j.begin(j.ctx, "Suspend()")
	job, jobArray, err := j.jobCheck()
	if err != nil {
		j.lastError = err
		return j
	}
	if job != nil {
		j.lastError = job.Suspend()
		return j
	}
	j.lastError = jobArray.Suspend()
	return j
}
func (j *Job) Resume() *Job {
	j.begin(j.ctx, "Resume()")
	job, jobArray, err := j.jobCheck()
	if err != nil {
		j.lastError = err
		return j
	}
	if job != nil {
		j.lastError = job.Resume()
		return j
	}
	j.lastError = jobArray.Resume()
	return j
}
func (j *Job) Kill() *Job {
	j.begin(j.ctx, "Kill()")
	job, jobArray, err := j.jobCheck()
	if err != nil {
		j.lastError = err
		return j
	}
	if job != nil {
		j.lastError = job.Terminate()
		return j
	}
	j.lastError = jobArray.Terminate()
	return j
}
func (j *Job) LastError() error {
	return j.lastError
}
func rerunTask(j *Job, e *task) {
	job, err := j.wfl.js.RunJob(e.template)
	j.lastError = err
	if err == nil {
		jobTemplate, _ := copystructure.Copy(e.template)
		j.tasklist = append(j.tasklist, &task{job: job, submitError: err,
			template: jobTemplate.(drmaa2interface.JobTemplate)})
	}
}
func replaceTask(j *Job, e *task) {
	e.job, e.submitError = j.wfl.js.RunJob(e.template)
}
func (j *Job) Resubmit(r int) *Job {
	j.begin(j.ctx, fmt.Sprintf("Resubmit(%d)", r))
	for i := 0; i < r || r == -1; i++ {
		if t := j.lastJob(); t != nil && !t.isJobArray {
			rerunTask(j, t)
		} else {
			j.errorf(
				j.ctx,
				"Resubmit(): Could not find any job in order to re-run it.",
			)
			j.lastError = errors.New("job not available")
			break
		}
	}
	return j
}
func (j *Job) AnyFailed() bool {
	j.begin(j.ctx, "AnyFailed()")
	for _, task := range j.tasklist {
		if !task.isJobArray {
			if task.job.GetState() == drmaa2interface.Failed {
				return true
			}
		} else {
			if jobArrayState(task.jobArray, false) == drmaa2interface.Failed {
				return true
			}
		}
	}
	return false
}
func (j *Job) RunEvery(d time.Duration, end time.Time, cmd string, args ...string) error {
	j.begin(j.ctx, fmt.Sprintf("RunEvery(%s %s %s %s)",
		d.String(),
		end.Format("15:04:05"),
		cmd,
		args),
	)
	return j.RunEveryT(d, end, drmaa2interface.JobTemplate{RemoteCommand: cmd, Args: args})
}
func (j *Job) RunEveryT(d time.Duration, end time.Time, jt drmaa2interface.JobTemplate) error {
	j.begin(j.ctx, fmt.Sprintf("RunEvery(%s %s %s %s)",
		d.String(),
		end.Format("15:04:05"),
		jt.RemoteCommand,
		jt.Args),
	)
	for range time.NewTicker(d).C {
		if time.Now().After(end) {
			j.infof(j.ctx, "RunEveryT() end time reached")
			break
		}
		j.infof(j.ctx, "RunEveryT() submit job")
		j.RunT(jt)
		if j.lastError != nil {
			j.errorf(
				j.ctx,
				"RunEveryT: Aborting: Job submission failed for job %s with %s",
				j.JobID(),
				j.lastError.Error(),
			)
			return j.lastError
		}
	}
	return nil
}
func (j *Job) After(d time.Duration) *Job {
	j.infof(j.ctx, "After()")
	<-time.After(d)
	return j
}
func wait(task *task) {
	if task.terminated == true {
		return
	}
	if task.job == nil {
		if task.jobArray == nil {
			return
		}
		task.terminationError = waitArrayJobTerminated(task.jobArray)
		task.terminated = true
		return
	}
	task.terminationError = task.job.WaitTerminated(drmaa2interface.InfiniteTime)
	task.terminated = true
	task.jobinfo, task.jobinfoError = task.job.GetJobInfo()
	task.waitForEndStateCollectedJobInfo = true
}
func (j *Job) Wait() *Job {
	j.infof(j.ctx, "Wait()")
	j.lastError = nil
	if task := j.lastJob(); task != nil {
		if task.job != nil {
			j.infof(j.ctx, fmt.Sprintf("Wait() for job %s", task.job.GetID()))
		} else if task.jobArray != nil {
			j.infof(j.ctx, fmt.Sprintf("Wait() for job array %s", task.jobArray.GetID()))
		}
		if task.waitForEndStateCollectedJobInfo {
			return j
		}
		wait(task)
	} else {
		j.errorf(
			j.ctx,
			"Wait() has no task to wait for",
		)
		j.lastError = errors.New("task not available")
	}
	return j
}
func (j *Job) Retry(r int) *Job {
	j.infof(j.ctx, "Retry()")
	for ; r > 0; r-- {
		if j.Wait().Success() {
			j.infof(j.ctx, "Retry(): Last task run successfully. No restart required.")
			return j
		}
		j.warningf(j.ctx, "Retry(): Last task failed. Resubmitting task %s.", j.JobID())
		j.Resubmit(1)
	}
	return j
}
func (j *Job) Synchronize() *Job {
	j.begin(j.ctx, "Synchronize()")
	for _, task := range j.tasklist {
		wait(task)
	}
	return j
}
func (j *Job) ListAllFailed() []drmaa2interface.Job {
	j.begin(j.ctx, "ListAllFailed()")
	failed := make([]drmaa2interface.Job, 0, len(j.tasklist))
	for _, task := range j.tasklist {
		if task.job == nil {
			continue
		}
		wait(task)
		if task.job.GetState() == drmaa2interface.Failed {
			failed = append(failed, task.job)
		}
	}
	return failed
}
func (j *Job) HasAnyFailed() bool {
	j.begin(j.ctx, "HasAnyFailed()")
	failed := j.ListAllFailed()
	return len(failed) > 0
}
func (j *Job) RetryAnyFailed(amount int) *Job {
	j.begin(j.ctx, fmt.Sprintf("RetryAnyFailed(%d)", amount))
	for i := 0; i < amount || amount == -1; i++ {
		for _, task := range j.tasklist {
			wait(task)
			if task.job.GetState() == drmaa2interface.Failed {
				failedJobID := task.job.GetID()
				replaceTask(j, task)
				j.warningf(j.ctx, "RetryAnyFailed(%d)): Task %s failed. Retry task (%s).",
					amount, failedJobID, task.job.GetID())
			}
		}
		if !j.HasAnyFailed() {
			break
		}
	}
	return j
}
func (j *Job) ReapAll() *Job {
	j.begin(j.ctx, fmt.Sprintf("ReapAll()"))
	for _, task := range j.tasklist {
		if task.job != nil {
			task.job.Reap()
		}
	}
	return j
}
func (j *Job) Success() bool {
	if j.State() == drmaa2interface.Done {
		if j.ExitStatus() == 0 {
			return true
		}
	}
	return false
}
func (j *Job) Errored() bool {
	if j.lastError != nil {
		return true
	}
	return false
}
func (j *Job) ExitStatus() int {
	j.infof(j.ctx, "ExitStatus()")
	j.Wait()
	if task := j.lastJob(); task != nil {
		return task.jobinfo.ExitStatus
	}
	j.errorf(j.ctx, "ExitStatus(): task not found")
	return -1
}
func (j *Job) Then(f func(job drmaa2interface.Job)) *Job {
	j.begin(j.ctx, fmt.Sprintf("Then(%s)",
		runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()))
	j.lastError = nil
	if task := j.lastJob(); task != nil && task.job != nil {
		task.terminationError = task.job.WaitTerminated(drmaa2interface.InfiniteTime)
		task.terminated = true
		task.jobinfo, task.jobinfoError = task.job.GetJobInfo()
		f(task.job)
	} else {
		j.errorf(j.ctx, "Then(%s): task not found",
			runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name())
		j.lastError = errors.New("task not available")
	}
	return j
}
func (j *Job) ThenRun(cmd string, args ...string) *Job {
	j.begin(j.ctx, "ThenRun()")
	return j.Wait().Run(cmd, args...)
}