# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA



##################################################################
# There is a problem with using Win32::OLE from a forked process
# this means that we have to run it in a completely different
# environment which is why we exec another perl process
# http://bugs.activestate.com/show_bug.cgi?id=38968
##################################################################


package Scmbug::Daemon::TestDirector;

@ISA = qw( Scmbug::Daemon::Bugtracker );

use strict;
use Data::Dumper;
use Scmbug::Daemon::Bugtracker;
use Scmbug::Common;
use Scmbug::VDD;



my $td_bug_status_map = undef;



sub bug_owner {
    my $self = shift;
    my $data = shift;

    if ( $data ) {
	$self->{ bug_owner } = $data;
    } else {
	return $self->{ bug_owner };
    }
}



sub bug_status {
    my $self = shift;
    my $data = shift;

    if ( $data ) {
	$self->{ bug_status } = $data;
    } else {
	return $self->{ bug_status };
    }
}


#
# The Test Director tags for given fields
#
sub tdc_bug_summary_tag {
    my $self = shift;
    my $data = shift;

    if ( $data ) {
	$self->{ tdc_bug_summary_tag } = $data;
    } else {
	return $self->{ tdc_bug_summary_tag };
    }
}



sub tdc_product_name_tag {
    my $self = shift;
    my $data = shift;

    if ( $data ) {
	$self->{ tdc_product_name_tag } = $data;
    } else {
	return $self->{ tdc_product_name_tag };
    }
}



sub tdc_bug_owner_tag {
    my $self = shift;
    my $data = shift;

    if ( $data ) {
	$self->{ tdc_bug_owner_tag } = $data;
    } else {
	return $self->{ tdc_bug_owner_tag };
    }
}



sub tdc_bug_status_tag {
    my $self = shift;
    my $data = shift;

    if ( $data ) {
	$self->{ tdc_bug_status_tag } = $data;
    } else {
	return $self->{ tdc_bug_status_tag };
    }
}



sub tdc_bug_comment_tag {
    my $self = shift;
    my $data = shift;

    if ( $data ) {
	$self->{ tdc_bug_comment_tag } = $data;
    } else {
	return $self->{ tdc_bug_comment_tag };
    }
}



sub tdc_bug_severity_tag {
    my $self = shift;
    my $data = shift;

    if ( $data ) {
	$self->{ tdc_bug_severity_tag } = $data;
    } else {
	return $self->{ tdc_bug_severity_tag };
    }
}



sub tdc_bug_resolution_tag {
    my $self = shift;
    my $data = shift;

    if ( $data ) {
	$self->{ tdc_bug_resolution_tag } = $data;
    } else {
	return $self->{ tdc_bug_resolution_tag };
    }
}



sub tdc_bug_priority_tag {
    my $self = shift;
    my $data = shift;

    if ( $data ) {
	$self->{ tdc_bug_priority_tag } = $data;
    } else {
	return $self->{ tdc_bug_priority_tag };
    }
}


#
# Builds the command line required to make a test director call
#
sub build_command_line {
    my $self = shift;
    my $bugid = shift;
    my $operation = shift;
    my $tag_name = shift;

    my $cmd_line = "perl -I \"";
    $cmd_line .= "C:/Program Files/Scmbug/share/scmbug/lib/";
    $cmd_line .= "\" \"";
    $cmd_line .= "C:/Program Files/Scmbug/share/scmbug/lib/Scmbug/Daemon/TestDirectorInterface.pl";
    $cmd_line .= "\" ";

    my $cmd_args = $operation;
    $cmd_args .= " ";

    # Now append the database connection information
    $cmd_args .= $self->location();
    $cmd_args .= " ";
    $cmd_args .= $self->username();
    $cmd_args .= " \"";
    $cmd_args .= $self->password();
    $cmd_args .= "\" ";
    $cmd_args .= $self->database_vendor();
    $cmd_args .= " ";
    $cmd_args .= $self->database();
    $cmd_args .= " ";

    $cmd_args .= $bugid;
    $cmd_args .= " ";
    $cmd_args .= $tag_name;

    return $cmd_line . $cmd_args;
}



