#!/usr/bin/env perl

# Copyright (c) Illumina 2009
# Author: Roman Petrovski
# 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 gantt chart of
# the specified workflow execution timing.

use warnings;
use strict;

use File::Basename;
use Getopt::Long;

use lib '/home/psgendb/local/pkg/CASAVA_v1.8.2-build/lib/CASAVA-1.8.2/perl';
use Casava::Common::Log;

use perlchartdir;

#------------------------------------------------------------------------------

use Casava::TaskManager qw(%taskFields);

my $discard = 0;
my $keep_name = 1;
my $keep_value = 2;
my $keep_name_and_value = 3;

my $CFG_USE_DESCRIPTION = 0;
my $CFG_DONT_ABBREVIATE_COMMANDS = 0;
my $CFG_DISCARD_REGEX = "";
my $CFG_HELP = 0;
my $CFG_WORKFLOW_FILE = "";
my $CFG_GRAPH_TASK_TYPE = 0;
my @CFG_RATIO = ();
my $CFG_FILE_TYPE = 'png';
my $CFG_X_AXIS_STEP = 10;
my @CFG_SORT=();


sub formatLabel($;$;$;$;$)
{
    my $taskQueueId         = shift;
    my $taskAppTag          = shift;
    my $taskId              = shift;
    my $taskCmd             = shift;
    my $taskHost            = shift;

    my $taskLabel = "$taskHost:$taskQueueId\[$taskId\] " . (
        ($CFG_USE_DESCRIPTION) ? "($taskAppTag)" : '');

    if( '' ne $CFG_DISCARD_REGEX)
    {
        $taskCmd =~ s/$CFG_DISCARD_REGEX/.../g;
    }
    
    if ($CFG_DONT_ABBREVIATE_COMMANDS)
    {
        $taskLabel .= " $taskCmd";
    }
    
    return ($taskLabel);
}

sub textTimeToCtTime
{
    my $taskTime   = shift;
    if (defined $taskTime)
    {
        my (
             $taskYear, $taskMonth,  $taskDay,
             $taskHour, $taskMinute, $taskSecond
        ) = $taskTime =~ /(\d+)\/(\d+)\/(\d+)\s(\d+):(\d+):(\d+)/;
        my $ctTaskTime =
          perlchartdir::chartTime(
                                   $taskYear,   $taskMonth,
                                   $taskDay,    $taskHour,
                                   $taskMinute, $taskSecond
          );
        return $ctTaskTime;
    }
    return undef;
}

my $result     = GetOptions(
    "tasks|t=s"         => \$CFG_WORKFLOW_FILE,
    "useDescription"    => \$CFG_USE_DESCRIPTION,
    "graphTaskType=s"   => \$CFG_GRAPH_TASK_TYPE,
    "dontAbbreviate"    => \$CFG_DONT_ABBREVIATE_COMMANDS,
    "discardRegex=s"    => \$CFG_DISCARD_REGEX,
    "ratio=f{2}"        => \@CFG_RATIO,
    "fileType=s"        => \$CFG_FILE_TYPE,
    "xStep=i"           => \$CFG_X_AXIS_STEP,
    "sort:s{,}"         => \@CFG_SORT,
    "help|h"            => \$CFG_HELP
);

if (!defined $CFG_RATIO[0])
{
    $CFG_RATIO[0] = $CFG_RATIO[1] = 1;
}

if (!scalar @CFG_SORT)
{
    @CFG_SORT = qw(end submit start);
}

my $usage       =
    "task2gantt.pl [options]\n"
  . "Generates the gantt-like chart of workflow execution.\n"  
  . "\t--tasks|t                - PATH to tasks file (required).\n"
  . "\t--dontAbbreviate         - Don't abbreviate commands. Works only if useDescription is not set\n"
  . "\t--useDescription         - use task description field for name generation instead of being smart about the command line content.\n"
  . "\t--discardRegex           - regular expression to discard matches from generated label names. Works only with dontAbbreviate\n"
  . "\t--graphTaskType          - (task|synch) determines which records to include in graph\n"
  . "\t--ratio rx ry            - X and Y multipliers for thre resulting image. default is 1 and 1\n"
  . "\t--xStep mins             - X axis mark step (minutes). Default $CFG_X_AXIS_STEP.\n"
  . "\t--sort submit|start|end|name|host|duration    - defines criteria list for sorting the rows. Default 'end submit start'.\n"
  . "\t--fileType type          - output image file type. Supported are: png (default), jpg, jpeg, gif, wbmp, bmp, svg, svgz\n"
  . "\t--help|-h                - Print this help\n";


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

