#!/usr/pkg/bin/perl -w
#
# $Header: /raid/cvsroot/rt/bin/rt,v 1.10 2002/07/19 05:22:06 jesse Exp $
# RT is (c) 1996-2001 Jesse Vincent <jesse@bestpractical.com>

use strict;
use Carp;
use Getopt::Long;

use lib "/usr/pkg/rt/lib";
use lib "/usr/pkg/rt/etc";

use RT::Interface::CLI  qw(CleanEnv LoadConfig DBConnect 
			   GetCurrentUser GetMessageContent);

#Clean out all the nasties from the environment
CleanEnv();

#Load etc/config.pm and drop privs
LoadConfig();

#Connect to the database and get RT::SystemUser and RT::Nobody loaded
DBConnect();

#Drop setgid permissions
RT::DropSetGIDPermissions();

#Get the current user all loaded
my $CurrentUser = GetCurrentUser();

unless ($CurrentUser->Id) {
	print "No RT user found. Please consult your RT administrator.\n";
	exit(1);
}


# {{{ commandline flags 

my ( @id,
     @limit_queue,
     @limit_status,
     @limit_owner,
     @limit_priority,
     @limit_final_priority,
     @limit_requestor,
     @limit_subject,
     @limit_body,
     @limit_created,
     @limit_resolved,
     @limit_lastupdated,
     @limit_dependson,
     @limit_dependedonby,
     @limit_memberof,
     @limit_hasmember,
     @limit_refersto,
     @limit_referredtoby,
     @limit_keyword,
     
     @limit_due,
     @limit_starts,
     @limit_started,
     $limit_first,
     $limit_rows,
     $history,
     $summary,
     $create,
     @requestors,
     @cc,
     @admincc,
     $status,
     $subject,
     $owner,
     $steal,
     $queue,
     $time_left,
     $priority,
     $final_priority,
     $due,
     $starts,
     $started,
     $contacted,
     $comment,
     $reply,
     $source,
     $edit,
     @dependson,
     @memberof, 
     @refersto,
     $mergeinto,
     @keywords,
     $time_taken,
     $verbose,
     $debug,
   $help,
   $version);

# }}}

# Set defaults for cli args

$edit = 1; # Assume the user wants to edit replies and comments 
           # unless they specify --noedit

# {{{    args

my @args =("id=s" => \@id,
	   "limit-queue=s" => \@limit_queue,
	   "limit-status=s" => \@limit_status,
	   "limit-owner=s" => \@limit_owner,
	   "limit-priority=s" => \@limit_priority,
	   "limit-final-priority=s" => \@limit_final_priority,
	   "limit-requestor=s" => \@limit_requestor,
	   "limit-subject=s" => \@limit_subject,
	   "limit-body=s",	\@limit_body,
	   "limit-created=s" => \@limit_created,
	   "limit-due=s" =>	\@limit_due,
 	   "limit-last-updated=s" => \@limit_lastupdated,
	   "limit-keyword=s" => \@limit_keyword,

	   "limit-member-of=s" => \@limit_memberof,
	   "limit-has-member=s" => \@limit_hasmember,
	   "limit-depended-on-by=s" => \@limit_dependedonby,
	   "limit-depends-on=s" => \@limit_dependson,
	   "limit-referred-to-by=s" => \@limit_referredtoby,
	   "limit-refers-to=s" => \@limit_refersto,

	   "limit-starts=s" => \@limit_starts,
	   "limit-started=s" => \@limit_started,
	   "limit-first=i" => \$limit_first,
	   "limit-rows=i" => \$limit_rows,
	   "history|show" => \$history,
	   "summary:s" => \$summary,
	   "create" => \$create,
	   "keywords=s" => \@keywords,
	   "requestor|requestors=s" => \@requestors,
	   "cc=s" => \@cc,
	   "admincc=s" => \@admincc,
	   "status=s" => \$status,
	   "subject=s" => \$subject,
	   "owner=s" => \$owner,
	   "steal" => \$steal,
	   "queue=s" => \$queue,

	   
	   "priority=i" => \$priority,
	   "final-priority=i" => \$final_priority,
	   "due=s" => \$due,
	   "starts=s" => \$starts,
	   "started=s" => \$started,
	   "contacted=s" => \$contacted,
	   "comment", \$comment,
	   "reply|respond", \$reply,
	   "source=s" => \$source,
	   "edit!" => \$edit,
	   "depends-on=s" => \@dependson,
	   "member-of=s" => \@memberof, 
	   "merge-into=s" => \$mergeinto, 
	   "refers-to=s" => \@refersto,
	   "time-left=i" => \$time_left,
	   "time-taken=i" => \$time_taken,
	   "verbose+" => \$verbose,
	   "debug" => \$debug,
	   "version" => \$version,
	   "help|h|usage" => \$help
	  );