#
# Initialize configuration information specific to TestDirector
#
sub init_specific {
    my $self = shift;
    my $testdirector_configuration;

    my $td_config_file = 'C:/Program Files/Scmbug/etc/scmbug/TestDirector.conf';

    my $file_content = file_content( $td_config_file );
    eval $file_content or die "Error parsing file '$td_config_file': $@";
    $testdirector_configuration->{ configuration_file } = $td_config_file;

    $td_bug_status_map = $testdirector_configuration->{ testdirector_config }->{ tdc_bug_status_map };
    $self->tdc_bug_summary_tag( $testdirector_configuration->{ testdirector_config }->{ tdc_bug_summary } );
    $self->tdc_product_name_tag( $testdirector_configuration->{ testdirector_config }->{ tdc_product_name } );
    $self->tdc_bug_owner_tag( $testdirector_configuration->{ testdirector_config }->{ tdc_bug_owner } );
    $self->tdc_bug_status_tag( $testdirector_configuration->{ testdirector_config }->{ tdc_bug_status } );
    $self->tdc_bug_comment_tag( $testdirector_configuration->{ testdirector_config }->{ tdc_bug_comment } );

    $self->tdc_bug_severity_tag( $testdirector_configuration->{ testdirector_config }->{ tdc_bug_severity } );
    $self->tdc_bug_resolution_tag( $testdirector_configuration->{ testdirector_config }->{ tdc_bug_resolution } );
    $self->tdc_bug_priority_tag( $testdirector_configuration->{ testdirector_config }->{ tdc_bug_priority } );

    return 0;
}



# -----------------------------------------------------------------------------
# These functions are required by the Bugtracker interface
# -----------------------------------------------------------------------------


sub set_version_type {
    my $self = shift;
    my $version = $self->version();

    if ( $version =~ m/(\d+)\.(\d+)\.(\d+)/ ) {
	my $major = $1;
	my $minor = $2;
	my $patch = $3;
	# Set default support to version 9 as that is what we have tested it on
	if ( $major == 9 ) {
	    $self->{ version_type } = $BUGTRACKER_VERSION_LATEST;
	} else {
	    $self->{ version_type } = $BUGTRACKER_VERSION_NOT_SUPPORTED;
	}
    } else {
	$self->{ version_type } = $BUGTRACKER_VERSION_WRONG_FORMAT;
    }
}



# -----------------------------------------------------------------------------
# These functions are required by the Integration interface
# -----------------------------------------------------------------------------



# Connects to the bug-tracker database
sub integration_connect_database {
    my $self = shift;
    $self->bug_owner("");
    $self->bug_status("");
}



# Disconnects from the bug-tracker database
sub integration_disconnect_database {
    my $self = shift;
    $self->bug_owner("");
    $self->bug_status("");
}



# Enters an SCM check-in comment, originating from the bug-tracking
# username of the SCM user, against a bug
#
# PARAMETERS:
# $1 - Bug id
# $2 - Bug-tracker username of SCM user that will enter a comment
# $3 - Comment to be entered
#
# RETURNS:
# - 0 on success
# - 1 on failure
sub integration_add_comment {
    my $self = shift;
    my ( $bugid, $username, $comment ) = ( @_ );

    # Generate a comment to insert
    my $full_comment = "\n\n";
    $full_comment .= $username;
    $full_comment .= " (";
    $full_comment .= localtime;
    $full_comment .= ") : ";
    $full_comment .= $comment;

    my $cmd_line = $self->build_command_line( $bugid, "add_comment", $self->tdc_bug_comment_tag() );
    $cmd_line .= " \"" . $full_comment . "\"";

    # Make the Test Director request
    open( TD_COMMAND, $cmd_line . " |" );

    # Check if there was an error
    my $error_message = <TD_COMMAND>;

    close ( TD_COMMAND );

    if( "$error_message" ne "" ) {
	return 1;
    }
    return 0;
}



# Given a bug id, returns the the value from test director
#
# PARAMETERS:
# $1 - Bug id
sub integration_get_bug_value {
    my $self = shift;
    my ( $bugid, $key_word, $tag_name ) = ( @_ );
    my $line;
    my $bug_value = "";

    if( "$tag_name" eq "" ) {
	  return "";
    }

    my $cmd_line = $self->build_command_line( $bugid, $key_word, $tag_name );

    # Make the Test Director request
    open( TD_COMMAND, $cmd_line . " |" );
    # Cygwin has a problem with newlines
    binmode( TD_COMMAND, ":crlf" );

    # Read the return to get the data
    while ( $line = <TD_COMMAND> ) {
	$bug_value .= $line;
    }

    close ( TD_COMMAND );

    return $bug_value;
}



#
# Set of wrappers to get values from Test Director
#
sub integration_get_product_name {
    my $self = shift;
    my $bugid = shift;
    return $self->integration_get_bug_value( $bugid, "get_bug_value", $self->tdc_product_name_tag() );
}



sub integration_get_bug_comments {
    my $self = shift;
    my $bugid = shift;
    return $self->integration_get_bug_value( $bugid, "get_bug_value", $self->tdc_bug_comment_tag() );
}



