#! /usr/bin/env perl
# $Id: jnitodo.pl 4138 2011-08-24 06:39:11Z jnieuwen $
# $URL: file:///usr/home/jnieuwen/subversion/files/trunk/j/jnitodo/jnitodo.pl $
# $Author: jnieuwen $
# $Date: 2011-08-24 08:39:11 +0200 (Wed, 24 Aug 2011) $
# $Rev: 4138 $

# Copyright (c) 2008 - 2011, Jeroen C. van Nieuwenhuizen
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of jeroen.se nor the names of its contributors may be
#       used to endorse or promote products derived from this software without
#       specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY Jeroen C. van Nieuwenhuizen ''AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL Jeroen C. van Nieuwenhuizen BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use strict ;
use warnings ;

#-------------------------------------------------------------------------------
# Uses
#-------------------------------------------------------------------------------

use Term::ReadLine ;
use Term::ANSIColor ;
use POSIX qw(strftime) ;
use Fcntl ':flock' ;

#-------------------------------------------------------------------------------
# Global variables.
#-------------------------------------------------------------------------------

my %g_todo ;
my %g_done ;
my $g_done_count = 0 ;
my $g_todofile ;
my $g_personfile ;
my $g_config_file ;
my $g_queue_dir ;
my $g_number = 1 ;
my $g_svn_version = '$Id: jnitodo.pl 4138 2011-08-24 06:39:11Z jnieuwen $' ;
my $g_context = "" ;
my $g_term = new Term::ReadLine "jnitodo $ARGV[0]";
my $g_queue_auto = 0 ;
my $g_log_file = '';
my $g_busy_warn = 1000000 ; #You have other problems if your todolist is this large.
my $g_warn_total= 1000000 ;
my $g_waitdefault = 0;
my $g_last_added_item = -1 ;

#-------------------------------------------------------------------------------
# Functions needed

#-------------------------------------------------------------------------------
sub is_valid_context {
	my ($context) = @_ ;
	return 1 unless ($context) ;
	foreach my $key (keys %g_todo) {
		return 1 if ($g_todo{$key}{context} eq $context) ;
	}
	return 0 ;
}

#-------------------------------------------------------------------------------
# Completion function
sub completion {
	my ($word,$buffer,$start) = @_ ;
	my @list = ();

	if ($buffer =~ /^(a|c|l[bsipt]?)[ ]+(.*)/) {
		my $current = $2 ;
		$current = '' unless (defined($current)) ;
		foreach my $key (keys %g_todo) {
			push (@list,$g_todo{$key}{context}) if ($g_todo{$key}{context} =~ /^\Q$current/);
		}
	} elsif ($buffer =~/^mv ([0-9]+ )+(.*)/) {
		my $current = $2 ;
		$current = '' unless (defined($current)) ;
		foreach my $key (keys %g_todo) {
			push (@list,$g_todo{$key}{context}) if ($g_todo{$key}{context} =~ /^\Q$current/);
		}
	} elsif ($buffer =~ /^f[ ]+(.*)/ and $g_personfile and -f $g_personfile) {
		my $current = $1 ;
		$current = '' unless (defined($current)) ;
		open(PFILE,"$g_personfile") or return @list ;
		while (my $person = <PFILE>) {
			chomp $person ;
			push (@list,$person.":") if ($person =~ /^\Q$current/);
		}
		close PFILE ;
	}

	return @list ;
}

#-------------------------------------------------------------------------------
sub version {
	my $year = strftime("%Y",localtime(time())); 
	print 'Copyright (c) 2008-'.$year.', Jeroen C. van Nieuwenhuizen'."\n" ;
	print '$Id: jnitodo.pl 4138 2011-08-24 06:39:11Z jnieuwen $'."\n" ;
}


#-------------------------------------------------------------------------------
sub add_wait_item {
	my ($context,$wait,$todo) = @_ ;
	my $addit = 1 ;
	my $is_new_person = 1 ;

	foreach my $key (keys %g_todo) {
		$addit = 0	if ($g_todo{$key}{context} eq $context and 
						$g_todo{$key}{todo} eq $todo and 
						$g_todo{$key}{who} eq $wait
					) ;
	}
	if ($addit) {
		$g_todo{$g_number}{context} = $context ;
		$g_todo{$g_number}{who} = $wait ;
		$g_todo{$g_number}{todo} = $todo ;
		$g_todo{$g_number}{date} = strftime("%Y-%m-%d",localtime(time()+$g_waitdefault*24*3600)); 
		status('Added '.todo_line($g_number,2,$g_todo{$g_number}));
		$g_last_added_item = $g_number ;
		$g_number++ ;

		if ($g_personfile and -f $g_personfile) {
			open(PFILE,"$g_personfile") or return ;
			while (my $person = <PFILE>) {
				chomp $person ;
				$is_new_person = 0 if ($person eq $wait);
			}
			close PFILE ;

			if ($is_new_person) {
				open(PFILE,">>$g_personfile") or return ;
				print PFILE $wait."\n" ;
				close PFILE ;
			}
		}

	} else {
		notice("Already in list: $context: $wait: $todo") ;
	}
}

