#!/usr/bin/perl -w

#====================================================================#
#SimArray version 1.2                                                #
#                                                                    #
#(c) 2005 r.auburn[at]gen.cam.ac.uk                                  #
#====================================================================#

use strict;
use Getopt::Std;

#====================================================================#
#SimArray                                                            #
#                                                                    #
#This script generates a randomised spot layout, computes a maximum  #
#array area, and an estimated print time, in response to a range of  #
#user-defined design decisions. Some checks are included for ease of #
#use and to reduce errors. User configurable files ensure that this  #
#tool is also compatible with a range of production environments.    #
#Help text, a detailed description of each stage, and a glossary     #
#are available for further ease of use.                              #
#====================================================================#


#====================================================================#
#Administration, etc.                                                #
#====================================================================#


#--------------------------------------------------------------------#
#Sun Sep 11 11:11:10 2005 - Richard                                  #
#User options                                                        #
#--------------------------------------------------------------------#

#$opt_i: index file name (and location)
#$opt_h: user help/usage/instructions
#$opt_d: user detailed help/usage/instructions
#$opt_g: glossary of terms for this code and the user interface
#$opt_f: example file formats and a description of each
#%opt_v: version release notes
#$opt_T: tab-separated values   (TSV) for $REPORT sv_map
#$opt_S: space-separated values (TSV) for $REPORT sv_map (default)
#$opt_C: comma-separated values (TSV) for $REPORT sv_map

our($opt_i, $opt_h, $opt_d, $opt_g, $opt_f, $opt_v, $opt_T, $opt_S, $opt_C);
getopt('i');

#--------------------------------------------------------------------#
#Mon Jul 25 15:39:21 2005 - Richard                                  #
#Global variables (upper case)                                       #
#--------------------------------------------------------------------#

#Database (1st run): this is user configurable
my $TOOLS_FILE;			# Pin configs

#Sub-routines (1st run):
my $PROBE_NUMBER;		# Probes (i.e., wells) to be printed
my $PLATE_FORMAT;		# Microtitre plate format
my $PIN_CONFIG;			# Pin config. name ($TOOLS_FILE)
my $PINS_X;			# Pins, x-axis ($TOOLS_FILE)
my $PINS_Y;			# Pins, y-axis ($TOOLS_FILE)
my $SUB_GRIDS;			# Sub-grid number ($TOOLS_FILE)
my $MAX_GRID_AREA;		# Max. grid area

#Output (1st run):
my $FILENAME = "sources.txt";	# File, for user to edit after the 1st 
                                # run to input number of replicates
				# per source visit

#Database (2nd run):  these are user configurable
my $PINS_FILE;			# Spot densities 
my $TIME_FILE;			# Print times 

#Sub-routines (2nd run):
my $DELIMITER;			# Spot map deliminator for $REPORT
my $PIN_TYPE;			# User selected pins name ($PINS_FILE)
my $PIN_SPOTS_X;		# Spots printable in x-axis ($PINS_FILE)
my $PIN_SPOTS_Y;		# Spots printable in y-axis ($PINS_FILE)
my $SPOTS;			# Spots that can be printed by this pin
my $PIN_COMMENT;		# Comment for selected pin ($PINS_FILE)
my %SOURCES;			# Source (keys) and replicates (values)
my $MAX_LENGTH = 0;		# Number of digits in the highest source
my $REQUIRED_SPOTS_PER_GRID;	# Spots that need to be printed per grid
my $PIN_SELECTION_FLAG = 0;	# Evaluates to 1 when pin selection is OK
my $GRID_SPOTS_X;		# Spots to be printed in the x-axis
my $GRID_SPOTS_Y;		# Spots to be printed in the y-axis
my $GRID_TOTAL;			# Number of spots to be printed per grid
my $PRINT_TIME;			# Predicted print-time
my $ARRAYER_PIN;		# Arrayer selected by the user (pin)
my $ARRAYER_PRINT;		# Arrayer selected by the user (wash)
my $ARRAYER;			# Arrayer selected by the user
my $BUFFER;			# Spotting buffer class
my $SOURCE_TIME;		# Time per source ($TIME_FILE)
my $SOURCES_TO_PRINT;           # Spots that need to be printed
my %GRID;			# Store the sv_map

#Output (2nd run)
my $REPORT = "report.txt";	# Report, for the user (2nd parse)
				# Include user design decisions and
				# the proposed array design, etc.


#--------------------------------------------------------------------#
#Wed Sep 28 16:01:52 2005 - Richard and Ros                          #
#Reading the index file                                              #
#--------------------------------------------------------------------#

if ( $opt_i ) {
    #Quick modification to get configuration file locations
    my $index_file = $opt_i;
    open (INDEX, "<$index_file") or die ("Could not open $index_file: $!");
    my @config_files;
    while (<INDEX>) {
	chomp ($_);
	#Ignore headers
	if ( $_ =~ /^#/ ) {
	     next;
	 } else {
	     #Store non-headers
	     push (@config_files, $_);
	 }
    }
    close INDEX;

    for my $i (0 .. $#config_files) {
	if ( $config_files[$i] =~ /^tool_file=(\S+)$/ ) {
	    $TOOLS_FILE = $1;
	}
	if ( $config_files[$i] =~ /^pins_file=(\S+)$/ ) {
	    $PINS_FILE = $1;
	}
	if ( $config_files[$i] =~ /^time_file=(\S+)$/ ) {
	    $TIME_FILE = $1;
	}
    }
} elsif ( $opt_h ) {
    #List of commands
    &usage_standard();
    exit;
} elsif ( $opt_d ) {
    #Describe each stage
    &usage_detailed();
    exit;
} elsif ( $opt_g ) {
    #Glossary of terms
    &glossary();
    exit;
} elsif ( $opt_f ) {
    #Example config and index files
    &example_file_formats();
    exit;
} elsif ( $opt_v ) {
    #Version release notes
    &version();
    exit;
} else {
    #List of commands (default)
    &usage_standard();
    exit;
}