sub integration_get_bug_subject {
    my $self = shift;
    my $bugid = shift;
    return $self->integration_get_bug_value( $bugid, "get_bug_value", $self->tdc_bug_summary_tag() );
}



sub integration_get_bug_severity {
    my $self = shift;
    my $bugid = shift;
    return $self->integration_get_bug_value( $bugid, "get_bug_value", $self->tdc_bug_severity_tag() );
}



sub integration_get_bug_resolution {
    my $self = shift;
    my $bugid = shift;
    return $self->integration_get_bug_value( $bugid, "get_bug_value", $self->tdc_bug_resolution_tag() );
}



sub integration_get_bug_priority {
    my $self = shift;
    my $bugid = shift;
    return $self->integration_get_bug_value( $bugid, "get_bug_value", $self->tdc_bug_priority_tag() );
}


# Given a bug id, returns the bug-tracking username of the user to
# which the bug is assigned
#
# PARAMETERS:
# $1 - Bug id
sub integration_get_bug_owner {
    my $self = shift;
    my $bugid = shift;

    my $bug_owner = $self->bug_owner();

    if( "$bug_owner" eq "" ) {
	my $cmd_line = $self->build_command_line( $bugid, "get_bug_owner_status", $self->tdc_bug_owner_tag() );

	# Actually get the status as well as the owner in order to
	# improve performance
	$cmd_line .= " ";
	$cmd_line .= $self->tdc_bug_status_tag();

	# Make the Test Director request
	open( TD_COMMAND, $cmd_line . " |" );
	# Cygwin has a problem with newlines
	binmode( TD_COMMAND, ":crlf" );

    	# Read the return to get the data
	$bug_owner  = <TD_COMMAND>;
	my $bug_status = <TD_COMMAND>;
	$bug_owner =~ s/\n$//g;
	$self->bug_owner($bug_owner);
	$self->bug_status($bug_status);

	close ( TD_COMMAND );
    }

    return $bug_owner;
}



# Given a bug id, returns the bug-tracking email address of the user to
# which that reported the bug
#
# PARAMETERS:
# $1 - Bug id
sub integration_get_bug_reporter_email {
    my $self = shift;
    my $bugid = shift;

    # NOT SUPPORTED
    return undef;
}



# Given a username, returns the user's email address
#
# PARAMETERS:
# $1 - Username
sub integration_get_user_email {
    my $self = shift;
    my $username = shift;

    # NOT SUPPORTED
    return undef;
}



# Given a bug id, returns the email addresses of the users monitoring
# the bug
#
# PARAMETERS:
# $1 - Bug id
sub integration_get_bug_monitors_email_list {
    my $self = shift;
    my $bugid = shift;
    my @bug_monitors_email_list;

    # NOT SUPPORTED
    return undef;
}



# Given a project_name, returns the email addresses of all the
# managers of the project.
#
# For TestDirector, this is not applicable
#
# PARAMETERS:
# $1 - Project name
sub integration_get_managers_email_list {
    my $self = shift;
    my $project_name = shift;

    # NOT SUPPORTED
    return undef;
}



# Given a bug id, returns the status of the bug
#
# PARAMETERS:
# $1 - Bug id
sub integration_get_bug_status {
    my $self = shift;
    my $bugid = shift;

    # Bug Status is retrieved at the same time as the bug owner
    # so see if we already have it
    my $bug_status = $self->bug_status();

    if( "$bug_status" eq "" ) {
	$self->integration_get_bug_owner($bugid);
	$bug_status = $self->bug_status();
    }

    return $bug_status;
}



# Given a bug id, returns 1 if the bug is in a state considered
# active, or 0 otherwise. Active bug state examples would be
# "assigned" and "reopened". A bug can accept checkins in this
# state. However, "new" and "confirmed" are not active since in this
# state the bug has not been assigned to a developer. "resolved" is
# obviously not active.
#
# PARAMETERS:
# $1 - Bug id
sub integration_bug_in_active_state {
    my $self = shift;
    my $bugid = shift;
    my $bug_status = $self->integration_get_bug_status( $bugid );
    
    # Verify that the bug is in the assigned or reopened state
    if ( $td_bug_status_map->{ $bug_status }->{ active } == 1 ) {
	return (1, $bug_status);
    } else {
	return (0, $bug_status);
    }
}



# Returns a string that reports the list of active states in the
# bug-tracker
sub integration_active_states_list {
    my $self = shift;

    return $self->active_states_list( $td_bug_status_map );
}