# }}}



GetOptions(@args);

print join(':',@keywords);
# {{{ If they want it, print a usage message and get out

if ($help) {


print <<EOUSAGE;

Limit the set of records returned:

--id=[first][-][last]
  Specify a single ticket, a range, or to start with (n-) or end with (-n)
a specific ticket.
  
  --limit-queue=<queue>
	  --limit-status=[!](new|open|stalled|resolved)

	  --limit-owner=[!]<userid>
	  --limit-priority=[starts][-][ends]
	  --limit-final-priority=[starts][-][ends]
	    starts is less than ends
	  --limit-requestor=[!]<userid>|<email>
	  --limit-subject=[!]<text>
	  --limit-body=[!]<text>
	  --limit-keyword=[!]<select>/<keyword>
        
       Links
          --limit-member-of=<ticketid>
          --limit-has-member=<ticketid>
          --limit-refers-to=<ticketid>
          --limit-referred-to-by=<ticketid>
          --limit-depends-on=<ticketid>
          --limit-depended-on-by=<ticketid>


       Dates
	  --limit-created=[starts][-][ends]
	  --limit-due=[starts][-][ends]
	  --limit-starts=[starts][-][ends]
	  --limit-started=[starts][-][ends]
          --limit-resolved=[starts][-][ends]
          --limit-last-updated=[starts][-][ends]
	    starts and ends are dates.  starts can not be less than ends

	  --limit-first=<first row returned>
	  --limit-rows=<row count>

	  --history | --show
            show a history of the tickets found
 

	  --summary [format-string]
             show a listing-style summary of the tickets found. If format string
             is ommitted, uses \$RT_SUMMARY_FORMAT or an internal default
            

             #TODO: doc summary 
             format: <atom>%<format>
             atom:   <name><size>
             size: <integer>
             name:  (grep for # {{{ attribs for the array of ok values)


	  --create
            create a new ticket. Any attributes that you can modify on an existing ticket
            can also be used for ticket creation.



Attributes
  Basics
	  --status=<new|open|stalled|resolved|dead>
           sets status
	   --subject=<subject>
           sets subject
	   --owner=<userid>
           set owner to 
           --steal
           Become the owner, even if someone else owns the ticket
	   --queue=<queueid>
           set queue to
	   
	   --priority=<int>
	  
           --final-priority=<int>

  Watchers
	  --requestors=[+|-]<userid|email address>
          add or remove this user as a ticket requestor 
	  --cc=[+|-]<userid|email address>
          add or remove this user as a ticket cc
	  --admincc=[+|-]<userid|email address>
          add or remove this user as a ticket admincc

	(When creating tickets, just leave off the + or - )

  Keywords
	  --keywords[+|-]<keyword_select>/<keyword>
          Add or remove a keyword.



  Dates
	   --due=<date>
	   --starts=<date>
	   --started=<date>
	   --contacted=<date>

	   --time-left=<int>
	     
	   --time-taken=<int>


   Link related manipulation:

	   --depends-on=[+|-]<ticketid>
	   --member-of=[+|-]<ticketid>
	   --refers-to=[+|-]<ticketid>
           --merge-into=<ticketid>

Comments and replies

	   --comment
	   --reply|respond
	     --source <path>
                Specify the path to the source file for this ticket update

	      --noedit
                Don't invoke \$EDITOR to edit the content of this update




   Condiments

	   --verbose
	   --debug
	   --version
	   --help|h|usage
             You're reading it.

EOUSAGE

    exit(0);
}

# Print version, and leave
if ($version) {
	print "RT $RT::VERSION for $RT::rtname. Copyright 1996-2001 Jesse Vincent <jesse\@fsck.com>\n";
	exit(0);
}

# }}}

# {{{ Validate any options that were passed in. normalize them.

#if a queue was specified
if ($queue) {
    # make sure that $queue is a valid queue and load it into $queue_obj
}

#For each date in: $due, $starts, $started

# load up an RT::Date object and parse it into a normalized form
# if it can't parse it, log an error and null out the variable

# }}}

# {{{ Check if we're creating, if so, create the ticket and be done

if ($create) {
    $RT::Logger->debug("Creating a new ticket");

    #Make sure the current user can create tickets in this queue
    
    #Make sure that the owner specified can own tickets in this queue


	    
    my $linesref = GetMessageContent( Edit => $edit, Source => $source,
				      CurrentUser => $CurrentUser
				    );
    
    require MIME::Entity;
    my $MIMEObj;
    
    if ($linesref) {
	$MIMEObj = MIME::Entity->build(Data => $linesref);
    }	
    
    use RT::Ticket;
    my $Ticket=new RT::Ticket($CurrentUser);
    my ($ticket, $trans, $msg) =
      $Ticket->Create(Queue => $queue,
		      Owner => $owner,
		      Status => $status || 'new' ,
		      Subject => $subject,
		      Requestor => \@requestors,
		      Cc => \@cc,
		      AdminCc => \@admincc,
		      Due => $due,
		      Starts => $starts,
		      Started => $started,
		      TimeLeft => $time_left,
		      InitialPriority => $priority,
		      FinalPriority => $final_priority,
		      MIMEObj => $MIMEObj
		     );
    print $msg . "\n";
}

# }}}

else {
    #Apply restrictions
    use RT::Tickets;
    my $Tickets = new RT::Tickets($CurrentUser);
    
    # {{{ Limit our search
    my $value;			#to use when iterating through restrictions
    my $queue_id;		#to use when limiting by keyword
    
    # {{{ limit on id

    foreach $value (@id) {
	if ($value =~ /^(\d+)$/) {
	    $Tickets->LimitId ( VALUE => $1,
				OPERATOR => '=');
	}	
	elsif ($value =~ /^(\d*)\D?(\d*)$/) {
	    my $start = $1;
	    my $end = $2;
	    $Tickets->LimitId(
			      VALUE => "$start",
			      OPERATOR => '>=') if ($start);
	    $Tickets->LimitId(
			      VALUE => "$end",
			      OPERATOR => '<=') if ($end);
	}	
    }


    # }}}
    
    # {{{ limit on status

    foreach $value (@limit_status) {
	if ($value =~ /^(=|!=|!|)(.*)$/) {
	    my $op = $1;
	    my $val = $2;
	         

	    $op = ParseBooleanOp($op);
	    $Tickets->LimitStatus(VALUE => "$val",
				  OPERATOR => "$op");
	}	
    }

    # }}}



    # {{{ limit on queue
    foreach $value (@limit_queue) {
	if ($value =~ /^(\W?)(.*?)$/i) {
	    my $op = $1;
	    my $val = $2;
		
	    $op = ParseBooleanOp($op);

	    my $queue_obj = new RT::Queue($RT::SystemUser);
		
	    unless ($queue_obj->Load($val)) {
		$RT::Logger->debug("Queue '$val' not found");
		print STDERR "Queue '$val' not found\n";	
		exit(-1);
	    }
	    $RT::Logger->debug ("Limiting queue to $op ".$queue_obj->Name);
	    $Tickets->LimitQueue(VALUE => $queue_obj->Name,
				 OPERATOR => $op);
	    $queue_id=$queue_obj->id;
	}	
    }	

    # {{{ limit on keyword
    foreach $value (@limit_keyword) {
	if ($value =~ /^(\W?)(.*?)\/(.*)$/i) {
	    my $op = $1;
	    my $select = $2;
	    my $keyword = $3;

	    $op = ParseBooleanOp($op);

	    # load the keyword select
	    my $keyselect = RT::KeywordSelect->new($RT::SystemUser);
	    unless ($keyselect->LoadByName(Name=>$select, Queue=>$queue_id)) {
		$RT::Logger->debug("KeywordSelect '$select' not found");
		print STDERR "KeywordSelect '$select' not fount\n";
		exit(-1);
	    }

	    # load the keyword
	    my $k = RT::Keyword->new($RT::SystemUser);
	    unless ($k->LoadByNameAndParentId($keyword, $keyselect->Keyword)) {
		$RT::Logger->debug("Keyword '$keyword' not found");
		print STDERR "Keyword '$keyword' not found\n";
		exit(-1);
	    }
	    $Tickets->LimitKeyword(OPERATOR => $op,
	                           KEYWORDSELECT => $keyselect->id,
				   KEYWORD => $k->id);
	    $RT::Logger->debug ("Limiting keyword to $op ".$k->Path);
	}
    }
    # }}}
    # {{{ limit on owner
    foreach $value (@limit_owner) {
	if ($value =~ /^(\W?)(.*?)$/i) {
	    my $op = $1;
	    my $val = $2;
		
	    $op = ParseBooleanOp($op);

	    my $user_obj = new RT::User($RT::SystemUser);
		
	    unless ($user_obj->Load($val)) {
		$RT::Logger->debug("User '$val' not found");
		print STDERR "User '$val' not found\n";	
		exit(-1);
	    }
	    $val = $user_obj->id();
		
	    $RT::Logger->debug ("Limiting owner to $op $val");
	    $Tickets->LimitOwner(VALUE => "$val",
				 OPERATOR => "$op");
	}	
    }	
    # }}}
    # {{{ limt on priority

    foreach $value (@limit_priority) {
	my ($start, $end) = ParseRange($value);
	if ($start == $end) {
	    $Tickets->LimitPriority( VALUE => $start,
				     OPERATOR => '=');
	} elsif ($start) {
	    $Tickets->LimitPriority( VALUE => $start,
				     OPERATOR => '>=');
	} elsif ($end) {
	    $Tickets->LimitPriority( VALUE => $end,
				     OPERATOR => '<=');
	}	
	    
    }
    foreach $value (@limit_final_priority) {
	my ($start, $end) = ParseRange($value);
	if ($start == $end) {
	    $Tickets->LimitFinalPriority( VALUE => $start,
					  OPERATOR => '=');
	} elsif ($start) {
	    $Tickets->LimitFinalPriority( VALUE => $start,
					  OPERATOR => '>=');
	} elsif ($end) {
	    $Tickets->LimitFinalPriority( VALUE => $end,
					  OPERATOR => '<=');
	}	
    }
    # }}}

    foreach $value (@limit_requestor) {
	if ($value =~ /^(\W?)(.*?)$/i) {
	    my $op = $1;
	    my $val = $2;
		
	    $op = ParseBooleanOp($op);
	    $Tickets->LimitRequestor(VALUE => $val,
				     OPERATOR => $op );
	}
	    
    }
    foreach $value (@limit_subject) {
	
	if ($value =~ /^(\W?)(.*?)$/i) {
	    my $op = $1;
	    my $val = $2;
	    
	    $op = ParseLikeOp($op);
	    
	    $Tickets->LimitSubject(VALUE => $val,
				   OPERATOR => $op );
	    }
    }
    
    foreach $value (@limit_body) {
	if ($value =~ /^(\W?)(.*?)$/i) {
	    my $op = $1;
	    my $val = $2;
	    
	    $op = ParseLikeOp($op);
	    
		$Tickets->LimitBody(VALUE => $val,
				    OPERATOR => $op );
	}	
	
    }
    
    
    
    # Dates
    foreach my $date (@limit_created) {
	my ($start, $end) = ParseDateRange($date);
	$Tickets->LimitCreated ( VALUE => $start,
				 OPERATOR => '>=' ) if ($start);
	$Tickets->LimitCreated ( VALUE => $end,
				 OPERATOR => '<=' ) if ($end);
    }

    foreach my $date (@limit_due) {
	my ($start, $end) = ParseDateRange($date);
	$Tickets->LimitDue ( VALUE => $start,
				 OPERATOR => '>=' ) if ($start);
	$Tickets->LimitDue ( VALUE => $end,
				 OPERATOR => '<=' ) if ($end);
    }

    foreach my $date (@limit_starts) {
	my ($start, $end) = ParseDateRange($date);
	$Tickets->LimitStarts ( VALUE => $start,
				 OPERATOR => '>=' ) if ($start);
	$Tickets->LimitStarts ( VALUE => $end,
				 OPERATOR => '<=' ) if ($end);
    }

    foreach my $date (@limit_started) {
	my ($start, $end) = ParseDateRange($date);
	$Tickets->LimitStarted ( VALUE => $start,
				 OPERATOR => '>=' ) if ($start);
	$Tickets->LimitStarted ( VALUE => $end,
				 OPERATOR => '<=' ) if ($end);
    }

    foreach my $date (@limit_resolved) {
	my ($start, $end) = ParseDateRange($date);
	$Tickets->LimitResolved ( VALUE => $start,
				 OPERATOR => '>=' ) if ($start);
	$Tickets->LimitResolved ( VALUE => $end,
				 OPERATOR => '<=' ) if ($end);
    }

    foreach my $date (@limit_lastupdated) {
	my ($start, $end) = ParseDateRange($date);
	$Tickets->LimitLastUpdated( VALUE => $start,
				 OPERATOR => '>=' ) if ($start);
	$Tickets->LimitLastUpdated ( VALUE => $end,
				 OPERATOR => '<=' ) if ($end);
    }

    foreach my $link (@limit_memberof) {
	$Tickets->LimitMemberOf($link);
    }	

    foreach my $link (@limit_hasmember) {
	$Tickets->LimitHasMember($link);
    }	

    foreach my $link (@limit_dependson) {
	$Tickets->LimitDependsOn($link);
    }	

    foreach my $link (@limit_dependedonby) {
	$Tickets->LimitDependedOnBy($link);
    }
    foreach my $link (@limit_refersto) {
	$Tickets->LimitRefersTo($link);
    }	
    
    foreach my $link (@limit_referredtoby) {
	$Tickets->LimitReferredToBy($link);
    }	

    
    if ($limit_first){
    }
    if ($limit_rows){
    }

# }}}
    
    # {{{ Iterate through all tickets we found


    my ($format, $titles, $code);
    
    #Set up the summary format if we need to
    if (defined $summary) {
	my $format_string = $summary || $ENV{'RT_SUMMARY_FORMAT'} || "%id4%status4%queue7%subject40%requestor16";

	($format, $titles, $code) = BuildListingFormat($format_string);
        printf "$format\n", eval "$titles";
   }	

 

    while (my $Ticket = $Tickets->Next()) {
	$RT::Logger->debug ("Now working on ticket ". $Ticket->id);
    
	#Run through all the ticket modifications we might want to do
	#TODO: these are all insufficiently lazy and should be replaced with some 
	# nice foreaches.


	# {{{ deal with watchers
	
	# add / delete requestors
	foreach $value (@requestors) {
	    if ($value =~ /^(\W?)(.*)$/) {
		my $op = $1;
		my $addr = $2;
		
		$Ticket->AddRequestor(Email => $addr) if ($op eq '+');
		$Ticket->DeleteRequestor( $addr) if ($op eq '-');
	    }	
	}
	
	# add / delete ccs
	foreach $value (@cc) {
	    if ($value =~ /^(\W?)(.*)$/) {
		my $op = $1;
		my $addr = $2;
		$Ticket->AddCc(Email => $addr) if ($op eq '+');
		$Ticket->DeleteCc($addr) if ($op eq '-');
	    }	
	}	
	
	# add / delete adminccs
        $RT::Logger->debug("Looking at admin ccs");
	foreach $value (@admincc) {
	    if ($value =~ /^(\W?)(.*)$/) {
		my $op = $1;
		my $addr = $2;
		$Ticket->AddAdminCc(Email => $addr) if ($op eq '+');
		$Ticket->DeleteAdminCc($addr) if ($op eq '-');
	    }	
	}	

	# }}}
	
	# {{{ Deal with ticket keywords

	my $KeywordSelects = $Ticket->QueueObj->KeywordSelects();
        $RT::Logger->debug ("Looking at keywords");
	foreach $value (@keywords) {
           $RT::Logger->debug("Looking at --keyword=$value");
	    if ($value =~ /^(\W?)(.*?)\/(.*)$/) {
		my $op = $1;
		my $select = $2;
		my $keyword = $3;
		
		$RT::Logger->debug("Going to $op Keyword $select / $keyword");	
		while (my $ks = $KeywordSelects->Next) {
                    $RT::Logger->debug("$select is select ".$ks->Name." is found");
		    next unless ($ks->Name =~ /$select/i);
		    $RT::Logger->debug ("Found a match for $select\n"); 
		    my $kids = $ks->KeywordObj->Descendents;
    
                    my ($kid);
		    foreach $kid (keys %{$kids}) {
                        $RT::Logger->debug("Now comparing $keyword with ".$kids->{$kid}. "\n");
			next unless ($kids->{$kid} =~ /^$keyword$/i);
		        $RT::Logger->debug("Going to $op $select / $keyword (".$kids->{$kid} .")");	
			$Ticket->DeleteKeyword(KeywordSelect => $ks->id,
					    Keyword => $kid) if ($op eq '-');
			
			$Ticket->AddKeyword(KeywordSelect => $ks->id,
					    Keyword => $kid) if ($op eq '+');
		    }
		    
		}
	    }
	}
	# }}}
	
	# {{{ deal with links

	# Deal with merging {
	if ($mergeinto) {
		my ($trans, $msg) =$Ticket->MergeInto($mergeinto);
		print $msg."\n";
	}	
	# add /delete depends-ons

	foreach my $value (@dependson) {
	    if ($value =~ /^(\W?)(.*)$/) {
		my $op = $1;
		my $ticket = $2;
		if (!$op or ($op eq '+')) {
		    my ($trans, $msg) =
		      $Ticket->AddLink(Type => 'DependsOn', Target => $ticket);
		    print $msg."\n";
		}
		elsif ($op eq '-') {
		    my ($trans, $msg) = 
		      $Ticket->DeleteLink(Type => 'DependsOn', Target => $ticket);
		    print $msg."\n";
		}

	    }
	}
	# add /delete member-of
	foreach my $value (@memberof) {
	    if ($value =~ /^(\W?)(.*)$/) {
		my $op = $1;
		my $ticket = $2;
		if ($op eq '+') {
		    my ($trans, $msg) =
		      $Ticket->AddLink(Type => 'MemberOf', Target => $ticket);
		    print $msg;
		}
		elsif ($op eq '-') {
		    my ($trans, $msg) = 
		      $Ticket->DeleteLink(Type => 'MemberOf', Target => $ticket);
		    print $msg;
		}

	    }
	}	
	# add / delete refers-to
		foreach my $value (@refersto) {
	    if ($value =~ /^(\W?)(.*)$/) {
		my $op = $1;
		my $ticket = $2;
		if ($op eq '+') {
		    my ($trans, $msg) =
		      $Ticket->AddLink(Type => 'RefersTo', Target => $ticket);
		    print $msg;
		}
		elsif ($op eq '-') {
		    my ($trans, $msg) = 
		      $Ticket->DeleteLink(Type => 'RefersTo', Target => $ticket);
		    print $msg;
		}

	    }
	}

	# }}}
	
	# {{{ deal with dates
	
	#set due 
	if ($due) {
	    my $iso = ParseDateToISO($due);
	    if ($iso) {
		$RT::Logger->debug("Setting due date to $iso ($due)");
		my ($trans, $msg) = 
		  $Ticket->SetDue($iso);
		print $msg;
	    }
	    else {
		print "Due date '$due' could not be parsed";
	    }
	}

	#set starts
	if ($starts) {
	    my $iso = ParseDateToISO($due);
	    if ($iso) {
		my ($trans, $msg) = 
		  $Ticket->SetStarts($iso);
		print $msg."\n";
	    }
	    else {
		print "Starts date '$starts' could not be parsed";
	    }
	}
	#set started
		if ($started) {
	    my $iso = ParseDateToISO($started);
	    if ($iso) {
		my ($trans, $msg) = 
		  $Ticket->SetStarted($iso);
		print $msg."\n";
	    }
	    else {
		print "Started date '$started' could not be parsed";
	    }
	}
	#set contacted
		if ($contacted) {
	    my $iso = ParseDateToISO($contacted);
	    if ($iso) {
		my ($trans, $msg) = 
		  $Ticket->SetContacted($iso);
		print $msg."\n";
	    }
	    else {
		print "Contacted date '$contacted' could not be parsed";
	    }
	}

    # }}}
	
	# {{{ set other attributes

	#Set subject
	if ($subject) {
	    my ($trans, $msg) = $Ticket->SetSubject($subject);
	    print $msg."\n";
	}
	
	#Set priority
	if ($priority) {
	    my ($trans, $msg) = 
	      $Ticket->SetPriority($priority);
	    print $msg."\n";
	}
	
	#Set final priority
	if ($final_priority) {
	    my ($trans, $msg) =
	      $Ticket->SetFinalPriority($final_priority);
	    print $msg."\n";
	}

	#Set status
	if ($status) {
	    my ($trans, $msg) = 
	      $Ticket->SetStatus($status);
	    print $msg."\n";
	}
	
	#Set time left
	if ($time_left) {
	    my ($trans, $msg) = 
	      $Ticket->SetTimeLeft($time_left);
	    print $msg."\n";
	}

	#Set time_taken 
	if ($time_taken) {
	    my ($trans, $msg) = 
	      $Ticket->SetTimeTaken($time_taken);
	    print $msg."\n";
	}
	
	#Set owner
	if ($owner) {
	    my ($trans, $msg) =
	      $Ticket->SetOwner($owner);
	    print $msg."\n";
	}

        # Steal
        if ($steal) {
                my ($trans, $msg) =
                 $Ticket->Steal();
                 print $msg . "\n";
        }
	#Set queue 
	if ($queue) {
	    my ($trans, $msg) = 
	      $Ticket->SetQueue($queue);
	    print $msg."\n";
	}

    # }}}
	


	# {{{ Perform ticket comments/replies
	if ($reply) {
	    $RT::Logger->debug("Replying to ticket ".$Ticket->Id);
	    
	    my $linesref = GetMessageContent( Edit => $edit, Source => $source,
					     CurrentUser => $CurrentUser
					   );
	    
	    #TODO build this entity
	    require MIME::Entity;
	    my $MIMEObj = MIME::Entity->build(Data => $linesref);
	    
	    $Ticket->Correspond( MIMEObj => $MIMEObj ,
				 TimeTaken => $time_taken);
	}	
	
	elsif ($comment) {
	    $RT::Logger->debug("Commenting on ticket ".$Ticket->Id);
	
	    my $linesref =GetMessageContent(Edit => $edit, Source => $source,
					    CurrentUser => $CurrentUser);
	    #TODO build this entity
	    require MIME::Entity;
	    my $MIMEObj = MIME::Entity->build(Data => $linesref);
	    
	    $Ticket->Comment( MIMEObj => $MIMEObj,
			      TimeTaken => $time_taken);
	}

    # }}}
	
	# {{{ Display whatever we need to display

	# {{{ Display a full ticket listing and history
	if ($history) {
	    #Display the history
	    $RT::Logger->debug("Show history for ".$Ticket->id);
	    
	    if ($Ticket->CurrentUserHasRight("ShowTicket")) {
		&ShowSummary($Ticket);
		print "\n";
		&ShowHistory($Ticket);
	    }
	    else {
		print "You don't have permission to view that ticket.\n";
	    }
	}	

	# }}}
	
	# {{{ Display a summary if we need to
	if (defined $summary) {
	    $RT::Logger->debug ("Show ticket summary with format $format");
	    
	    printf $format."\n", eval $code;
	    
	}	
	# }}}

	# }}}
	
    }

    # }}}
    
}


$RT::Handle->Disconnect();







# {{{ sub ParseBooleanOp

=head2 ParseBooleanOp

  Takes an option modifier. returns the apropriate SQL operator.
  If it's handed ! or -, returns !=.  Otherwise returns =.

=cut

sub ParseBooleanOp {
    
    my $op = shift;
    
    #so that !new limits to not new, etc
    if ($op =~ /^(\!|-)/) {
	$op = "!=";
    }
    else {
	$op = "=";
    }
    
    return($op);
}

# }}}

# {{{ sub ParseLikeOp
=head2 ParseLikeOp

  Takes an option modifier. returns the apropriate SQL operator.
  If it's handed ! or -, returns NOT  LIKE.  Otherwise returns LIKE

=cut

sub ParseLikeOp {
    
    my $op = shift;
    
    #so that !new limits to not new, etc
    if ($op =~ /^(\!|-)/) {
	$op = "NOT LIKE";
    }
    else {
	$op = "LIKE";
    }
    
    return($op);
}
# }}}

# {{{ sub ParseDateToISO

=head2 ParseDateToISO

Takes a date in an arbitrary format.
Returns an ISO date and time in GMT

=cut

sub ParseDateToISO {
    my $date = shift;

	my $date_obj = new RT::Date($CurrentUser);
	$date_obj->Set( Format => 'unknown',
			Value => $date
		      );
	return ($date_obj->ISO);
}

# }}}

# {{{ sub ParseDateRange

=head2 ParseDateRange [RANGE]

Takes a range of dates of the form [<date>][-][<date>] and returns 
starting and ending dates (as ISOs) If a date is specified as neither a starting nor ending 
date, we parse it it as "midnight tonight to midnight tomorrow"

=cut

sub ParseDateRange {
    my $in = shift;
    my ($start, $end);
    
    
    use RT::Date;
    my $start_obj = new RT::Date($CurrentUser);
    my $end_obj = new RT::Date($CurrentUser);
    
    if ($in =~ /^(.*?)-(.*?)$/) {
	$start = $1;
	$end = $2;

	if ($start) {
	    $start_obj->Set(Format => 'unknown', 
			    Value => $start);
	}
	if ($end) {
	    $end_obj->Set(Format => 'unknown', 
			  Value => $end);
	}
    }
    else {
	$start = $in;
	$end = $in;

	$start_obj->Set(Format => 'unknown', 
			Value => $start);
	
	$end_obj->Set(Format => 'unknown', 
		      Value => $end);
	
	$start_obj->SetToMidnight();
	$end_obj->SetToMidnight();
	$end_obj->AddDay();
    }	
    
    if ($start) {
	$start = $start_obj->ISO;
    }
    if ($end) {
	$end = $end_obj->ISO;
    }

    return ($start, $end);
}

# }}}

# {{{ ParseRange
=head2 ParseRange [RANGE]

Takes a range of the form [<int>][-][<int>] and returns 
a first and a last value. If the - is omitted, both $start and $end are the same.
=cut

sub ParseRange {
    my $in = shift;
    my ($start, $end);
    
    if ($in =~ /(.*?)-(.*?)/) {
	$start = $1;
	$end = $2;
    }
    else {
	$start = $in;
	$end = $in;
    }	
    
    return ($start, $end);
    

    
}

# }}}
	  
# {{{ sub ShowSummary 

sub ShowSummary  {
    my $Ticket = shift;


    print <<EOFORM;
Serial Number: @{[$Ticket->Id]}   Status:@{[$Ticket->Status]} Worked: @{[$Ticket->TimeWorked]} minutes  Queue:@{[$Ticket->QueueObj->Name]}
      Subject: @{[$Ticket->Subject]}
   Requestors: @{[$Ticket->RequestorsAsString]}
           Cc: @{[$Ticket->CcAsString]}
     Admin Cc: @{[$Ticket->AdminCcAsString]}
        Owner: @{[$Ticket->OwnerObj->Name]}
     Priority: @{[$Ticket->Priority]} / @{[$Ticket->FinalPriority]}
          Due: @{[$Ticket->DueAsString]}
      Created: @{[$Ticket->CreatedAsString]} (@{[$Ticket->AgeAsString]})
 Last Contact: @{[$Ticket->ToldAsString]} (@{[$Ticket->LongSinceToldAsString]})
  Last Update: @{[$Ticket->LastUpdatedAsString]} by @{[$Ticket->LastUpdatedByObj->Name]}
	         
EOFORM

my $selects = $Ticket->QueueObj->KeywordSelects();
    #get the keyword selects
    print "Keywords:\n";
    while (my $select = $selects->Next) {
	print "\t" .$select->Name .": ";
	my $keys = $Ticket->KeywordsObj($select->id);	
	while (my $key = $keys->Next) {
	    print $key->KeywordObj->RelativePath($select->KeywordObj) . "  ";
	    
	}	
	print "\n";
    }
    
#iterate through the keyword selects.
#print the keyword select and all the related keywords



#TODO: finish link  descriptions
print "Dependencies: \n";
   while (my $l=$Ticket->DependedOnBy->Next) {
       print $l->BaseObj->id," (",$l->BaseObj->Subject,") ",$l->Type," this ticket\n";
   }
   while (my $l=$Ticket->DependsOn->Next) {
       print "This ticket ",$l->Type," ",$l->TargetObj->Id," (",$l->TargetObj->Subject,")\n";
   }
}

# }}}

# {{{ sub ShowHistory 
sub ShowHistory  {
    my $Ticket = shift;
    my $Transaction;    
    my $Transactions = $Ticket->Transactions;

    while ($Transaction = $Transactions->Next) {
      &ShowTransaction($Transaction);
    }   
  }
# }}}

# {{{ sub ShowTransaction 
sub ShowTransaction  {
  my $transaction = shift;
  
print <<EOFORM;
==========================================================================
Date: @{[$transaction->CreatedAsString]} (@{[$transaction->TimeTaken]} minutes)
@{[$transaction->Description]}
EOFORM
    ;
  my $attachments=$transaction->Attachments();
  while (my $message=$attachments->Next) {
    print <<EOFORM;
--------------------------------------------------------------------------
@{[$message->Headers]}
EOFORM

    if ($message->ContentType =~ m{^(text/plain|message|text$)}) {
	print $message->Content;
    } else {
	print $message->ContentType, " not shown";
    }
  }
  print "\n";
  return();
}
# }}}


# {{{ sub BuildListingFormat

sub BuildListingFormat {
    my $format_string = shift;

    my ($id, @format, @code, @titles);
    my ($field,$titles,$length, $format);

    my $code = "";

    # {{{ attribs
    my $attribs = { id => { chars => '4',
			    justify => 'r',
			    title => 'id',
			    value => '$Ticket->id',
			  },
		    
		    queue => { chars => '8',
			       justify => 'l',
			       title => 'Queue',
			       value => '$Ticket->QueueObj->Name' 
			     },
		    subject => { chars => '30',
				 justify => 'l',
				 title => 'Subject',
				 value => '$Ticket->Subject',
			       },
		    priority => { chars => '2',
				  justify => 'r',
				  title => 'Pri',
				  value => '$Ticket->Priority',
				},
		    final_priority => {  chars => '2',
					 justify => 'r',
					 title => 'Fin',
					 value => '$Ticket->FinalPriority',
				      },
		    time_worked => { chars => '6',
				     justify => 'r',
				     title => 'Worked',
				     value => '$Ticket->TimeWorked',
				   },
		    time_left => { chars => '5',
				   justify => 'r',
				   title => 'Left',
				   value => '$Ticket->TimeLeft',
			       
				 },
		
		    status => {  chars => '6',
				 justify => 'r',
				 title => 'Status',
				 value => '$Ticket->Status',
			      },
		    owner => {  chars => '10',
				justify => 'r',
				title => 'Owner',
				value => '$Ticket->OwnerObj->Name'
			     },
		    requestor => {  chars => '10',
				    justify => 'r',
				    title => 'Requestor',
				    value => '$Ticket->RequestorsAsString'
				 },
		    created => {  chars => '12',
				  justify => 'r',
				  title => 'Created',
				  value => '$Ticket->CreatedAsString'
			       },
		    updated => {  chars => '12',
				  justify => 'r',
				  title => 'Updated',
				  value => '$Ticket->LastUpdatedAsString'
			       },
		    due => {  chars => '12',
			      justify => 'r',
			      title => 'Due',
			      value => '$Ticket->DueAsString'
			   },
		    told => {  chars => '12',
			       justify => 'r',
			       title => 'Told',
			       value => '$Ticket->ToldAsString'
			    },
		
		
		
		  };

    # }}}
    

    foreach $field (split ('%',$format_string)) {
	
	if ($field =~ /^(\D*?)(\d*?)$/) {
	    $id = $1;
	    $length = $2;
	}
	else {	
	    $RT::Logger->debug ("Error parsing $field\n");
	}
	if ($length) {
	    push (@format, "%".$length.".".$length."s ");
	    
	    push (@code,  $attribs->{"$id"}->{'value'});
		  
	    push (@titles, "'". $attribs->{"$id"}->{title}. "'");
	}
	
	
    }
     $code = join (',', @code);
     $format = join (" ", @format);
     $titles = join (', ', @titles);
    
  
    return ($format, $titles, $code);
}

# }}}



1;