#--------------------------------------------------------------------#
#Sun Sep 11 11:38:01 2005 - Richard                                  #
#Deliminator, for $REPORT                                            #
#--------------------------------------------------------------------#

if ( $opt_T ) {
    $DELIMITER = "\t";#Tab-separated values   (TSV)
}elsif ( $opt_C ) {
    $DELIMITER = "," ;#Comma-separated values (CSV)
}elsif ($opt_S ) {
    $DELIMITER = " " ;#Space-separated values (SSV)
}else {
    $DELIMITER = " " ;#Space-separated values (default)
}



#====================================================================#
#Mon Jul 25 15:39:35 2005 - Richard                                  #
#Call sub-routines in the correct order                              #
#====================================================================#



#====================================================================#
#Wed Sep 14 13:46:16 2005 - Richard                                  #
#Process user input and generate output                              #
#====================================================================#

#Global variables are not assigned to sub-rountines
#Outputs are returned to permit exit from all loops

#Second run processes initialised when $FILENAME exists
if (-s $FILENAME) {
    ($REQUIRED_SPOTS_PER_GRID) = &do_compute_required_spots_per_grid();
    ($PIN_TYPE, $ARRAYER_PIN, $PIN_SPOTS_X, $PIN_SPOTS_Y, $PIN_COMMENT) = &do_get_pin_details();
    () = &do_compare_pin_and_spot_density();
    ($GRID_SPOTS_X, $GRID_SPOTS_Y) = &do_compute_grids_required();
    ($ARRAYER_PRINT, $BUFFER, $SOURCES_TO_PRINT, $PRINT_TIME) = &do_compute_print_time();
    (%GRID) = &do_make_sv_map();
    () = &do_make_report_for_the_user();
    print ( "\n" . "=" x 70 . "\n" );
    print ("r.auburn[at]gen.cam.ac.uk  http://www.flychip.org.uk/SimArray/\n\n");
} else {
    #First run processes initialised when $FILENAME does not exist
    ($PROBE_NUMBER) = &do_get_probe_number();
    ($PLATE_FORMAT) = &do_get_plate_format();
    ($PIN_CONFIG, $PINS_X, $PINS_Y, $SUB_GRIDS, $MAX_GRID_AREA) = &do_get_pin_config();
    () = &do_make_plate_source_replicates_file();
    print ( "\n" . "=" x 70 . "\n" );
    print ("r.auburn[at]gen.cam.ac.uk  http://www.flychip.org.uk/SimArray/\n\n");
}




#====================================================================#
#Sub-routines from this point onwards!                               #
#First run processes:                                                #
#====================================================================#



#--------------------------------------------------------------------#
#Sun Jul 17 14:28:07 2005 - Richard                                  #
#Enter number of probes to be printed                                #
#--------------------------------------------------------------------#

sub do_get_probe_number {

    #Header
    print ("\n" . "=" x 70 . "\n");
    print ("\n#PROBE NUMBER\n");

    #Request and process user input
    while (1) {
	print ("Enter number of probes (i.e. wells): ");
	chomp ($PROBE_NUMBER = <STDIN>);
	#Only permit integers 
	if ( $PROBE_NUMBER =~ /^\d+$/ ) {
	    return $PROBE_NUMBER;
	    last;
	}else{
	    print ("That wasn't an integer, please try again!\n\n");
	}
    }

}

#--------------------------------------------------------------------#
#Sun Jul 17 14:40:05 2005 - Richard & Ros                            #
#Enter plate format                                                  #
#--------------------------------------------------------------------#

sub do_get_plate_format {

    #Header
    print ("\n" . "=" x 70 . "\n");
    print ("\n#PLATE FORMAT\n");

   #Read $TOOLS_FILE and save to @all_tools
    open (FORMATS, "<$TOOLS_FILE") or die ("Could not open $TOOLS_FILE: $!");
    my @all_formats;
    my %seen_formats;
    while (<FORMATS>) {
	chomp ($_);
	#Ignore headers
	if ( $_ =~ /^#/ ) {
	     next;
	 } else {
	     #Store unique microtitre plate formats (i.e., 96, 384, or 1536)
	     #Following allows for tab -> space conversion by text editors
	     if ($_ =~ /(\d+)\s+/) {
		 my $format = ($1);
		 push (@all_formats, $format) unless $seen_formats{$format}++;
	     } else {
		 #When file is of the wrong format, warn and exit
		 print ("\n\nERROR: $TOOLS_FILE has an unkown file format!\n");
		 print ("ACTION: use -f, to see an example file\n\n");
		 print ("=" x 70 . "\n");
		 exit;
	     }
	 }
    }
    close TOOLS;

    

    #Read @all_formats and display to the user
    my @valid_format_keys;
    my @valid_formats;
    my $format;
    print ("Please select a plate format from the list below:\n\n");
    print ("." x 55 . "\n");
    print ("Key\tPlate\n");
    print ("." x 55 . "\n");    
    for my $i (0 .. $#all_formats) {
	print ($i."\t".$all_formats[$i]."\n");
	push (@valid_formats, $all_formats[$i]);
	push (@valid_format_keys, $i);
	}
    print ("." x 55 . "\n");

    #User selects the plate by entering a valid key
    my $format_flag=0;
    my $format_selected;
    while (1) {
	print ("\nEnter key for the required plate format: ");
	chomp (my $user_format_key = <STDIN>);
	#Only permit integers
	if ( $user_format_key !~ /^\d+$/ ) {
	    print ("That wasn't an integer, please try again!\n\n");
	    next;
	}
	#Store correct key values
	foreach my $key (@valid_format_keys) {
	    if ( $user_format_key == $key ) {
		$format_flag = 1;
	    } 
	}
	#Try again, if $user_tool_key is outside known range
	if ($format_flag == 0) {
	    print ("That wasn't a valid option!\n\n");
	    next;
	}else{
	    #Store the selected tool, when $tool_flag = 1
		$format_selected = $valid_formats[$user_format_key];
	}
	#No need to worry about tab -> space because this is an internal structure
	return ($PLATE_FORMAT) = split (/\t/, $format_selected);
    }

}