errorExit "ERROR: tasks file not specified\n$usage" if ( $CFG_WORKFLOW_FILE eq "");
errorExit "ERROR: invalid value for graphTaskType\n$usage" if ( not grep /^$CFG_GRAPH_TASK_TYPE$/, ("task", "synch"));

my $pngFilePath;
if ($CFG_WORKFLOW_FILE =~ /(\S+).txt$/) {
    $pngFilePath = join('.', $1, $CFG_FILE_TYPE);
} else {
    $pngFilePath = join('.', $CFG_WORKFLOW_FILE, $CFG_FILE_TYPE);
}

my @tasks = ();
my $ctMinSubmitTime;
my $MinSubmitTimeText;
my $ctMaxFinishTime;
my $maxFinishTimeText;
my $totalRunTime = 0;
my $totalQueueWaitTime = 0;

my ($blue, $orange, $green, $red) = (0x0080c0, 0xff8000, 0x00ff00, 0xff0000);

my %stateColor = (
    pending => $blue,
    running => $orange,
    finished => $green
);

open(WORKFLOW, "<$CFG_WORKFLOW_FILE") or die("Failed to open $CFG_WORKFLOW_FILE");

while (my $line = <WORKFLOW>)
{
    chomp $line;
    my @fields = split(/\t/, $line);

    # Although the fields are tab-separated they are also space-padded!
    # Trim off the spaces so the same tokens in different fields match.
    for (my $field_ind = 0; $field_ind < scalar @fields; ++$field_ind)
    {
    	$fields[$field_ind] =~ s/^\s+//;
    	$fields[$field_ind] =~ s/\s+$//;
    }

    my $taskType = $fields[$taskFields{taskType}];
    my $taskState = $fields[$taskFields{status}];

    if ('waiting' ne $taskState and $CFG_GRAPH_TASK_TYPE eq $taskType)
    {
        my $taskQueueId = $fields[$taskFields{queueId}];
        my $taskId = $fields[$taskFields{taskId}];
        my $taskAppTag = $fields[$taskFields{appTag}];
#        my $taskWaitFor = $fields[$taskFields{waitFor}];
        my $taskCmd = $fields[$taskFields{cmd}];
        my $taskSubmitTime = $fields[$taskFields{submitTime}];
        my $taskStartTime = $fields[$taskFields{startTime}];
        my $taskFinishTime = $fields[$taskFields{finishTime}];
        my $taskHost = $fields[$taskFields{host}];
        if (!defined $taskHost)
        {
            $taskHost = 'unknown';
        }
    
        my $ctTaskFinishTime = textTimeToCtTime ($taskFinishTime);
        my $ctTaskStartTime = textTimeToCtTime ($taskStartTime);
        my $ctTaskSubmitTime = textTimeToCtTime ($taskSubmitTime);

        my $taskColor = (defined $stateColor{$taskState}) ? $stateColor{$taskState} : $red;

        if (!defined $ctMinSubmitTime || $ctTaskSubmitTime < $ctMinSubmitTime)
        {
            $ctMinSubmitTime = $ctTaskSubmitTime;
            $MinSubmitTimeText = $taskSubmitTime;
        }
        
        my $ctRightMostTime = $ctTaskFinishTime;
        my $rightMostTime = $taskFinishTime;
        if (!defined $ctRightMostTime)
        {
            $ctRightMostTime = $ctTaskStartTime;
            $rightMostTime = $taskStartTime;
        }
        
        if (defined $ctRightMostTime && (!defined $ctMaxFinishTime || $ctRightMostTime > $ctMaxFinishTime))
        {
            $ctMaxFinishTime = $ctRightMostTime;
            $maxFinishTimeText = $rightMostTime;
        }

        my $taskLabel = formatLabel ($taskQueueId, $taskAppTag, $taskId, $taskCmd, $taskHost);
        
        my @task = ($ctTaskSubmitTime, $ctTaskStartTime, $ctTaskFinishTime, $taskLabel, $taskColor, $taskHost, $taskQueueId, $taskId);
        push @tasks, \@task;
    }
}