#-------------------------------------------------------------------------------
sub add_item {
	my ($context,$todo,$datum) = @_ ;
	my $addit = 1 ;

	foreach my $key (keys %g_todo) {
		$addit = 0 if ($g_todo{$key}{todo} eq $todo) ;
	}
	if ($addit) {
		$g_todo{$g_number}{context} = $context ;
		$g_todo{$g_number}{todo} = $todo ;
		$g_todo{$g_number}{date} = strftime("%Y-%m-%d",localtime()); 
		if (defined $datum) {
			$g_todo{$g_number}{date} = $datum ;
			$g_todo{$g_number}{duedate} = 1 ;
		}
		status('Added '.todo_line($g_number,2,$g_todo{$g_number}));
		$g_last_added_item = $g_number ;
		$g_number++ ;
	} else {
		notice("Already in list $todo") ;
	}
}

#-------------------------------------------------------------------------------
sub read_queue_file { 
	my ($file) = @_ ;
	print "Found $file in the queue. Processing\n" ;
	if (open (QUEUEFILE,"$file")) {
		flock(QUEUEFILE,LOCK_EX) ;
		while (my $line=<QUEUEFILE>) {
			chomp($line) ;
			my ($context,$description) = split(/:/,$line,2) ;
			if ($context eq 'wait') {
				add_wait_item($context,split(/:/,$description,2)) ;
			} else {
				if ($context =~ /\[(.*)\](.*)/) {
					add_item($2,$description,$1) ;
				} else {
					add_item($context,$description) ;
				}
			}
		}
		flock(QUEUEFILE,LOCK_UN) ;
		close QUEUEFILE ;
		error("Remove of $file failed") unless(unlink ($file)) ;
		print "$file done\n" ;
	}
}

#-------------------------------------------------------------------------------
sub read_queue_dir {
	my ($queuedir) = @_ ;
	opendir (QUEUEDIR, $queuedir) or (error($!) && return) ;
	foreach my $file (readdir QUEUEDIR) {
		read_queue_file($queuedir.'/'.$file) if (-f $queuedir.'/'.$file and -r $queuedir.'/'.$file) ;
	}
	closedir QUEUEDIR ;
}

#-------------------------------------------------------------------------------
sub count_queue_files {
	my ($queuedir) = @_ ;
	my $count = 0 ;
	opendir (QUEUEDIR, $queuedir) or (error($!) && return) ;
	foreach my $file (readdir QUEUEDIR) {
		$count++ if (-f $queuedir.'/'.$file and -r $queuedir.'/'.$file) ;
	}
	closedir QUEUEDIR ;
	return $count ;
}

#-------------------------------------------------------------------------------
sub is_valid_item {
	my ($key,$todo) = @_ ;
	return exists($$todo{$key}) ;
}

#-------------------------------------------------------------------------------
sub todo_line {
	my ($key,$num,$todo_item) = @_ ;
	my $line = $$todo_item{context}.":" ;
	unless ($num == 2) {
		$line .= "B" if ($$todo_item{busy}) ;
		$line .= "P" if ($$todo_item{planned}) ;
		$line .= "I" if ($$todo_item{important}) ;
		$line .= "T" if ($$todo_item{today}) ;
		$line .= "D" if ($$todo_item{duedate}) ;
		$line .= "-:" ;
	}
	$line .= $$todo_item{date}.":" ;
	$line .= " ".$$todo_item{who}.":" if ($$todo_item{context} eq 'wait') ;
	$line .= " ".$$todo_item{todo} ;
	$line .= " - (in progress)" if ($num and $$todo_item{busy}) ;
	return $key.") ".$line if ($num);
	return "- ".$line ;
}

#-------------------------------------------------------------------------------
sub logline {
	my ($line) = @_ ;
	chomp $line ;
	my $datetime = strftime("%Y-%m-%d %H:%M",localtime());
	if ($g_log_file) {
		open(LOGFILE, ">>".$g_log_file) or die $! ;
		print LOGFILE $datetime." ".$line."\n" ;
		close LOGFILE ;
	}
}

#-------------------------------------------------------------------------------
sub todo_done {
	my ($param) = @_ ;
	$param = $g_term->readline("Done number: ") unless ($param) ;
	$param =~ s/^[ ]+//g ;
	$param =~ s/[ ]+$//g ;
	$param =~ s/[ ]+/ /g ;
	foreach my $key (split(/ /,$param)) {
		if (is_valid_item($key,\%g_todo)) {
			my $line = todo_line($key,2,$g_todo{$key}) ;
			print "Marked $line as done\n" ;
			logline($line) if ($g_log_file) ;
			$g_done{$key} = $g_todo{$key} ;
			delete $g_todo{$key} ;
			$g_done_count++ ;
		} else {
			error("$key is not a valid item number") ;
		}
	}
}

#-------------------------------------------------------------------------------
sub todo_remove {
	my ($param) = @_ ;
	$param = $g_term->readline("Remove number: ") unless ($param) ;
	$param =~ s/^[ ]+//g ;
	$param =~ s/[ ]+$//g ;
	$param =~ s/[ ]+/ /g ;
	foreach my $key (split(/ /,$param)) {
		if (is_valid_item($key,\%g_todo)) {
			my $line = todo_line($key,2,$g_todo{$key}) ;
			print "Removed $line\n" ;
			delete $g_todo{$key} ;
		} else {
			error("$key is not a valid item number") ;
		}
	}
}