# Given a product name and a tag name, adds the tag in the list of
# available versions of the product
#
# PARAMETERS:
# $1 - Name of product on which a tag is added
# $2 - Name of tag
# $3 - Description of the tag
sub integration_add_tag {
    my $self = shift;
    my ( $product_name, $tag_name, $description ) = ( @_ );

    # NOT SUPPORTED
    return 0;
}



# Given a product name and a tag name, checks if the tag exists in the
# list of available versions of the product
#
# PARAMETERS:
# $1 - Name of product on which a tag is added
# $2 - Name of tag
sub integration_tag_exists {
    my $self = shift;
    my ( $product_name, $tag_name ) = ( @_ );

    # NOT SUPPORTED
    return 1;
}



# Given a product name and a tag name, deletes the tag from the list
# of available versions of the product
#
# PARAMETERS:
# $1 - Name of product on which a tag is added
# $2 - Name of tag
sub integration_delete_tag {
    my $self = shift;
    my ( $product_name, $tag_name ) = ( @_ );

    # NOT SUPPORTED
    return 1;
}



# Runs a VDD Query
#
# PARAMETERS:
# $1 - Name of product for which a VDD will be generated
# $2 - Previous version name of the product
# $3 - Newer version name of the product
# $4 - Date the previous version name was applied in the SCM system for the product
# $5 - Date the newer version name was applied in the SCM system for the product
#
# RETURNS:
# - A Scmbug::VDD object
sub integration_get_vdd {
    my $self = shift;
    my ( $product_name, $from_version, $to_version, $from_label_date, $to_label_date ) = ( @_ );
    my $vdd = Scmbug::VDD->new();

    return $vdd;
}

# Gets the used and date from the given comment text
#
# PARAMETERS:
# $1 - Comment
sub integration_get_data_from_comment {
    my $self = shift;
    my ( $comment ) = ( @_ );

    # Get the first line of the comment
    my @comment_lines = split( "\n", $comment );

    my @comment_words = split( " ", $comment_lines[0] );

    # First value is the username entering the comment
    my $username = $comment_words[0];

    @comment_words = split( '\(', $comment_lines[0] );
    @comment_lines = split( '\)', $comment_words[1] );
    my $date = $comment_lines[0];

    return $username, $date;
}



# Runs a get bugs query
#
# PARAMETERS:
# $1 - Name of product in which bugs will be queried
# $2 - List of bug ids that should be queried
#
# RETURNS:
# - A Scmbug::Bug_Information object
# - An integer ret_val that if set to 1 indicates that an error
#   occured.
# - A text message that should be reported to the client if an error
#   occurred.
sub integration_get_bugs {
    my $self = shift;
    my ( $product_name, $ids ) = ( @_ );
    my $mergelist = Scmbug::MergeList->new();
    my $ret_val = 0;
    my $ret_message = "";

    foreach my $id ( @{ $ids } ) {
	# Get the complete set of comments
	my $comments = $self->integration_get_bug_comments( $id );
	
	my $summary         = $self->integration_get_bug_subject( $id );
	my $status          = $self->integration_get_bug_status( $id );
	my $resolution      = $self->integration_get_bug_resolution( $id );
	my $severity        = $self->integration_get_bug_severity( $id );
	my $priority        = $self->integration_get_bug_priority( $id );

	# Split the comment up into the correct sections
	my @comment_sets = $self->split_comment_sections( $comments );
	my $number_of_comment_sets = scalar @comment_sets;
	my $comment_set_counter = 0;
	while ( $comment_set_counter < $number_of_comment_sets ) {
	    my ($is_an_integration_comment,
		$affected_files_description,
		$extracted_comment,
		$extracted_ret_val,
		$extracted_ret_message) = $self->extract_affected_files( $comment_sets[ $comment_set_counter ], $id, ($comment_set_counter + 1) );
	    
	    if ($is_an_integration_comment == 1) {
		if ($extracted_ret_val == 0) {
		    
		    my ($username, $date) = $self->integration_get_data_from_comment($comment_sets[ $comment_set_counter ]);

		    $mergelist->add_bug_comment( $id,
						 $summary,
						 $status,
						 $resolution,
						 $severity,
						 $priority,
						 ($comment_set_counter + 1),
						 $extracted_comment,
						 $date,
						 $username,
						 "",
						 "",
						 $affected_files_description );
		    
		} else {
		    # There was an error message detected. Should report it.
		    $ret_val = 1;
		    $ret_message .= $extracted_ret_message;
		}
	    } else {
		# This is just a user comment. Ignore it.
	    }
	    $comment_set_counter++;
	}
    }
    
    return $mergelist, $ret_val, $ret_message;
}



1;