close(WORKFLOW);
errorExit 'No task has started yet' unless (defined $ctMaxFinishTime);

foreach my $task (@tasks)
{
    if (!defined @{$task}[1])
    {
        @{$task}[1] = $ctMaxFinishTime;
    }
    if (!defined @{$task}[2])
    {
        @{$task}[2] = $ctMaxFinishTime;
    }
    my $taskRunTime = @{$task}[2] - @{$task}[1];
    my $taskTotalTime = @{$task}[2] - @{$task}[0];
    my $taskQueueWaitTime = $taskTotalTime - $taskRunTime;
    @{$task}[8] = $taskRunTime;
    $totalRunTime += $taskRunTime;
    $totalQueueWaitTime += $taskQueueWaitTime;
}

my $workflowDuration = sprintf("%.2f", ($ctMaxFinishTime - $ctMinSubmitTime) / 60 / 60);
$totalQueueWaitTime = sprintf("%.2f", $totalQueueWaitTime / 60 / 60);
$totalRunTime = sprintf("%.2f", $totalRunTime / 60 / 60);

my %sortColumnSubs = (
    submit  => sub {(@$a)[0] <=> (@$b)[0]},
    start   => sub {(@$a)[1] <=> (@$b)[1]},
    end     => sub {(@$a)[2] <=> (@$b)[2]},
    name    => sub {(@$a)[6] cmp (@$b)[6] || (@$a)[7] <=> (@$b)[7]},
    host    => sub {(@$a)[5] cmp (@$b)[5]},
    duration => sub {(@$b)[8] <=> (@$a)[8]},
);

my @sortedTasks = sort{ 
    foreach my $column (@CFG_SORT) {
        return (&{$sortColumnSubs{$column}} || next);
    }
} @tasks;


my @taskSubmitTimes =   map ((@$_)[0], @sortedTasks);
my @taskStartTimes =    map ((@$_)[1], @sortedTasks);
my @taskFinishTimes =   map ((@$_)[2], @sortedTasks);
my @taskLabels =        map ((@$_)[3], @sortedTasks);
my @taskColors =        map ((@$_)[4], @sortedTasks);


my $baseFontHeight = $CFG_RATIO[1] * 8;
my $titleFontHeight = $baseFontHeight + 2;
my $plotTitleHeight = $CFG_RATIO[1] * 40 + $baseFontHeight;
my $plotStatusHeight = $CFG_RATIO[1] * 30;
my $plotHeight = ($baseFontHeight + $CFG_RATIO[1]) * scalar (@taskLabels) ;
my $plotAreaX = $CFG_RATIO[0] * 20;
my $plotAreaW = $CFG_RATIO[0] * 512;
my $plotAreaY = $plotTitleHeight;
my $plotAreaH = $plotHeight;
my $chartWidth = $CFG_RATIO[0] * 1024;
my $chartHeight = $plotTitleHeight + $plotHeight + $plotStatusHeight;

printLog ( "chart titleFonthHeight: $baseFontHeight\n", 0);
printLog ( "chart plotTitleHeight: $plotTitleHeight\n", 0);
printLog ( "chart plotStatusHeight: $plotStatusHeight\n", 0);
printLog ( "chart plotHeight: $plotHeight\n", 0);
printLog ( "chart plotAreaX: $plotAreaX\n", 0);
printLog ( "chart plotAreaW: $plotAreaW\n", 0);
printLog ( "chart plotAreaY: $plotAreaY\n", 0);
printLog ( "chart plotAreaH: $plotAreaH\n", 0);
printLog ( "chart chartWidth: $chartWidth\n", 0);
printLog ( "chart chartHeight: $chartHeight\n", 0);