#-------------------------------------------------------------------------------
sub todo_undone {
	my ($param) = @_ ;
	$param = $g_term->readline("Undone number: ") unless ($param) ;
	$param =~ s/^[ ]+//g ;
	$param =~ s/[ ]+$//g ;
	$param =~ s/[ ]+/ /g ;
	foreach my $key (split(/ /,$param)) {
		if (is_valid_item($key,\%g_done)) {
			my $line = todo_line($key,2,$g_done{$key}) ;
			print "Marked {$line} as undone\n" ;
			$g_todo{$key} = $g_done{$key} ;
			delete $g_done{$key} ;
			$g_done_count-- ;
		} else {
			error("$key is not a valid history number") ;
		}
	}
}

#-------------------------------------------------------------------------------
sub make_todo_array {
	my ($context,$num,$todo) = @_ ;
	my @todos ;
	unless ($context) {
		foreach my $key (keys %$todo) {
			push @todos, todo_line($key,$num,$$todo{$key}) ;
		}
	} else {
		foreach my $key (keys %$todo) {
			if ($context eq $$todo{$key}{context}) {
				push @todos, todo_line($key,$num,$$todo{$key}) ;
			}
		}
	}
	if ($num) {
		return sort { substr($a,index($a,')')) cmp substr($b,index($b,')')) } @todos ;
	} else {
		return sort @todos ;
	}
}

#-------------------------------------------------------------------------------
sub search {
	my ($search) = @_ ;
	my @todos ;
	foreach my $key (keys %g_todo) {
		if ($g_todo{$key}{context} eq 'wait') {
			if ($g_todo{$key}{todo}  =~ m/\Q$search/i || $g_todo{$key}{who} =~ m/\Q$search/i) {
				push @todos, todo_line($key,1,$g_todo{$key}) ;
			}
		} else {
			if ($g_todo{$key}{todo}  =~ m/\Q$search/i ) {
				push @todos, todo_line($key,1,$g_todo{$key}) ;
			}
		}
	}
	print_list(sort { substr($a,index($a,')')) cmp substr($b,index($b,')')) } @todos );
}

#-------------------------------------------------------------------------------
# Usage: flagname, parameters
sub show_flag {
	my @todos ;
	my ($flag,$parameters) = @_ ;
	my $context = $parameters ? $parameters : "" ;
	$context = "" if (defined($parameters) and $parameters eq '*');
	$context =~ s/[ ]+$// ;
	if (is_valid_context($context)) {
		foreach my $key (keys %g_todo) {
			if ($g_todo{$key}{$flag}) {
				if ($context eq "") {
					push @todos, todo_line($key,1,$g_todo{$key}) ;
				} else {
					if ($context eq $g_todo{$key}{context}) {
						push @todos, todo_line($key,1,$g_todo{$key}) ;
					}
				}
			}
		}
		print_list(sort { substr($a,index($a,')')) cmp substr($b,index($b,')')) } @todos );
	}
}

#-------------------------------------------------------------------------------
# Usage: flagname, parameters
sub show_overdue {
	my @todos ;
	my ($flag,$parameters) = @_ ;
	my $context = $parameters ? $parameters : "" ;
	my $currentdate = strftime("%Y-%m-%d",localtime()); 
	$context = "" if (defined($parameters) and $parameters eq '*');
	$context =~ s/[ ]+$// ;
	if (is_valid_context($context)) {
		foreach my $key (keys %g_todo) {
			if ($g_todo{$key}{$flag} and $g_todo{$key}{date} lt $currentdate) {
				if ($context eq "") {
					push @todos, todo_line($key,1,$g_todo{$key}) unless ($g_todo{$key}{context} eq 'wait');
				} else {
					if ($context eq $g_todo{$key}{context}) {
						push @todos, todo_line($key,1,$g_todo{$key}) ;
					}
				}
			}
		}
		print_list(sort { substr($a,index($a,')')) cmp substr($b,index($b,')')) } @todos );
	}
}

#-------------------------------------------------------------------------------
# Usage: flagname, parameters
sub show_hasdue {
	my @todos ;
	my ($flag,$parameters) = @_ ;
	my $context = $parameters ? $parameters : "" ;
	my $currentdate = strftime("%Y-%m-%d",localtime()); 
	$context = "" if (defined($parameters) and $parameters eq '*');
	$context =~ s/[ ]+$// ;
	if (is_valid_context($context)) {
		foreach my $key (keys %g_todo) {
			if ($g_todo{$key}{$flag} and $g_todo{$key}{date} gt $currentdate) {
				if ($context eq "") {
					push @todos, todo_line($key,1,$g_todo{$key}) unless ($g_todo{$key}{context} eq 'wait');
				} else {
					if ($context eq $g_todo{$key}{context}) {
						push @todos, todo_line($key,1,$g_todo{$key}) ;
					}
				}
			}
		}
		print_list(sort { substr($a,index($a,')')) cmp substr($b,index($b,')')) } @todos );
	}
}

#-------------------------------------------------------------------------------
# Usage: flagname, parameters
sub show_due {
	my @todos ;
	my ($flag,$parameters) = @_ ;
	my $context = $parameters ? $parameters : "" ;
	my $currentdate = strftime("%Y-%m-%d",localtime()); 
	$context = "" if (defined($parameters) and $parameters eq '*');
	$context =~ s/[ ]+$// ;
	if (is_valid_context($context)) {
		foreach my $key (keys %g_todo) {
			if ($g_todo{$key}{$flag} and $g_todo{$key}{date} eq $currentdate) {
				if ($context eq "") {
					push @todos, todo_line($key,1,$g_todo{$key}) unless ($g_todo{$key}{context} eq 'wait');
				} else {
					if ($context eq $g_todo{$key}{context}) {
						push @todos, todo_line($key,1,$g_todo{$key}) ;
					}
				}
			}
		}
		print_list(sort { substr($a,index($a,')')) cmp substr($b,index($b,')')) } @todos );
	}
}

