#!/usr/bin/env perl

# Copyright (c) Illumina 2009
# Author: Richard Shaw
# This software is covered by the "Illumina Genome Analyzer Software
# License Agreement" and the "Illumina Source Code License Agreement",
# and certain third party copyright/licenses, and any user of this
# source file is bound by the terms therein (see accompanying files
# Illumina_Genome_Analyzer_Software_License_Agreement.pdf and
# Illumina_Source_Code_License_Agreement.pdf and third party
# copyright/license notices).

# Takes a CASAVA Task Manager task file as input and produces a graph of
# the specified workflow in DOT format.
# (This can then be displayed by tools such as dotty.)

use warnings;
use strict;

use File::Basename;
use File::Spec;
use Getopt::Long;
use Carp;

use lib '@CASAVA_FULL_PERL_LIBDIR@';

use Casava::Common::Log;
use Casava::TaskManager qw(%taskFields);

my $help = 0;
my $finishedFilePrefix=File::Spec->catfile('tmp', 'make', 'finished.');

my $result     = GetOptions(
    'finishedPrefix=s'  => \$finishedFilePrefix,
    "help|h"            => \$help
);

my $usage       =
    "task2make.pl [--finishedPrefix=<path to finished.* files>] < <tasks file> > <makefile> \n"
  . "Generates makefile from TaskManager task file\n"  
  . "\t--finishedPrefix     - make will create <finishedPrefix><task name> file for each completed task. (default: $finishedFilePrefix)\n"
  . "\t--help|-h            - Print this help\n";

if ($help) {
    print $usage;
    exit(0);
}    # if

my $not_applic_str = 'N/A';

sub replace_special_chars($)
{
    my ($str) = @_;
    $str =~ s/\W/_/g;
    return "$finishedFilePrefix$str.finished";
}

sub append_label_num($;$)
{
    my ($label_str, $label_num) = @_;

    # Square brackets are significant in DOT, so cannot use the TASKS
    # notation of Task Manager; instead append _N<num>.
    $label_str .= '_N' . $label_num if defined $label_num;
    return $label_str;
}

sub parse_prereqs($)
{
    my ($cmd) = @_;
    my @prereqs;

    if ($cmd =~ /TASKS \((.*)\)/) {
    my $prereqs_str = $1;
    my @prereq_strs = split(',', $prereqs_str);

    foreach my $prereq_str (@prereq_strs) {

        if ($prereq_str =~ /([^\[]+)\[(\d+)\]/) {
            my $prereq = append_label_num($1, $2);
            push(@prereqs, $prereq);
        } else {
            die("Unexpected format of prereq string : $prereq_str");
        }
    }
    } else {
        die("Unexpected prereqs string format : $cmd");
    }

    return @prereqs;
}

my %leaf_nodes;
my %checkpointTargets = ();

sub write_dependency($$)
{
    my ($prereq_label, $label) = @_;
    delete $leaf_nodes{$prereq_label};
    $leaf_nodes{$label} = 1;
    my $target = replace_special_chars($label);
    $target = $checkpointTargets{$target} if defined $checkpointTargets{$target}; 

    my $prereq_target = replace_special_chars($prereq_label);
    $prereq_target = $checkpointTargets{$prereq_target} if defined $checkpointTargets{$prereq_target}; 

    print <<"END"
$target: $prereq_target

END
}

sub write_command_rule($$)
{
    my ($label, $cmd) = @_;

    $leaf_nodes{$label} = 1;

    my $target = replace_special_chars($label);

    $cmd =~ s/\$/\$\$/g;

    print <<"END"
$target: \$(dir $target).sentinel
\t($cmd) >&2 \\
    \$(AND) \$(TOUCH) \$@

END
}

sub process_synch_rule($$$$)
{
    my ($label, $label_num, $user_label, $cmd) = @_;

    $label = append_label_num($label, $label_num);

    my @prereqs = parse_prereqs($cmd);

    foreach my $prereq_label (@prereqs)
    {
        delete $leaf_nodes{$prereq_label};
    }
    $leaf_nodes{$label} = 1;

    my $target = replace_special_chars($label);
    # .done targets don't generate WARNINGS during makefile execution if they are not present in file sytem
    my $phonyTarget = "$target.done";
    $checkpointTargets{$target} = $phonyTarget;
    
    my @prereq_targets = map {replace_special_chars($_)} @prereqs;
    @prereq_targets = map {(defined $checkpointTargets{$_}) ? $checkpointTargets{$_} : $_ } @prereq_targets;

    print <<"END"
.SECONDARY: $phonyTarget
$phonyTarget: @prereq_targets

END
}

print << "END";
default: all

\#include \$(MAKEFILES_DIR)/Sentinel.mk

END

while (my $line = <>) {
    chomp $line;
    # Although the fields are tab-separated they are also space-padded!
    # Trim off the spaces so the same tokens in different fields match.
    my @fields = split(/\ *\t/, $line);

    my $label = $fields[$taskFields{queueId}];
    my $label_num = $fields[$taskFields{taskId}];
    my $rule_type = $fields[$taskFields{taskType}];
    my $user_label = $fields[$taskFields{appTag}];
    my $state = $fields[$taskFields{status}];
    my $prereq_label = $fields[$taskFields{waitFor}];
    my $cmd = $fields[$taskFields{cmd}];

    # Task Manager allows forward slashes and dashes in labels
    # but dotty does not allow these in tokens.
    if ($rule_type eq 'task')
    {
        # Make the label unique by appending its number using the notation 
        # _#<num>.
        # Square brackets as in the TASKS list are significant in DOT so
        # cannot be used.
        my $raw_label = $label;
        $label = append_label_num($label, $label_num);
    
        # default shape is box
        write_command_rule($label, $cmd);
    
        # Explicit dependency
        if ($prereq_label ne $not_applic_str) {
    
            # A simple prereq must be unique, so assume it has a label number of 0.
            my $prereq_label_num = 0;
            $prereq_label = append_label_num($prereq_label, $prereq_label_num);

            write_dependency($prereq_label, $label);
        }
    
        # Implicit dependency : If label_num > 0 : dependency upon task with
        # same label but label_num one less.
        if ($label_num > 0)
        {
            my $implicit_prereq_label = append_label_num($raw_label, $label_num - 1);
            write_dependency($implicit_prereq_label, $label);
        }
    }
    elsif ($rule_type eq 'synch')
    {
        process_synch_rule($label, $label_num, $user_label, $cmd);
    }
    else
    {
        die("Unhandled rule type $rule_type");
    }
}

my @all_prereq_targets = map {replace_special_chars($_)} keys (%leaf_nodes);
@all_prereq_targets = map {(defined $checkpointTargets{$_}) ? $checkpointTargets{$_} : $_ } @all_prereq_targets;
print <<"END"
all: @all_prereq_targets

END