# Create a XYChart object. Set background color to light
# blue (ccccff), with 1 pixel 3D border effect.
my $c = new XYChart($chartWidth, $chartHeight, 0xccccff, 0x000000, 1);

 
# Add a title to the chart using $baseFontHeight points Times Bold Itatic font, with white
# (ffffff) text on a deep blue (000080) background
$c->addTitle("$CFG_WORKFLOW_FILE Duration: $workflowDuration h, TotalQueueWait: $totalQueueWaitTime h, TotalRuntime $totalRunTime h", "timesbi.ttf", $titleFontHeight, 0xffffff)->setBackground(0x000080);

 

# Set the plotarea. Use alternative
# white/grey background. Enable both horizontal and vertical grids by setting their
# colors to grey (c0c0c0). Set vertical major grid (represents month boundaries) 2
# pixels in width
$c->setPlotArea($plotAreaX, $plotAreaY, $plotAreaW, $plotAreaH, 
                0xffffff, 0xeeeeee, $perlchartdir::LineColor,
    0xc0c0c0, 0xc0c0c0)->setGridWidth(2, 1, 1, 1);

 

# swap the x and y axes to create a horziontal box-whisker chart
$c->swapXY();

# Set the y-axis scale to be date scale from Aug 16, 2004 to Nov 22, 2004, with ticks
# every 7 days (1 week)

$c->yAxis()->setDateScale($ctMinSubmitTime, $ctMaxFinishTime, $CFG_X_AXIS_STEP, 0);

printLog ( "Generating plot $pngFilePath for range $MinSubmitTimeText - $maxFinishTimeText\n", 0 );
 

# Set multi-style axis label formatting. Month labels are in Arial Bold font in "mmm
# d" format. Weekly labels just show the day of month and use minor tick (by using
# '-' as first character of format string).
#$c->yAxis()->setMultiFormat(perlchartdir::StartOfMonthFilter(),
#    "<*font=arialbd.ttf*>{value|mmm d}", perlchartdir::StartOfDayFilter(), "-{value|d}");

#$c->yAxis()->setMultiFormat(perlchartdir::StartOfMonthFilter(), "<*font=arialbd.ttf*>{value|mmm d}", 
#    perlchartdir::StartOfDayFilter(), "-{value|d}",
#    perlchartdir::StartOfHourFilter(), "-{value|d h}",
#    perlchartdir::AllPassFilter(), "-{value|d hh:nn:ss}");
 
$c->yAxis()->setMultiFormat(perlchartdir::StartOfDayFilter(), "-{value|d mmm}",
#    perlchartdir::AllPassFilter(), "-{value|hh:nn:ss}", 3);
    perlchartdir::RegularSpacingFilter(60), "-{value|hh:nn:ss}", 3);

# Set the y-axis to shown on the top (right + swapXY = top)
$c->setYAxisOnRight();

$c->setXAxisOnTop();

# Set the labels on the x axis
($c->xAxis()->setLabels(\@taskLabels))->setFontSize($baseFontHeight);

# Reverse the x-axis scale so that it points downwards.
$c->xAxis()->setReverse();

 
# Set the horizontal ticks and grid lines to be between the bars
$c->xAxis()->setTickOffset(0.5);

# Add a green (33ff33) box-whisker layer showing the box only.
#$c->addBoxWhiskerLayer2(\@taskStartTimes, \@taskFinishTimes, undef, undef, undef, \@taskColors);
$c->addBoxLayer(\@taskSubmitTimes, \@taskStartTimes, $blue);
#$c->addBoxLayer(\@taskStartTimes, \@taskFinishTimes);
$c->addBoxWhiskerLayer2(\@taskStartTimes, \@taskFinishTimes, undef, undef, undef, \@taskColors);

 
# Output the chart
$c->makeChart($pngFilePath)