#-------------------------------------------------------------------------------
sub write_file {
	my ($status) = @_ ;
	my @todos = make_todo_array("",0,\%g_todo) ;
	if (open(TODOFILE,">$g_todofile")) {
		print TODOFILE $g_svn_version ;
		foreach my $line (@todos) {
			print TODOFILE $line."\n" ;
		}
		close TODOFILE ;
		status("Wrote todo list to $g_todofile") if ($status);
	} else {
		error("Error writing file: $!") ;
	}
}

#-------------------------------------------------------------------------------
sub write_and_quit {
	write_file(1) ;
	exit(0);
}

#-------------------------------------------------------------------------------
sub status {
	my ($error) = @_ ;
	print $error."\n" ;
}

#-------------------------------------------------------------------------------
sub notice {
	my ($error) = @_ ;
	print color 'bold green' ;
	print "Notice: ".$error."\n" ;
	print color 'reset' ;
}

#-------------------------------------------------------------------------------
sub error {
	my ($error) = @_ ;
	print_divider() ;
	print color 'bold red' ;
	print "ERROR: ".$error."\n" ;
	print color 'reset' ;
}

#-------------------------------------------------------------------------------
sub print_line {
	my ($line,$print_color) = @_ ;
	my $currentdate = strftime("%Y-%m-%d",localtime()); 
	if ($line =~ /^[0-9]+\) wait:/) {
		my ($context,$flags,$date,$who,$todo) = split(/:/,$line,5) ;
		my ($number,$real_context) = split(/ /,$context,2) ;
		print color "bold $print_color" ;
		print $number." " ;
		print color "bold yellow" if ($flags =~ /T/) ;
		print color "bold blue" if ($flags =~ /B/) ;
		print color "bold magenta" if ($flags =~ /P/) ;
		print color "bold cyan" if ($flags =~ /I/) ;
		print $real_context.":" ;
		if ($flags =~ /D/) {
			if ($date gt $currentdate) {
				print color 'bold magenta' ;
			} elsif ($date eq $currentdate) {
				print color 'bold cyan' ;
			} else {
				print color 'bold red' ;
			}
		} else {
			print color 'bold blue' ;
		}
		print $date.":" ;
		print color 'bold yellow' ;
		print $who.":" ;
		print color 'reset' ;
		print color $print_color ;
		print $todo."\n" ;
		print color 'reset' ;
	} else {
		my ($context,$flags,$date,$todo) = split(/:/,$line,4) ;
		my ($number,$real_context) = split(/ /,$context,2) ;
		print color "bold $print_color" ;
		print $number." " ;
		print color "bold yellow" if ($flags =~ /T/) ;
		print color "bold blue" if ($flags =~ /B/) ;
		print color "bold magenta" if ($flags =~ /P/) ;
		print color "bold cyan" if ($flags =~ /I/) ;
		print $real_context.":" ;
		if ($flags =~ /D/) {
			if ($date gt $currentdate) {
				print color 'bold magenta' ;
			} elsif ($date eq $currentdate) {
				print color 'bold cyan' ;
			} else {
				print color 'bold red' ;
			}
		} else {
			print color 'bold blue' ;
		}
		print $date.":" ;
		print color 'reset' ;
		print color $print_color ;
		print $todo."\n" ;
		print color 'reset' ;
	}
}

#-------------------------------------------------------------------------------
sub print_list {
	my (@todos) = @_ ;
	my ($cols,$lines) = $g_term->TermSize() ;
	my $count=0;
	my $col_count=0 ;
	my $still_todo = $#todos ;
	my @colors = ('white','green') ;
	foreach my $line (@todos) {
		if ($count < $lines-4 || ($count < $lines-1 && $still_todo > 0)) {
			my $print_color=$colors[$col_count] ;
			print_line($line,$print_color) ;
			$still_todo-- ;
		} else {
			my $key = $g_term->readline("--- Enter for more  ---") ;
			$count = 0 ;
			my $print_color=$colors[$col_count] ;
			print_line($line,$print_color) ;
			$still_todo-- ;
		}
		$count++ ;
		$col_count = ($col_count+1) % 2 ;
	}
}

#-------------------------------------------------------------------------------
sub list {
	my ($parameters) = @_ ;
	my $context = $parameters ? $parameters : $g_context ;
	$context =~ s/[ ]+$// ;
	$context = "" if (defined($parameters) and $parameters eq '*');
	if(is_valid_context($context)) {
		print_list(make_todo_array($context,1,\%g_todo)) ;
	} else {
		error("$context is not a valid context") ;
	}
}

#-------------------------------------------------------------------------------
sub history {
	print_list(make_todo_array("",1,\%g_done)) ;
}

#-------------------------------------------------------------------------------
sub clear_history {
	foreach my $key (keys %g_done) {
		delete $g_done{$key} ;
	}
	$g_done_count = 0 ;
	status "history cleared" ;
}