#--------------------------------------------------------------------#
#Sun Jul 17 14:43:52 2005 - Richard & Ros                            #
#Choose a pin configuration, i.e, a tool                             #
#--------------------------------------------------------------------#

sub do_get_pin_config {

    #Header
    print ("\n" . "=" x 70 . "\n");
    print ("\n#TOOLS AVAILABLE\n");

   #Read $TOOLS_FILE and save to @all_tools
    open (TOOLS, "<$TOOLS_FILE") or die ("Could not open $TOOLS_FILE: $!");
    my @all_tools;
    while (<TOOLS>) {
	chomp ($_);
	#Ignore headers
	if ( $_ =~ /^#/ ) {
	     next;
	 } else {
	     #Store non-headers
	     push (@all_tools, $_);
	 }
    }
    close TOOLS;

    #Read @all_tools and compute meta-grid dimensions
    #Only save result, when $PLATE_FORMAT compatible
    my (@valid_tools, $valid_tool);
    for my $i (0 .. $#all_tools) {
	my ($format, $tool, $grids, $distance, $distX, $distY);
	#Following allows for tab -> space conversion by text editors!
	if ($all_tools[$i] =~ /(\d+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(\d+)/) {
	    $format = $1;
	    $tool = $2;
	    $PINS_X = $3;
	    $PINS_Y = $4;
	    $grids = $5;
	} else {
	    #When file is the wrong format, warn and exit 
	    print ("\n\nERROR: $TOOLS_FILE has an unkown file format!\n");
	    print ("ACTION: use -f, to see an example file\n\n");
	    print ("=" x 70 . "\n");
	    exit;
	}
	#Microtitre plate format-specific pin-to-pin distances (mm)
	if ($format == 96) {
	    $distance = 9;
	}
	if ($format == 384) {
	    $distance = 4.5;
	}
	if ($format == 1536) {
	    $distance = 2.25;
	}
	#Compute meta-grid max. x- and y-axis distance
	$distX = ($PINS_X * $distance);
	$distY = ($PINS_Y * $distance);
	#Store data, for later usage
	if ($PLATE_FORMAT == $format) {
	    $valid_tool = ("$tool\t$PINS_X\t$PINS_Y\t$grids\t$distX x $distY");
	    push (@valid_tools, $valid_tool);
	}
    }

    #Display @valid_tools
    my @valid_tools_keys;
    print ("Please select a tool from the list below:\n\n");
    print ("." x 55 . "\n");
    print ("Key\tTool\tPins(X)\tPins(Y)\tGrids\tMax Area(mm)\n");
    print ("." x 55 . "\n");
    for my $i (0 .. $#valid_tools) {
	print ("$i\t$valid_tools[$i]\n");
	push (@valid_tools_keys, $i);
    }
    print ("." x 55 . "\n");

    #User selects the required tool
    my $tool_selected;
    my $tool_flag = 0;
    while (1) {
	print ("\nEnter key for the required tool: ");
	chomp (my $user_tool_key = <STDIN>);
	#Only permit integers
	if ( $user_tool_key !~ /^\d+$/ ) {
	    print ("That wasn't an integer, please try again!\n\n");
	    next;
	}
	#Store correct key values
	foreach my $key (@valid_tools_keys) {
	    if ( $user_tool_key == $key ) {
		$tool_flag = 1;
	    } 
	}
	#Try again, if $user_tool_key is outside known range
	if ($tool_flag == 0) {
	    print ("That wasn't a valid option!\n\n");
	    next;
	}else{
	    #Store the selected tool, when $tool_flag = 1
		$tool_selected = $valid_tools[$user_tool_key];
	}
	#No need to worry about tab -> space conversion, as this is internal to the script
	return ($PIN_CONFIG, $PINS_X, $PINS_Y, $SUB_GRIDS, $MAX_GRID_AREA) = split (/\t/, $tool_selected);
    }

}

#--------------------------------------------------------------------#
#Sun Jul 17 15:42:20 2005 - Richard                                  #
#Create $FILENAME                                                    #
#--------------------------------------------------------------------#

sub do_make_plate_source_replicates_file {
    
    #Header
    print ("\n" . "=" x 70 . "\n");
    print ("\n#SOURCE VISITS:\n");

    #Define file name and generate header
    open (SOURCES, ">$FILENAME") or die ("Could not make $FILENAME: $!");
    print (SOURCES "#Probes: $PROBE_NUMBER\n");
    print (SOURCES "#Plate format: $PLATE_FORMAT\n");
    print (SOURCES "#Print tool: $PIN_CONFIG\n");
    print (SOURCES "#Max area (mm): $MAX_GRID_AREA\n");
    print (SOURCES "#Plate\tSource\tReplicates\n");

    #Perform the required calculations:
    #source visit numbers:
    my $sources = ( $PROBE_NUMBER / $SUB_GRIDS );
    #Round up all decimals as partial sources are not possible
    if ($sources =~ /(\d+)\.(\d+)/) {
	$sources = int ( $PROBE_NUMBER / $SUB_GRIDS ) + 1;
    }
    #sources per plate:
    my $sources_per_plate = $PLATE_FORMAT / $SUB_GRIDS;
    
    #Compute $FILENAME contents
    my $plate = 1;
    my $plate_number = 0;
    #Cycle through all sources
    for my $i (0 .. $sources) {
	#Define which plate this source relates to and track plate number
	my $j = $sources_per_plate * $plate;
	if ($i > $j) {
	    $plate++;
	    if ($plate > $plate_number) {
		$plate_number = $plate;
	    }
	}
	#For source = 0, define plate and replicates as 0
	if ($i == 0) {
	    print (SOURCES "0\t$i\t0\n");
	}else {#For all other sources, use correct plate number and set replicates to 1
	    print (SOURCES "$plate\t$i\t1\n");
	       }
    }
    close SOURCES;

    #Additional information for the user
    print ("Now open $FILENAME and amend before re-running this script.\n");
    print ("Delete or rename $FILENAME to repeat this first run.\n");

}


#====================================================================#
#Second parse processes:                                             #
#====================================================================#

#--------------------------------------------------------------------#
#Sun Jul 17 16:23:59 2005 - Richard                                  #
#Compute spots required per grid, using $FILENAME data               #
#--------------------------------------------------------------------#

sub do_compute_required_spots_per_grid {

    #Header
    print ( "\n" . "=" x 70 . "\n" );
    print ("\n#REQUIRED SPOT `DENSITY'\n");

    #Read $FILENAME and save to @lines
    open (SOURCES, "<$FILENAME")  or die ( "Could not open $FILENAME: $!" );
    my @lines;
    while (<SOURCES>) {
	chomp ($_);
	#Ignore headers
	if ( $_ =~ /^#/ ) {
	     next;
	 } else {
	     #Store non-headers
	     push (@lines, $_);
	 }
    }
    close SOURCES;

    #Key (source) => value (replicates)
    for my $i ( 0 .. $#lines ) {
	#Quietly ignore $plate!
	my ($source, $replicates);
	#Following allows for tab -> space conversion by text editors
	if ($lines[$i] =~ /\s+(\d+)\s+(\d+)/) {
	    $source = $1;
	    $replicates = $2;
	} else {
	    #When wrong format, warn and exit
	    print ("\n\nERROR: $FILENAME has an unkown file format!\n");
	    print ("ACTION: use -f, to see an example file\n\n");
	    print ("=" x 70 . "\n");
	    exit;
	}
	$SOURCES{$source} = $replicates;
    }

    #Calculate total spots per grid
    foreach my $key (keys %SOURCES) {
	$REQUIRED_SPOTS_PER_GRID += $SOURCES{$key};
    }
    print ("Total spots per sub-grid: $REQUIRED_SPOTS_PER_GRID\n");
    `sleep 1`; #Pause for 1 second
    return ($REQUIRED_SPOTS_PER_GRID);

}

#--------------------------------------------------------------------#
#Sun Jul 17 16:23:59 2005 - Richard & Ros                            #
#Select a pin and confirm that it's valid                            #
#--------------------------------------------------------------------#

sub do_get_pin_details {

    #Header
    print ( "\n" . "=" x 70 . "\n" );
    print ("\n#PINS AVAILABLE\n");

    #Read database and save to @all_pins
    open (PINS, "<$PINS_FILE") or die ( "Could not open $PINS_FILE: $!" );
    my @all_pins;
    while (<PINS>) {
	chomp ($_);
	#Ignore headers
	if ( $_ =~ /^#/ ) {
	     next;
	 } else {
	     #Store non-headers
	     push (@all_pins, $_);
	 }
    }
    close PINS;

    #Display pin database
    my @pin_keys;
    print ("Please select a pin from the list below:\n\n");
    print ("." x 55 . "\n");
    print ("Key\tPin\tArrayer\tSpotsX\tSpotsY\tTotal\tComment\n");
    print ("." x 55 . "\n");
    for my $i (0 .. $#all_pins) {
	print ("$i\t$all_pins[$i]\n");
	push (@pin_keys, $i);
    }
    print ("." x 55 . "\n");    

    #User selects the required pin
    my $pin_flag = 0;
    my $pin_selected;
    while (1) {
	print ("\nEnter the key for the required pin: ");
	my $user_pin_key = <STDIN>;
	chomp ($user_pin_key);
	#Only permit integers
	if ( $user_pin_key !~ /^\d+$/ ) {
	    print ("That wasn't an integer, please try again!\n");
	    next;
	}
	#Check if $user_pin_key is valid
	foreach my $key (@pin_keys) {
	    if ( $user_pin_key == $key ) {
		$pin_flag = 1;
	    } 
	}
	#Check if $user_pin_key is valid
	if ($pin_flag == 0) {
	    print ("That wasn't a valid option!\n\n");
	}else{
	    #When valid, return data
	    for my $i (0 .. $#all_pins) {
		#Following compensates for tab -> space conversion by text editors
		if ($all_pins[$user_pin_key] =~ /(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)/) {
		   $PIN_TYPE = $1;
		   $ARRAYER_PIN = $2;
		   $PIN_SPOTS_X = $3;
		   $PIN_SPOTS_Y = $4;
		   $SPOTS = $5;
		   $PIN_COMMENT = $6;
	       } else {
		   #When wrong format, warn and exit
		   print ("\n\nERROR: $PINS_FILE has an unkown file format!\n\n");
		   print ("ACTION: use -f, to see an example file\n\n");
		   print ("=" x 70 . "\n");
		   exit;
	       }
		return ($PIN_TYPE, $ARRAYER_PIN, $PIN_SPOTS_X, $PIN_SPOTS_Y, $SPOTS, $PIN_COMMENT);
	    }
	}
    }

}

#--------------------------------------------------------------------#
#Mon Jul 18 15:02:56 2005 - Richard                                  #
#Evaluate if the pin and required spot density are compatible        #
#--------------------------------------------------------------------#

sub do_compare_pin_and_spot_density {
    
    #Header
    print ( "\n" . "=" x 70 . "\n" );
    print ("\n#EVALUATE PIN SELECTION\n");
    
    #Perform evaluation
    my $achievable_spots_per_grid = $PIN_SPOTS_X * $PIN_SPOTS_Y;
    if ($achievable_spots_per_grid >= $REQUIRED_SPOTS_PER_GRID) {
        #Message for when OK.
	print <<EOF;

$PIN_TYPE is OK to use because you need to print $REQUIRED_SPOTS_PER_GRID spots per sub-grid 
and $PIN_TYPE can print $achievable_spots_per_grid spots per sub-grid.

EOF
        `sleep 1`; #Pause for 1 second

    }else {
    #Message for when not OK
	print <<EOF;

$PIN_TYPE can not be used because you need to print $REQUIRED_SPOTS_PER_GRID spots per
sub-grid and $PIN_TYPE is only capable of printing $achievable_spots_per_grid spots.

You could:

[1] Restart $0 and choose a different pin type
    and/or reduce the number of replicates
[2] Delete $FILENAME and restart $0 to allow
    selection of a tool with more pins

The proposed microarray design is otherwise unworkable, as the
specified spotting pins are not able to print this many spots.

======================================================================

EOF
	exit;
    }
}

#--------------------------------------------------------------------#
#Thu Jul 21 18:50:19 2005 - Richard & Ros                            #
#Compute required number of spots in the x- and y-axis.              #
#--------------------------------------------------------------------#

sub do_compute_grids_required {

    #Header
    print ( "\n" . "=" x 70 . "\n" );
    print ("\n#COMPUTE SPOTS_X AND SPOTS_Y\n");

    #Ask user for 'spot number' margin
    my $margin = 0;
    while (1) {
	print ("Enter `spot number margin' (0 for no margin): ");
	chomp ($margin = <STDIN>);
	#Only permit integers
	if ( $margin =~ /^\d+$/ ) {
	    last;
	}else{
	    print ("That wasn't an integer, please try again!\n\n");
	}
    }

    #Compute min. and max. range for SPOTS_X and SPOTS_Y
    my $min = $REQUIRED_SPOTS_PER_GRID;
    my $max = $REQUIRED_SPOTS_PER_GRID + $margin;
    
    #Perform calculations, store only that which is within the desried range
    my %spots_per_grid;
    my $i = 0 ;
    my @grid_layout_keys;
    my @print_matrix;
    foreach my $PIN_SPOTS_X ( 1 .. $PIN_SPOTS_X ) {
	foreach my $PIN_SPOTS_Y ( 1 ..  $PIN_SPOTS_Y ) {
	    my $total = $PIN_SPOTS_X * $PIN_SPOTS_Y;
	    if ( $margin == 0) {
		if ($REQUIRED_SPOTS_PER_GRID == $total) {
		    $spots_per_grid{$i} = ($PIN_SPOTS_X."\t".$PIN_SPOTS_Y."\t".$total);
		    push (@print_matrix, "$i\t$PIN_SPOTS_X\t$PIN_SPOTS_Y\t$total\n");
		    push (@grid_layout_keys, $i);
		    $i++;
		}
	    } elsif ( ($total > $min - 1) && ($total < $max + 1) ) {
		$spots_per_grid{$i} = ($PIN_SPOTS_X."\t".$PIN_SPOTS_Y."\t".$total);
		push (@print_matrix, "$i\t$PIN_SPOTS_X\t$PIN_SPOTS_Y\t$total\n");
		push (@grid_layout_keys, $i);
		$i++;
	    }
	}
    }

    #Test to see if we have any solutions
    my $matrix_size = @print_matrix;
    if ($matrix_size == 0) {
	print ("\n" . "=" x 70 . "\n");
	print ("\n\nERROR: No plausible Spot_X and Spot_Y combinations between $min and $max\n");
        print ("ACTION: Restart $0 and use a higher `spot number margin'\n\n");
	print ("=" x 70 . "\n");
	exit;
    } else {
    #Displays results to the user, if the test is passed
	print ("\n" . "." x 55 . "\n");
	print ("Key\tSpotsX\tSpotsY\tTotal\n");
	print ("." x 55 . "\n");
	print (@print_matrix);
	print ("." x 55 . "\n");
	print ("Required: $REQUIRED_SPOTS_PER_GRID ; Min: $min ; Max: $max\n");
	print ("." x 55 . "\n");
    }

    #Ask user to pick the required grid layout and check input was correct
    my $grid_flag = 0;
    my $user_grid_layout_key;
    while (1) {
	print ("\nEnter the key for the desired grid layout: ");
	chomp ($user_grid_layout_key = <STDIN>);
	#Only permit integers
	if ( $user_grid_layout_key !~ /^\d+$/ ) {
	    print ("That wasn't an integer, please try again!\n");
	    next;
	}
	#When input is correct, set flag to 1
	foreach my $key (@grid_layout_keys) {
	    if ( $user_grid_layout_key == $key ) {
		$grid_flag = 1;
	    } 
	}
	#When input is incorrect, print error message
	if ($grid_flag == 0) {
	    print ("That wasn't a valid option!\n\n");
	}else{
	    #When flag is non zero, generate output
	    #Conversion of tab -> space is not an issue, as internal to the script
	    return ($GRID_SPOTS_X, $GRID_SPOTS_Y,$GRID_TOTAL) = split (/\t/, $spots_per_grid{$user_grid_layout_key});
	}
    }

}


#--------------------------------------------------------------------#
#Mon Jul 25 16:56:05 2005 - Richard                                  #
#Compute print times for each arrayer                                #
#--------------------------------------------------------------------#

sub do_compute_print_time {

    #Header
    print ( "\n" . "=" x 70 . "\n" );
    print ("\n#COMPUTE PRINT TIME\n");
    
    #Read $TIME_FILE and save to @wash, whilst remving headers
    open (WASH, "<$TIME_FILE")  or die ( "Could not open $TIME_FILE: $!" );
    my @wash;
    while (<WASH>) {
	chomp ($_);
	#Ignore headers
	if ( $_ =~ /^#/ ) {
	     next;
	 } else {
	     #Store non-headers in @wash
	     push (@wash, $_);
	 }
    }
    close WASH;

    #Display $TIME_FILE (print time database)
    my @wash_keys;
    print ("Please select a wash condition from the list below:\n\n");
    print ("." x 55 . "\n");
    print ("Key\tBuffer\tArrayer\tMins\tComment\n");
    print ("." x 55 . "\n");
    for my $i (0 .. $#wash) {
	print ("$i\t$wash[$i]\n");
	push (@wash_keys, $i);
    }
    print ("." x 55 . "\n");    


    #User selects the required wash condition
    my $wash_flag = 0;
    my $wash_selected;
    while (1) {
	print ("\nEnter the key for the required wash condition: ");
	my $user_wash_key = <STDIN>;
	chomp ($user_wash_key);
	#Only permit integers
	if ( $user_wash_key !~ /^\d+$/ ) {
	    print ("That wasn't an integer, please try again!\n");
	    next;
	}
	#Check if $user_pin_key is valid
	foreach my $key (@wash_keys) {
	    if ( $user_wash_key == $key ) {
		$wash_flag = 1;
	    } 
	}
	#When input is incorrect, print error message
	if ($wash_flag == 0) {
	    print ("That wasn't a valid option!\n\n");
	}else{
	    #When valid, return data to exit this sub-routine
	    for my $i (0 .. $#wash) {
		my $comment;
		#Following compensates for tab -> space conversion by some text editors
		if ($wash[$user_wash_key] =~ /(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/) {
		   $BUFFER = $1;
		   $ARRAYER_PRINT = $2;
		   $SOURCE_TIME = $3;
		   $comment = $4;
	       } else {
		   #When wrong format, warn and exit
		   print ("\n\nERROR: $TIME_FILE has an unkown file format!\n");
		   print ("ACTION: use -f, to see an example file\n\n");
		   print ("=" x 70 . "\n");
		   exit;
	       }
	    }
	    #Exit this loop
	    last;
	}
    }

    #Calculate total spots to be printed (ignore source = 0)
    foreach my $key (keys %SOURCES) {
	#Ignore source = 0 because this is a non-printed spot
	if ($key == 0) {
	    next;
	} else {
	    #Count number of spots to be printed
	    $SOURCES_TO_PRINT += $SOURCES{$key}; 
	}
    }

    #Calculate print-time (hours) and limit output to 1 decimal place
    $PRINT_TIME = sprintf("%.1f", ( ($SOURCES_TO_PRINT * $SOURCE_TIME ) / 60 ) );

    #Output results to user
    print ("\nThis print-run will take $PRINT_TIME hours (approx).\n");
    `sleep 1`; #Pause for 1 second
    return ($ARRAYER_PRINT, $BUFFER, $SOURCES_TO_PRINT, $PRINT_TIME);
}


#--------------------------------------------------------------------#
#Mon Aug 29 16:30:06 2005 - Richard and Ros                          #
#Generate sv_map for use with an arrayer / data tracking tool        #
#--------------------------------------------------------------------#

sub do_make_sv_map {

    #Extract data from previous run and save to @lines
    open (SOURCES, "<$FILENAME")  or die ( "Could not open $FILENAME: $!" );
    my @lines;
    while (<SOURCES>) {
	chomp ($_);
	#Only work on headers
	if ( $_ =~ /^#/ ) {
	     next;
	 } else {
	     #Store non-headers in @lines
	     push (@lines, $_);
	 }
    }
    close SOURCES;

    #Save sources
    my (@sources, $source_length);
    for my $i ( 0 .. $#lines) {
	#Quietly ignore $plate!
	my ($source, $replicates);
	#Following allows for tab -> space conversion by some text editors
	if ($lines[$i] =~ /\s+(\d+)\s+(\d+)/) {
	    $source = $1;
	    $replicates = $2;
	} else {
	    #When wrong format, warn and exit
	    print ("\n\nERROR: $FILENAME has an unkown file format!\n");
	    print ("ACTION: use -f, to see an example file\n\n");
	    print ("=" x 70 . "\n");
	    exit;
	}
	$source_length = length($source);
	if ($source_length > $MAX_LENGTH) {
	    $MAX_LENGTH = $source_length;
	}
	for ($i = 1; $i <= $replicates; $i++) {
	    push (@sources, $source);
	}
    }


   #Add not printed spots when the user defined grid is greater
   #than the number of spots to be printed
   my $user_defined_spot_number = $GRID_SPOTS_X * $GRID_SPOTS_Y;
   my $spots_to_be_printed = @sources;
   if ($user_defined_spot_number > $spots_to_be_printed){
       my $difference = $user_defined_spot_number - $spots_to_be_printed;
       my $i;
       for ($i = 0; $i < $difference; $i++) {
	    push (@sources, "0");
	}
   }

   #Store sources
    my ($x,$y);
    for ($x = 0; $x < $GRID_SPOTS_X; $x++) {
	for ($y = 0; $y < $GRID_SPOTS_Y; $y++) {
	    my $max = @sources;
	    my $element = int(rand($max));
	    my $source = splice (@sources , $element, 1);
	    $GRID{$x}{$y} = $source;
	}
    }
   
    return %GRID;

}

#--------------------------------------------------------------------#
#Mon Aug 29 16:30:27 2005 - Richard and Ros                          #
#Generate a report for the end user                                  #
#--------------------------------------------------------------------#

sub do_make_report_for_the_user {

    #Header
    print ( "\n" . "=" x 70 . "\n" );
    print ("\n#SUMMARY REPORT\n");

    #Extract data from previous run and save headers to @lines
    open (SOURCES, "<$FILENAME")  or die ( "Could not open $FILENAME: $!" );
    my @lines;
    while (<SOURCES>) {
	chomp ($_);
	#only work on headers
	if ( $_ !~ /^#/ ) {
	     next;
	 } else {
	     #Store headers in @lines
	     push (@lines, $_);
	 }
    }
    close SOURCES;

    #Extract useful data from @lines headers for the report
    #$lines[0] = Probe number, in $FILENAME
    if ($lines[0] =~ /^#Probes: (\d+)$/) {
	$PROBE_NUMBER = $1; 
    } else {
	$PROBE_NUMBER = "Unknown";
    }
    #$lines[1] = Plate format, in $FILENAME
    if ($lines[1] =~ /^#Plate format: (\d+)$/) {
	$PLATE_FORMAT = $1; 
    } else {
	$PLATE_FORMAT = "Unknown";
    }
    #$lines[2] = Tool, in $FILENAME 
    if ($lines[2] =~ /^#Print tool: (\d+\D\d+)$/) {
	$PIN_CONFIG = $1; 
    } else {
	$PIN_CONFIG = "Unknown";
    }
    #$lines[3] = max meta-grid area (mm), in $FILENAME
    if ($lines[3] =~ /^#Max area \(mm\): (\d+\D+\d+)$/) {
	$MAX_GRID_AREA = $1;
    } else {
	$MAX_GRID_AREA = "Unknown";
    }

    #Define date and time
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
    my $date = sprintf ("%4d-%02d-%02d", $year+1900,$mon+1,$mday);
    my $time = sprintf ("%02d:%02d:%02d", $hour,$min,$sec);

    #Print report
    open (REPORT, ">>$REPORT") or die ("Could not create $REPORT: $!");

print REPORT <<EOF;

#######################################################

#REPORT:
Date: $date
Time: $time

#USER DEFINED OPTIONS:
-------------------------------------------------------
Condition               Response
-------------------------------------------------------
Probe number         :  $PROBE_NUMBER
Plate format         :  $PLATE_FORMAT
Tool                 :  $PIN_CONFIG
Pin type             :  $PIN_TYPE ($ARRAYER_PIN)
Spots (per sub-grid) :  $GRID_TOTAL
Spots(X)             :  $GRID_SPOTS_X
Spots(Y)             :  $GRID_SPOTS_Y
Wash condition       :  $BUFFER ($ARRAYER_PRINT)
-------------------------------------------------------

#SCRIPT OUTPUT:
-------------------------------------------------------
Condition               Response
-------------------------------------------------------
Print time           :  $PRINT_TIME hours
Array area (max)     :  $MAX_GRID_AREA mm
-------------------------------------------------------

#SV_MAP:
EOF

    #Print sv_map with user defined delimiter
    my ($i, $x, $y, $length_diff);
    for ($y=0; $y < $GRID_SPOTS_Y; $y++) {
	for ($x=0; $x < $GRID_SPOTS_X; $x++) {
	    my $length = length($GRID{$x}{$y});
	    my $length_diff = $MAX_LENGTH - $length;
	    for ($i = 0; $i < $length_diff; $i++) {
		print (REPORT "0");
	    }
	    print (REPORT $GRID{$x}{$y} . $DELIMITER);
	}
	print (REPORT "\n");
    }
    print (REPORT "\n");

    close REPORT;

#Information for the user
print <<EOF;

A report has been generated to summarise this print design. If you
repeat the second run multiple times, the report will contain a
summart of each of these microarray designs.
EOF

}



#====================================================================#
#Help texts:                                                         #
#====================================================================#



#--------------------------------------------------------------------#
#Tue Jul 26 14:35:12 2005 - Richard                                  #
#Basic help text                                                     #
#--------------------------------------------------------------------#

sub usage_standard {

print <<EOF;

--------------------------------------------------------------------
USAGE
--------------------------------------------------------------------

First run: print $FILENAME for you to edit

   $0 -i index.sa       runs the script

Example index.sa file:

   http://www.flychip.org.uk/SimArray/downloads/source_code.php

Second run: print $REPORT with the results

   $0 -i index.sa       space separated source vist map
   $0 -Ti index.sa      tab-separated source vist map
   $0 -Ci index.sa      comma separated source vist map
   $0 -Si index.sa      space-separated source vist map

Additional help options:

   $0 -h                this help text
   $0 -d                detailed help hext
   $0 -g                glossary of terms
   $0 -f                example index and config. files
   $0 -v                version and release date

Enter CRTL-C to exit, at any time

--------------------------------------------------------------------
r.auburn[at]gen.cam.ac.uk        http://www.flychip.org.uk/SimArray/

EOF
}

#--------------------------------------------------------------------#
#Tue Jul 26 14:35:12 2005 - Richard                                  #
#Detailed help text                                                  #
#--------------------------------------------------------------------#

sub usage_detailed {

print <<EOF;

--------------------------------------------------------------------
SUMMARY
--------------------------------------------------------------------

This script needs to be run twice. The commands to run this script
are listed in the basic help text, -h key.

   First run: prints a file for you to edit
   Second run: prints the results

Results include the randomised source visit map, estimated meta-grid
area, and an estimated print time.

--------------------------------------------------------------------
FIRST RUN
--------------------------------------------------------------------

PROBE NUMBER:

Number of probes to be printed, including controls. If it helps, can
also be thought of as being 'the number of wells to be printed'.

PLATE FORMAT:

Microtitre plate type. Select from the range of plate formats that are
available by entering the correct index key.

TOOLS AVAILABLE:

Define the required pin configuration. Select from the range of tools
that are available by entering the correct index key.

SOURCE VISITS:

The source visits that need to be printed. Saves $FILENAME to the 
current working directory.

Plate:      Number of microtire plates
Source:     Source visit to be printed (zero means not printed)
Replicates: Number of times that each source is to be printed

This file can be edited by the user to define variable numbers of
replicates for each source. Do not modify the headers!

--------------------------------------------------------------------
SECOND RUN
--------------------------------------------------------------------

REQUIRED SPOT DENSITY:

Number of spots to be printed per sub-grid.

PINS AVAILABLE:

Pins available to the user for printing and the achievable spot number
per sub-grid.

EVALUATE PIN SELECTION:

Evaluate if the selected pin is compatible with the number of spots 
that need to be printed per sub-grid.

COMPUTE SPOTS_X AND SPOTS_Y:

When 'spot number margin' is set to zero:

Display the number of spots that can be printed in x- and y- axis that 
exactly matches the number of spot to be printed. COMPUTE SPOTS_X AND
SPOTS_Y will fail, if a solution is not mathematically possible.

When 'spot number margin' is non-zero:

Display all possible X and Y combinations, between minimum and maximum.
REQUIRED SPOT DENSITY is set to be the 'minimum', with the REQUIRED 
SPOT DENSITY plus 'spot number margin' set to be the 'maximum'.

Error message is displayed when there are no suitable solutions.

COMPUTE PRINT TIME

Select the correct print settings condition, then compute an estimated
print time.

SUMMARY REPORT

Generate a report that includes the key design decisions, and the
estimated print time, the maximum meta-grid area, and a source
visit map.

--------------------------------------------------------------------
r.auburn[at]gen.cam.ac.uk        http://www.flychip.org.uk/SimArray/

EOF
}

#--------------------------------------------------------------------#
#Wed Jul 27 10:54:59 2005 - Richard                                  #
#Glossary                                                            #
#--------------------------------------------------------------------#

sub glossary {

print <<EOF;

--------------------------------------------------------------------
TERM               DEFINITION
--------------------------------------------------------------------
Meta-grid	   Spots printed by all pins in the tool
Plate format       Microtitre plate format (96, 384, or 1536)
Probe number       Number of probe/wells to be printed
Replicates         Number of times each source is printed
Source visit	   The act of filling a pin with probe
Sub-grid           The patch of spots that is printed by one pin
Tool               Permitted pin configurations
--------------------------------------------------------------------
r.auburn[at]gen.cam.ac.uk        http://www.flychip.org.uk/SimArray/

EOF
}

#--------------------------------------------------------------------#
#Mon Oct 31 17:27:03 2005 - Richard                                  #
#Example index and configuration files                               #
#--------------------------------------------------------------------#

sub example_file_formats {

print <<EOF;

--------------------------------------------------------------------
INDEX FILE
--------------------------------------------------------------------

#Version 1.1 (Example)

#USAGE:
#
#Use this file to point to each of your configuration
#files. All lines that start with a hash are ignored.
#
#WARNING
#
#DO NOT MODIFY THE EQUALS SIGN OR THE TEXT THAT IS 
#BEFORE IT, AS SimAarray NEEDS THIS INFORMATION


#TOOL FILE:
#Describes the available pin configurations
#
#Include the full or relative file path:
#[UNIX]
#tool_file=/home/adirectory/anotherdirectory/example.tool
#tool_file=anotherdirectory/example.tool
#[Windows]
#tool_file=C:\\adirectory\\anotherdirectory\\example.tool
#tool_file=anotherdirectory\\example.tool

tool_file=/home/richard/SimArray/config/standard.tool

#PINS FILE:
#Describes the pins and their achievable spot densities
#
#Include the full or relative file path:
#[UNIX]
#tool_file=/home/adirectory/anotherdirectory/example.pins
#tool_file=anotherdirectory/example.pins
#[Windows]
#tool_file=C:\\adirectory\\anotherdirectory\\example.pins
#tool_file=anotherdirectory\\example.pins

pin_file=/home/richard/SimArray/config/standard.pins

#TIME FILE
#
#Defines single print cycle time, indexed by print settings
#Include the full or relative file path:
#[UNIX]
#time_file=/home/adirectory/anotherdirectory/example.time
#time_file=anotherdirectory/example.time
#[Windows]
#time_file=C:\\adirectory\\anotherdirectory\\example.time
#time_file=anotherdirectory\\example.time

time_file=/home/richard/SimArray/config/standard.time

--------------------------------------------------------------------
TOOL FILE
--------------------------------------------------------------------

#Version 1.0 (Example)
#
#Plate    :     Microtitre plate format
#Tool     :     Pin configuration name
#Pins(X)  :     Number of pins (x-axis)
#Pins(Y)  :     Number of pins (y-axis)
#Grids    :     Number of sub-grids, per meta-grid
#
#Plate  Tool    Pins(X) Pins(Y) Grids
96      1x1     1       1       1
96      2x2     2       2       4
96      2x3     2       3       6
96      2x4     2       4       8
96      2x6     2       6       12
384     1x1     1       1       1
384     2x2     2       2       4
384     4x4     4       4       16
384     4x6     4       6       24
384     4x8     4       8       32
384     4x12    4       12      48
1536    1x1     1       1       1
1536    2x2     2       2       4
1536    4x4     4       4       16
1536    8x8     8       8       64
1536    8x16    8       16      128
1536    8x24    8       24      192

--------------------------------------------------------------------
PINS FILE
--------------------------------------------------------------------

#Version 1.0 (Example)
#
#Pin      :     Spotting pin (type, model, etc.)
#Arrayer  :     Spotting instrument (type, model, etc.)
#SpotsX   :     Maximum number of spots per sub-grid (x-axis)
#SpotsY   :     Maximum number of spots per sub-grid (y-axis)
#Total    :     Total number of spots per sub-grid
#Comment  :     Free text `comment' for the user
#
#Pin    Arrayer SpotsX  SpotsY  Total   Comment
Type1   Type1   40      40      1600    Example
Type2   Type1   30      30      900     Example
Type3   Type1   20      20      400     Example

--------------------------------------------------------------------
TIME FILE
--------------------------------------------------------------------

#Version 1.1 (Example)
#
#Setting:       Print setting employed
#Arrayer:       Spotting instrument (type, model, etc.)
#Mins   :       Time for one print-cycle, in minutes
#Comment:       Free text `comment' for the user
#
#Setting Arrayer Mins    Comment
Salt    Type1   1.8     90 slides
Salt    Type1   1.4     40 slides
DMSO    Type1   1.7     90 slides
DMSO    Type1   1.3     40 slides

--------------------------------------------------------------------
r.auburn[at]gen.cam.ac.uk        http://www.flychip.org.uk/SimArray/

EOF

}


#--------------------------------------------------------------------#
#Mon Oct 31 17:27:03 2005 - Richard                                  #
#Example index and configuration files                               #
#--------------------------------------------------------------------#

sub version {

print <<EOF;

--------------------------------------------------------------------
VERSION
--------------------------------------------------------------------

Version:

This is version 1.2

Release:

This was released on 8th December, 2005

Notes:

1. Switched to use of a 'time' config. file,instead of a 'wash'
   config. file to prevent confusion with regard to what the 
   print time estimate represents.
2. Modified the internal variables and the help texts to reflect
   the above change. This will also help to prevent confusion. 

--------------------------------------------------------------------
r.auburn[at]gen.cam.ac.uk        http://www.flychip.org.uk/SimArray/

EOF

}