#-------------------------------------------------------------------------------
sub set_context {
	my ($parameters) = @_ ;
	my $newcontext = $parameters ? $parameters : $g_term->readline("Limit context to: ") ;
	$newcontext =~ s/[ ]+$// ;

	$newcontext = "" if ($newcontext eq '*') ;
	if (is_valid_context($newcontext)) {
		$g_context = $newcontext ;
		status "Context limited to $g_context" if ($newcontext) ;
		status "Context limited to *" unless ($newcontext) ;
	} else {
		error("$g_context is not an existing context") ;
	}
}

#-------------------------------------------------------------------------------
sub move_to_context {
	my ($parameters) = @_ ;
	my @numbers = split(/ /,$parameters) ;
	my $context = pop @numbers ;
	$context =~ s/[ ]+$//g ;
	foreach my $number (@numbers) {
		if (is_valid_item($number,\%g_todo)) {
			if ($context =~ m/:/) {
				error("Context may not contain a : character") ;
				return ;
			}
			if ($context eq "wait") {
				$g_todo{$number}{who} = $g_term->readline("Waiting on: ", $g_todo{$number}{who}) ;
			}
			$g_todo{$number}{context} = $context ;
			status("Moved $number to context $context") ;
		} else {
			error("$number is not a valid item number") ;
		}
	}
}

#-------------------------------------------------------------------------------
# Edits an item in the todolist
# edit($number)
#	$number = The item number to edit.
# Post: $g_todo{$number} contains the new entered values
sub edit {
	my ($number) = @_ ;
	$number = $g_last_added_item if ($number eq "&") ;
	if (is_valid_item($number,\%g_todo)) {
		$g_todo{$number}{context} = $g_term->readline("Context: ", $g_todo{$number}{context}) ;
		if ($g_todo{$number}{context} eq 'wait') {
			$g_todo{$number}{who} = $g_term->readline("Waiting on: ", $g_todo{$number}{who}) ;
			$g_todo{$number}{todo} = $g_term->readline("Waiting for: ", $g_todo{$number}{todo}) ;
		} else {
			$g_todo{$number}{todo} = $g_term->readline("Todo: ", $g_todo{$number}{todo}) ;
		}
		$g_todo{$number}{date} = strftime("%Y-%m-%d",localtime()); 
	} else {
		error("$number is not a valid item number") ;
	}
}

#-------------------------------------------------------------------------------
sub now_date {
	my ($number) = @_ ;
	$number =~ s/^[ ]+//g ;
	$number =~ s/[ ]+$//g ;
	$number =~ s/[ ]+/ /g ;
	foreach my $key (split(/ /,$number)) {
		if ($key and is_valid_item($key,\%g_todo)) {
			$g_todo{$key}{date} = strftime("%Y-%m-%d",localtime()); 
			status(todo_line($key,2,$g_todo{$key})." set to current date") ;
		} else {
			if ($key) {
				error("$key is not a valid item key") ;
			} else {
				error("No item key specified") ;
			}
		}
	}
}

#-------------------------------------------------------------------------------
sub set_date {
	my ($input) = @_ ;
	return unless(defined($input)) ;
	my ($number,$timespec) ;
	if ($input =~ /(\+[0-9]+|\-[0-9]|[0-9]{4}\-[0-9]{2}\-[0-9]{2}) ([0-9 ]+|&)/) {
		($number,$timespec) = ($2,$1) ;
	}
	return unless(defined($number) and defined($timespec)) ;
	my $date ;
	if ($timespec =~ /[0-9]{4}\-[0-9]{2}\-[0-9]{2}/) {
		$date = $timespec ;
	} elsif ($timespec =~ /\+([0-9]+)/) {
		$date = strftime("%Y-%m-%d",localtime(time()+$1*24*3600)) ;
	} elsif ($timespec =~ /\-([0-9])+/) {
		$date = strftime("%Y-%m-%d",localtime(time()-$1*24*3600)) ;
	} else {
		error("Error in set date that should not happen!") ;
		return ;
	}

	foreach my $key (split(/ /,$number)) {
		$key = $g_last_added_item if ($key eq "&") ;
		print $key."\n" ;
		if ($key and is_valid_item($key,\%g_todo)) {
			$g_todo{$key}{date} = $date ;
			$g_todo{$key}{duedate} = 1 ;
			status(todo_line($key,2,$g_todo{$key})." set to $date") ;
		} else {
			if ($key) {
				error("$key is not a valid item key") ;
			} else {
				error("No item key specified") ;
			}
		}
	}
}


#-------------------------------------------------------------------------------
sub toggle_important {
	my ($number) = @_ ;
	return unless(defined($number)) ;
	$number =~ s/^[ ]+//g ;
	$number =~ s/[ ]+$//g ;
	$number =~ s/[ ]+/ /g ;
	foreach my $key (split(/ /,$number)) {
		if ($key and is_valid_item($key,\%g_todo)) {
			if ($g_todo{$key}{important}) {
				$g_todo{$key}{important} = 0 ;
				status(todo_line($key,2,$g_todo{$key})." no longer marked as important") ;
			} else {
				$g_todo{$key}{important} = 1 ;
				$g_todo{$key}{today} = 1 ;
				status(todo_line($key,2,$g_todo{$key})." marked as important") ;
			}
		} else {
			if ($key) {
				error("$key is not a valid item key") ;
			} else {
				error("No item key specified") ;
			}
		}
	}
}

#-------------------------------------------------------------------------------
sub expand_reg_number {
	my ($search) = @_ ;
	my @todos ;
	foreach my $key (keys %g_todo) {
		if ($g_todo{$key}{context} eq 'wait') {
			if ($g_todo{$key}{todo}  =~ m/\Q$search/i || $g_todo{$key}{who} =~ m/\Q$search/i) {
				push @todos, $key ;
			}
		} else {
			if ($g_todo{$key}{todo}  =~ m/\Q$search/i ) {
				push @todos, $key ;
			}
		}
	}
	return join " ", @todos ;
}
#-------------------------------------------------------------------------------
# Usage: flagname, flagtext, item number string.
sub toggle_flag {
	my ($flag,$text,$number) = @_ ;
	return unless(defined($number)) ;
	$number =~ s/^[ ]+//g ;
	$number =~ s/[ ]+$//g ;
	$number =~ s/[ ]+/ /g ;
	$number = expand_reg_number(substr($number,1)) if ($number =~ m#^/# );
	return unless(defined($number)) ;
	foreach my $key (split(/ /,$number)) {
		$key = $g_last_added_item if ($key eq "&") ;
		if ($key and is_valid_item($key,\%g_todo)) {
			if ($g_todo{$key}{$flag}) {
				$g_todo{$key}{$flag} = 0 ;
				status(todo_line($key,2,$g_todo{$key})." no longer marked as $text") ;
			} else {
				$g_todo{$key}{$flag} = 1 ;
				status(todo_line($key,2,$g_todo{$key})." marked as $text") ;
			}
		} else {
			if ($key) {
				error("$key is not a valid item key") ;
			} else {
				error("No item key specified") ;
			}
		}
	}
}
#-------------------------------------------------------------------------------
sub clear_today {
	foreach my $key (keys %g_todo) {
		$g_todo{$key}{today} = 0 ;
	}
	status("No items marked as today anymore") ;
}

#-------------------------------------------------------------------------------
# Adds a item to the todolist
# add (<$parameters>)
#	$parameters = String containing the context the item should be added to.
# Post: Item added to %g_todo
sub add {
	my ($parameters) = @_ ;
	my ($context,$who,$todo) ;

	if ($parameters) {
		$parameters =~ s/^[ ]+// ;
		$parameters =~ s/[ ]+$// ;
		if ($parameters =~ / /) {
			my ($t) ;
			($context,$t) = split(/ /,$parameters,2) ;
			if ($context eq 'wait') {
				$who = $t ;
				if ($who =~ /(.*):(.*)/) {
					$who = $1 ;
					$todo = $2 ;
				}
			} else {
				$todo = $t ;
			}
		} else {
			$context = $parameters ;
		}
	}

	$context = $g_term->readline("Context: ") unless ($context) ;

	if ($context eq '+' and $g_context) {
		$context = $g_context ;
	} elsif ($context eq '+') {
		error("There is no current context. Set it before using +") ;
		return ;
	}


	if ($context =~ m/:/) {
		error("Context may not contain a : character") ;
		return ;
	}

	if ($context eq 'wait') {
		$who = $g_term->readline("Waiting on: ") unless ($who) ;
		$todo = $g_term->readline("Waiting for: ") unless ($todo) ;
		add_wait_item($context,$who,$todo) ;
	} else {
		$todo = $g_term->readline("Todo: ") unless ($todo) ;
		add_item($context,$todo) ;
	}
}

#-------------------------------------------------------------------------------
# count_todo_wait()
# Returns the number of todos and the number of waiting for items.
#
# returns:
#	$todo = The number of todo items
#	$wait = The number of waiting for items.
sub count_todo_wait { 
	my $todo = 0 ;
	my $wait = 0 ;
	my $busy = 0 ;
	my $planned = 0 ;
	my $important = 0 ;
	my $today = 0 ;
	foreach my $key (keys %g_todo) {
		if (exists($g_todo{$key}{who})) {
			$wait++ ;
		} else {
			$todo++ ;
		}
		$busy++ if ($g_todo{$key}{busy}) ;
		$today++ if ($g_todo{$key}{today}) ;
		$planned++ if ($g_todo{$key}{planned}) ;
		$important++ if ($g_todo{$key}{important}) ;
	}
	return ($todo,$wait,$busy,$today,$planned,$important) ;
} 

#-------------------------------------------------------------------------------
# Prints a divider that is as width as the terminal.
sub print_divider {
	my ($cols,$lines) = $g_term->TermSize() ;

	# Print a seperator over the terminal width.
	for (my $i=0;$i<$cols;$i++) {
		print '-' ;
	}
	print "\n" ;
}

#-------------------------------------------------------------------------------
sub show_used_contexts {
	my %contexts ;
	foreach my $key (keys %g_todo) {
		$contexts{$g_todo{$key}{context}}++  ;
	}
	status('Available contexts:') ;
	foreach my $context (sort keys(%contexts)) {
		status('    '.$context." ($contexts{$context})") ;
	}
}

#-------------------------------------------------------------------------------
# Prints the status line.
sub print_status_line {
	my ($todo,$wait,$busy,$today,$planned,$important) = count_todo_wait() ;
	my ($queue_count) = count_queue_files($g_queue_dir) ;


	print_divider() ;

	print color 'bold' ;
	print "C: $g_context" if ($g_context) ;
	print "C: * " unless ($g_context) ;
	print " | " ;
	print "# T: $todo" ;
	print " | " ;
	print "# WF: $wait" ;
	print " | " ;
	print "# D: $g_done_count" ;
	print " | # Q: $queue_count" ;
	print " | # MB: $busy" ;
	print " | # MT: $today" ;
	print " | # MP: $planned" ;
	print "\n" ;
	print color 'reset' ;

	print_divider() ;
	if ($important > 3) {
		notice("More than 3 items marked important ($important)") ;
		print_divider() ;
	}
	if ($busy > $g_busy_warn) {
		notice("You have more than $g_busy_warn busy items ($busy)") ;
		print_divider() ;
	}
	if ($todo > $g_warn_total) {
		notice("You have more than $g_warn_total todo items ($todo)") ;
		print_divider() ;
	}
}

sub help {
	print_divider() ;

	print <<EOF
a [context] : add an item to [context]. if no context is provided it is asked for.
b <numbers>  : toggle busy flag for item <number>
c <context> : limit the l command to <context>. When <context> is set to * all items
              are displayed.
ch          : Clear the history list
ct          : Clear all today flags
d <numbers>  : mark item <number> as done.
e <number>  : edit item <number>.
f           : shortcut for a wait <who> .....
h           : list all the items in the history list.
i <number>  : toggle important flag for item <number>
l [context] : list all items in the [context]. Or all if no context is given.
ls [context] : list all items in the [context]. Or all if no context is given.
lb          : list the items marked as busy
lc          : list contexts that are currently in use
ld          : list the items that are due
lf          : list the items that are due in the future
li          : list the items marked as important
lp          : list the items marked as planned
lo          : list the items that are overdue
lt          : list the items marked as today
lw          : list the waiting for items
lwo         : list the waiting for items that are overdue
lwd         : list the waiting for items that are due
lwf         : list the waiting for items that are due in the future
n           : update the date of the entry to the current date.
              Does NOT set the duedate flag.
mv <numbers> <context> : move item <number> to context <context>
p <number>  : toggle planned flag for item <number>
q           : write and quit
r           : read input from the input queue
s [+days|-days|yyyy-mm-dd] <numbers> : Sets the date of the item to day.
                                       Sets the duedate flag.
rm <number> : remove item <number> from the list.
t <numbers> : toggle today flag for item <nubmer>
td <number> : toggles the deadline flag from item <number>
u <number>  : mark item <number> as undone.
v           : print version info.
w           : write todolist to the todofile.
/ <string>  : search for <string> in the todolist
?           : prints this help
EOF
;

}

#-------------------------------------------------------------------------------
# MAIN LOOP
#-------------------------------------------------------------------------------

# Get the todofile name.

$g_config_file = shift @ARGV ;
open(CONFIGFILE,"$g_config_file") or die $! ;
while (my $line=<CONFIGFILE>) {
	chomp $line ;
	my ($item,$value) = split(/=/,$line,2) ;
	$g_todofile = $value if ($item eq 'todofile') ;
	$g_personfile = $value if ($item eq 'personfile') ;
	$g_queue_dir = $value if ($item eq 'queuedir') ;
	$g_queue_auto = $value if ($item eq 'autoqueue') ;
	$g_log_file = $value if ($item eq 'logfile') ;
	$g_busy_warn = $value if ($item eq 'busywarn') ;
	$g_warn_total = $value if ($item eq 'warntotal') ;
	$g_waitdefault = $value if ($item eq 'waitdefault') ;
}
close CONFIGFILE ;

-r $g_todofile or die "The file $g_todofile does not exists :(\n" ;

# Read in our current todo list.
open(TODOFILE,"$g_todofile") or die "Could not open $g_todofile :(\n" ;

$g_svn_version = <TODOFILE> ; # Ignore the svn tag.
$g_svn_version = '$Id: jnitodo.pl 4138 2011-08-24 06:39:11Z jnieuwen $'."\n" unless($g_svn_version) ;

while (my $line = <TODOFILE>) {
	chomp $line ;
	if ($line =~ /^- wait:/) {
		# Handle waiting for events
		$line =~ s/^- // ;
		my ($context,$flags, $date,$who,$todo) = split(/:/,$line,5) ;
		$g_todo{$g_number}{context} = $context ;
		$g_todo{$g_number}{date} = $date ;
		$g_todo{$g_number}{who} = $who ;
		$g_todo{$g_number}{todo} = $todo ;
		$g_todo{$g_number}{todo} =~ s/^[ ]+//;
		$g_todo{$g_number}{who} =~ s/^[ ]+//;
		$g_todo{$g_number}{busy} = 0 ;
		$g_todo{$g_number}{planned} = 0 ;
		$g_todo{$g_number}{important} = 0 ;
		$g_todo{$g_number}{today} = 0 ;
		$g_todo{$g_number}{busy} = 1 if ($flags =~ /B/) ;
		$g_todo{$g_number}{planned} = 1 if ($flags =~ /P/) ;
		$g_todo{$g_number}{important} = 1 if ($flags =~ /I/) ;
		$g_todo{$g_number}{today} = 1 if ($flags =~ /T/) ;
		$g_todo{$g_number}{duedate} = 1 if ($flags =~ /D/) ;
		$g_number++ ;
	} else {
		$line =~ s/^- // ;
		my ($context,$flags, $date,$todo) = split(/:/,$line,4) ;
		$g_todo{$g_number}{context} = $context ;
		$g_todo{$g_number}{date} = $date ;
		$g_todo{$g_number}{todo} = $todo ;
		$g_todo{$g_number}{todo} =~ s/^[ ]+//;
		$g_todo{$g_number}{busy} = 0 ;
		$g_todo{$g_number}{today} = 0 ;
		$g_todo{$g_number}{planned} = 0 ;
		$g_todo{$g_number}{important} = 0 ;
		$g_todo{$g_number}{busy} = 1 if ($flags =~ /B/) ;
		$g_todo{$g_number}{planned} = 1 if ($flags =~ /P/) ;
		$g_todo{$g_number}{important} = 1 if ($flags =~ /I/) ;
		$g_todo{$g_number}{today} = 1 if ($flags =~ /T/) ;
		$g_todo{$g_number}{duedate} = 1 if ($flags =~ /D/) ;
		$g_number++ ;
	}
}

close TODOFILE ;

# Set the complition function.
$g_term->Attribs()->{completion_function} = \&completion ;

# Enter waiting loop of commands.
while (1) {
	print_status_line() ;
	my $input = $g_term->readline('% ') ;
	read_queue_dir($g_queue_dir) if ($g_queue_auto) ;
	write_file(0) unless($input) ; #Ignore empty input.
	next unless($input) ;
	$g_term->addhistory($input) ;
	#chomp $input ;
	$input =~ s/;[ ]*/;/g ;
	my @cmds = split(/;/,$input) ;
	foreach $input (@cmds) {
		$input =~ s#^/(.*)#/ $1# if ($input =~ m#^/[^ ]#) ;
		$input =~ s/^([a-z])([0-9 ]+)$/$1 $2/ if ($input =~ m/^[a-z][0-9 ]+$/) ;
		my ($command,$parameters) = split(/ /,$input,2) ;
		$parameters =~ s/^[ ]+// if ($parameters);
		SWITCH: {
			if ($command eq '/') { search($parameters) ; last SWITCH } ;
			if ($command eq '?') { help() ; last SWITCH } ;
			if ($command eq 'a') { add($parameters) ; last SWITCH } ;
			if ($command eq 'b') { toggle_flag('busy','busy',$parameters) ; last SWITCH } ;
			if ($command eq 'c') { set_context($parameters) ; last SWITCH } ;
			if ($command eq 'ch') { clear_history() ; last SWITCH } ;
			if ($command eq 'ct') { clear_today() ; last SWITCH } ;
			if ($command eq 'd') { todo_done($parameters) ; last SWITCH } ;
			if ($command eq 'e') { edit($parameters) ; last SWITCH } ;
			if ($command eq 'f') { $parameters="" unless (defined($parameters)) ;
									add("wait ".$parameters) ; last SWITCH } ;
			if ($command eq 'h') { history ; last SWITCH } ;
			if ($command eq 'l') { list($parameters) ; last SWITCH } ;
			if ($command eq 'ls') { list($parameters) ; last SWITCH } ;
			if ($command eq 'lw') { list('wait') ; last SWITCH } ;
			if ($command eq 'lb') { show_flag('busy',$parameters) ; last SWITCH } ;
			if ($command eq 'lc') { show_used_contexts() ; last SWITCH } ;
			if ($command eq 'ld') { show_due('duedate',$parameters) ; last SWITCH } ;
			if ($command eq 'lwd') { show_due('duedate','wait') ; last SWITCH } ;
			if ($command eq 'lf') { show_hasdue('duedate',$parameters) ; last SWITCH } ;
			if ($command eq 'lwf') { show_hasdue('duedate','wait') ; last SWITCH } ;
			if ($command eq 'lo') { show_overdue('duedate',$parameters) ; last SWITCH } ;
			if ($command eq 'lwo') { show_overdue('duedate','wait') ; last SWITCH } ;
			if ($command eq 'lp') { show_flag('planned',$parameters) ; last SWITCH } ;
			if ($command eq 'li') { show_flag('important',$parameters) ; last SWITCH } ;
			if ($command eq 'lt') { show_flag('today',$parameters) ; last SWITCH } ;
			if ($command eq 'n') { now_date($parameters) ; last SWITCH } ;
			if ($command eq 'mv') { move_to_context($parameters) ; last SWITCH } ;
			if ($command eq 'p') { toggle_flag('planned','planned',$parameters) ; last SWITCH } ;
			if ($command eq 'i') { toggle_important($parameters) ; last SWITCH } ;
			if ($command eq 'q') { write_and_quit() ; last SWITCH } ;
			if ($command eq 'r') { read_queue_dir($g_queue_dir) ; last SWITCH } ;
			if ($command eq 's') { set_date($parameters) ; last SWITCH } ;
			if ($command eq 'rm') { todo_remove($parameters) ; last SWITCH } ;
			if ($command eq 't') { toggle_flag('today','today',$parameters) ; last SWITCH } ;
			if ($command eq 'td') { toggle_flag('duedate','having a due date',$parameters) ; last SWITCH } ;
			if ($command eq 'u') { todo_undone($parameters) ; last SWITCH } ;
			if ($command eq 'v') { version() ; last SWITCH } ;
			if ($command eq 'w') { write_file(1) ; last SWITCH } ;
			if ($command =~ m#^https{0,1}://(.*)#) {
				add('online '.$1) ;
			}
			# error()
		}
		write_file(0) ; # Always flush the todo file case the next action triggers a bug.
	}
}


