#!/perl/bin/perl       # windows
#/usr/bin/perl         # linux
#---------------
#
#    Sambru* - a free phonebook synch utility for 
#           Samsung A310 cellular phones
#
#       (*Samsung Backup and Restore Utility)
#
#---------------
#
#    Modified by Ben Henty   (email: henty+sambru@ghz.cc)
#    Version 0.21.20
#    Jan 13, 2003
#
#    based on Sambru v. 0.21 by Eric Sandeen (eric_sandeen@bigfoot.com)
#    
#    Modifications Copyright (C) 2003 Benjamin E. Henty 
#    Original Copyright (C) 2000 Eric Sandeen (eric_sandeen@bigfoot.com)
#
#    The original program and its modifications are 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., 675 Mass Ave, Cambridge, MA 02139, USA.
#        
#    Original source code by Eric Sandeen also released under the 
#    GNU General Public License and is available for download at:
#    http://lager.dyndns.org/sambru/
#
#    Summary of modifications: 
#    - Added csv format and convert_to_csv and convert_from_csv subroutines
#    - Modified opt_get and opt_put routines from original code.
#    - Added opt_gets, opt_puts, opt_gett, and opt_putt for schedule and todo items. 
#    - Modified beautify_number routine to my personal preferences
#    - Modified behavior of vcard output to use X-SAMBRU fields instead of TITLE and ORG
#    - Added check record routines for todo and get items
#    - Modified code to work under windows using the Win32::SerialPort package
#    - Changed field formats and at commands for use with the samsung a310 phone.
#
#----------------
#
#    The script CAN OVERWRITE ALL DATA IN YOUR PHONE.  
#
#    To get this work under windows you're going to need perl and the 
#    Win32::SerialPort perl package available at:
#    http://search.cpan.org/author/BBIRTH/Win32-SerialPort-0.19/
#    I've had success with the Active State's Active Perl package 
#    available at:
#    http://www.activestate.com/Products/ActivePerl
#
#    Quite a bit of the code is untested.  
#    If you find a bug, please send it my way.
#
#    If you find yourself getting a "got eof" error occaisionally, 
#    try increasing $DELAY_BETWN_CMDS 
# 
#    I've had some problems getting the a310 to report what is 
#    assumed to be the voice dial phone number so it is not 
#    implemented in this code.  
#
#    The raw format should not be used for backup since the input and output
#    AT commands of the phone have different syntax.
#
#    I use TODO: comments to indicate functions that are not working
#    and FIXME: comments to indicate functionality that may not work
#    in the best way possible (e.g. it's a hack).


# Don't set both to non-zero.
$WINDOWS = 1;
$LINUX = 0;

use FileHandle;
use Getopt::Long;
if ($LINUX) { 
  use IPC::Open2;
  require 5.003;
}
if ($WINDOWS) {
  use Win32::SerialPort qw( :STAT 0.19 );
}


#-----------------------
# Constants --  You may need to edit these
if ($LINUX) {
  $cu_path = "/usr/bin/cu";
  $port = "/dev/ttyS0"; 	# Default
} 
if ($WINDOWS) {
  $port		= "COM2";	# Default
}
$baud_rate	= "57600";	# should be able to make this faster
$Configuration_File_Name = "sambru.cfg";
$debug = 0;
$version = "0.21.20";
#$default_event_start_time = "1000";  
#$default_event_end_time = "1000";
$DELAY_BETWN_CMDS = 1.3;        

# if function should overwrite files
$ALLOW_ENTRY_ERASE = 1;
$ALLOW_SCHED_ERASE = 1;
$ALLOW_TODO_ERASE = 1;
#-----------------------


# Other constants
# FIXME: Could get these from AT#PBOKR=? etc
# phone book constants
$MIN_ENTRIES = 1;
$MAX_ENTRIES  = 500;
$MAX_RINGER_NUMBER = 21;  # FIXME: actually use these constants
$MAX_NAME_LENGTH   = 12;
$MAX_NUMBER_LENGTH = 32;
$MAX_EMAIL_LENGTH = 48;
@NUM_TO_LABEL=("HOME", "WORK", "CELL", "PAGER", "FAX", "");   # vcard labels
%LABEL_TO_NUM=("HOME","8",
               "WORK","10",
               "CELL","12",
               "PAGER","14",
               "FAX","16");

# schedule constants
$MIN_SCHED = 0;
$MAX_SCHED = 19;  
$MAX_SCHED_LENGTH = 32;

# todo list constants
$MIN_TODO = 0;
$MAX_TODO = 19;
$MAX_TODO_LENGTH = 32;


#-----------------------
# Begin code
#-----------------------

if ($debug) {
  print STDERR "starting up\n";
  $foo = localtime();
  print STDERR "now: $foo\n";
}

GetOptions	("format:s",	# Records format (default RAW, or vcard or csv)
		 "getp",		# Get phone numbers from phone
		 "putp",		# Put phone numbers into phone
		 "gets",		# Get schedule from phone
		 "puts",		# Put schedule into phone
		 "gett",	 # Get todo list from phone
		 "putt",	 # Put todo list into phone
		 "file:s",	# File to read or write from (else STDIN/STDOUT)
		 "port:s",	# Serial device (default /dev/ttyS0 or COM2)
		 "help"		# Show help usage
		)

  or Show_Usage();

if ($opt_port) {
  $port = $opt_port;
}

if (!$opt_putp && !$opt_getp && !$opt_puts && !$opt_gets && !opt_gett && !opt_putt) {
  print "\nERROR - must specify a get or a put\n";
  Show_Usage();
}

if ($opt_putp && $opt_getp) {
  print "\nERROR - must specify either --getp OR --putp\n";
  Show_Usage();
}

if ($opt_puts && $opt_gets) {
  print "\nERROR - must specify either --gets OR --puts\n";
  Show_Usage();
}

if ($opt_putt && $opt_gett) {
  print "\nERROR - must specify either --gett OR --putt\n";
  Show_Usage();
}

if (($opt_putp && $opt_puts) || ($opt_putp && $opt_putt) || ($opt_puts && $opt_putt)) {
  print "\nError - only one put option allowed\n";
  Show_Usage();
}

if (($opt_getp && $opt_gets) || ($opt_getp && $opt_gett) || ($opt_gets && $opt_gett)) {
  print "\nError - only one get option allowed\n";
  Show_Usage();
}

if ($opt_help) {
  Show_Usage();
}

# Open port and put the phone in data mode
open_phone();

# open/redirect files
if ($opt_file) {
  if (($opt_putp) || ($opt_puts) || ($opt_putt)) {
    # if putting Open file & Redirect STDIN
    open(FILE, "<$opt_file");
    *STDINSAVE = *STDIN;
    *STDIN = *FILE;
  } else {
    # if getting Open file & Redirect STDOUT
    open(FILE, ">$opt_file");
    *STDOUTSAVE = *STDOUT;
    *STDOUT = *FILE;
  }
}

###########################################################################################
# Get phone book from phone
###########################################################################################

if ($opt_getp) {
  if ($debug) {
    print STDERR "starting get phone numbers\n";
  }
  
  # header for csv files
  if ($opt_format eq "csv") {
    print "\"Key\",\"Speed Dial\",\"Group Number\",\"Ringer Number\",\"User Name\",";
    print "\"Which Speed Dial\",\"Secret\",\"Home\",\"Office\",\"Mobile\",\"Pager\",";
    print "\"Fax\",\"No label\",\"Voice Dial\",\"E-Mail\",\"Time Stamp\"\n";
  }

  for ( $entry_num = $MIN_ENTRIES; $entry_num <= $MAX_ENTRIES; $entry_num++) {
    print STDERR "Reading phone number $entry_num/$MAX_ENTRIES\r";
    $entry = do_command("AT#PBOKR=$entry_num");
    if ($entry) {
      if ($opt_format eq "vcard") {
        $entry = convert_to_vcard($entry);
      } elsif ($opt_format eq "csv") {
        $entry = convert_to_csv($entry);
      } 
      print "$entry\n";
    }	
  }

  print STDERR "\nDone			     \n";
}

###########################################################################################
# Put Phone book on phone
###########################################################################################

if ($opt_putp) {
  if ($debug) {
    print STDERR "Starting put of phone book\n";
  }

  # Read each entry from file, and load into phone
  # WARNING - no checking for duplicate locations, so may overwrite
  # some previously loaded entries.
  while ( !eof(STDIN) ) {
    if ($opt_format eq "vcard") {
      $entry = convert_from_vcard();
    } elsif ($opt_format eq "csv") {
      $entry = convert_from_csv();
    } else {
      $entry = <STDIN>;
    }
    chomp ($entry);
    @fields = split(/,/,$entry);	# Split entry into array

    # Sanity check what we're writing to the phone	
    if (($entry) && (!check_entry(@fields))) {    
      print STDERR "Writing to location $fields[1]/$MAX_ENTRIES\r";
      do_command("AT#PBOKW=$entry");
    }
  }

  print STDERR "\nDone			     \n";
}

###########################################################################################
# Get Schedule from Phone
###########################################################################################

if ($opt_gets) {
  if ($debug) {
    print STDERR "starting get of schedule\n";
  }
  
  # header for csv files
  if ($opt_format eq "csv") {
    print "\"Key\",\"Start Time\",\"Stop Time\",\"Creation Time\",\"Alarm Code\",\"Event Name\"\n";
  }
  # header for vcard file
  if ($opt_format eq "vcard") {
    print "BEGIN:VCALENDAR\nVERSION:1.0\n";
  }

  # Read each entry from schedule and display
  for ( $entry_num = $MIN_SCHED; $entry_num <= $MAX_SCHED; $entry_num++) {
    print STDERR "Reading event $entry_num/$MAX_SCHED\r";
    $entry = do_command("AT#PISHR=$entry_num");
    if ($entry) {
      if ($opt_format eq "vcard") {
        $entry = sched_to_vcard($entry);
      } elsif ($opt_format eq "csv") {
        $entry = sched_to_csv($entry);
      } 
      print "$entry\n";
    }
  }

  # footer for vcard file 
  if ($opt_format eq "vcard") {
    print "END:VCALENDAR\n\n";
  }  
  
  print STDERR "\nDone			     \n";
}

###########################################################################################
# Put schedule in phone
###########################################################################################

if ($opt_puts) {
  if ($debug) {
    print STDERR "starting put of schedule\n";
  }

  # Read each entry from file, and load into phone
  # WARNING - no checking for duplicate locations, so may overwrite
  # some previously loaded entries.

  while ( !eof(STDIN) ) {	
    if ($opt_format eq "csv") {
      $entry = csv_to_sched();							
    } elsif ($opt_format eq "vcard") {
      $entry = sched_from_vcard();
    } else {
      $entry = <STDIN>;
    }
    chomp ($entry);
    @fields = split(/,/,$entry);	# Split entry into array

    # Sanity check what we're writing to the phone	
    if (($entry) && (!check_sched(@fields))) {
      print STDERR "Writing to location $fields[0]/$MAX_SCHED\r";
      do_command("AT#PISHW=$entry");
    }
  }
  
  print STDERR "\nDone			     \n";
}

###########################################################################################
# Get todo list in phone
###########################################################################################

if ($opt_gett) {
  if ($debug) {
    print STDERR "starting get of todo list\n";
  }

  # header for csv files
  if ($opt_format eq "csv") {
    print "\"Key\",\"Urgent\",\"Due Date\",\"Creation Date\",\"Task Name\"\n";
  }
  # header for vcard file
  if ($opt_format eq "vcard") {
    print "BEGIN:VCALENDAR\nVERSION:1.0\n";
  }

  # Read each entry from schedule and display
  for ( $entry_num = $MIN_TODO; $entry_num <= $MAX_TODO; $entry_num++) {
    print STDERR "Reading event $entry_num/$MAX_TODO\r";
    $entry = do_command("AT#PITDR=$entry_num");
    if ($entry) {
      if ($opt_format eq "vcard") {
        $entry = todo_to_vcard($entry);
      } elsif ($opt_format eq "csv") {
        $entry = todo_to_csv($entry);
      } 
      print "$entry\n";
    }
  }

  # footer for vcard file 
  if ($opt_format eq "vcard") {
    print "END:VCALENDAR\n\n";
  }

  print STDERR "\nDone                      \n";
}


###########################################################################################
# Put todo list in phone
###########################################################################################

if ($opt_putt) {
  if ($debug) {
    print STDERR "ending put of todo list\n";
  }
  
  # Read each entry from file, and load into phone
  # WARNING - no checking for duplicate locations, so may overwrite
  # some previously loaded entries.

  while ( !eof(STDIN) ) {	
    if ($opt_format eq "csv") {
      $entry = csv_to_todo();							
    } elsif ($opt_format eq "vcard") {
      $entry = todo_from_vcard();
    } else {
      $entry = <STDIN>;
    }
    chomp ($entry);
    @fields = split(/,/,$entry);	# Split entry into array

    # Sanity check what we're writing to the phone	
    if (($entry) && (!check_todo(@fields))) {
      print STDERR "Writing to location $fields[0]/$MAX_SCHED\r";
      do_command("AT#PITDW=$entry");
    }
  }

  print STDERR "\nDone                              \n";
}

########## get/put complete #############

# close/redirect file handles
if ($opt_file) {
  if (($opt_putp) || ($opt_puts) || ($opt_putt)) {
    # Close file & Restore STDIN
    *FILE = *STDIN;
    close(FILE);
    *STDIN = *STDINSAVE;
  } else { 
    # Close file & Restore STDOUT
    *FILE = *STDOUT;
    close(FILE);
    *STDOUT = *STDOUTSAVE;    
  }
}
  
close_phone();

############################
# Phone Access Subroutines #
############################

###########################################################################################
# Open phone: Get phone's attention, set echo off, set data mode
###########################################################################################

sub open_phone {
  if ($WINDOWS) {
    $serialport = new Win32::SerialPort ($port) || die "Can't open $serialport: $^E\n";    
    
    $serialport->user_msg(ON);
    $serialport->databits(8);
    $serialport->baudrate($baud_rate);
    $serialport->parity("none");
    $serialport->stopbits(1);
    #$serialport->handshake("rts");
    $serialport->buffers(4096, 4096);
  
    $serialport->write_settings || undef $serialport;
  
    $serialport->save($Configuration_File_Name) || warn "Can't save $Configuration_File_Name: $^E\n";
    #	$serialport->close() || warn "close failed";
    #	undef $serialport;
    #	$serialport = tie (*TO_PHONE, 'Win32::SerialPort', $Configuration_File_Name) || die "Can't open $serialport: $^E\n";
  
    *FROM_PHONE = *TO_PHONE;
  }

  if ($LINUX) {
    ############
    # Start the 'cu' program and get handles to in, out
    # NEED TO DO SOME ERROR CHECKING HERE!
    ###open2(*FROM_PHONE, *TO_PHONE, "$cu_path -l$port -s$baud_rate 2>&1");
  }

  if ($debug) {
    print STDERR "first command\n";
  }
  
  # Init phone, turn off command echo
  do_command("ATE0");
  sleep(2);    # give windows a chance to find the usb port ...
  do_command("ATE0");
  
  # Make sure this is a phone we can talk to...
  $model = do_command("AT+GMM");
  if ($model =~ /SCH-A310/) {
    print STDERR "Found phone model $model\n";
  } else {
    die ("Whoa, buddy, can't talk to this $model phone!");
  }
  
  $battery = do_command("AT+CBC?");
  print STDERR "Battery level at $battery\n";
  
  # Put it into data mode
  do_command("AT#PMODE=1");
}

###########################################################################################
# Close out phone: end data mode, set echo on, kill cu
###########################################################################################

sub close_phone {

  do_command("AT#PMODE=0");
  # #PMODE=0 gives us 2 "OK" lines for some reason on SCH 6100, 8500, flush it...
  #read_line();   
  
  # Turn command echoes back on
  do_command("ATE1");
  
  $serialport->close || die "failed to close serial port";  
  undef $serialport;	 # frees memory back to perl
  
  if ($LINUX) {
    untie *TO_PHONE;

    # end cu with "~."
    print TO_PHONE "\r\n~.\r\n";
  }
  close FROM_PHONE;
  close TO_PHONE;
  die ("\n");
}

###########################################################################################
# Sends a command to the phone, and returns result
# (result is any information before "OK", with original
# command stripped out of the line)
###########################################################################################

sub do_command {
  my ($command) = @_;

  if ($WINDOWS) {
    $serialport->write("$command\r\n")    || warn "couldn't send command to phone";
  } 
  if ($LINUX) {
    #print TO_PHONE "$command\r\n"	|| warn "couldn't send command to phone";
    print TO_PHONE "$command\n"          || warn "couldn't send command to phone";
  }
  
  # insert delay to prevent getting stuck  # FIXME: look sooner, but look again if nothing returned yet 
  if ($DELAY_BETWN_CMDS >= 1) {     
    sleep($DELAY_BETWN_CMDS);           # >= 1 second delay
  } else { 
    select(undef, undef, undef, $DELAY_BETWN_CMDS);   # < 1 second delay
  }  

  $result = get_result();
  die ("Got ERROR from phone on command $command") if ($result =~ /ERROR/);
  
  # Strip down to result
  # Remove original command, error, ok, etc.
  #$result =~ s/^.+:\s//;    
  $result =~ s/.*:\s|OK|ERROR//g;
  return $result;
}

###########################################################################################
# Retrieves lines until it gets an "OK" or an "ERROR"
# and then returns all the lines in one string...
###########################################################################################

sub get_result {
  $lines = "";
  do {
    $line = read_line();
    $lines .= $line;
  } while ( ( $line ne "OK" ) && ( $line ne "ERROR" ) );
  return $lines;
}

###########################################################################################
# Gets a single line from the phone, dies if it's empty
# Strips any newline monkey business as well
###########################################################################################

sub read_line {
  if ($LINUX) {
    $_ = <FROM_PHONE>;
  }
  if ($WINDOWS) {
    $_ = $serialport->streamline();
  }
  $_ || die("got eof on serial");
  s/[\r\n]+$//;

  return $_;
}

#########################################
# Phone Book Record conversion routines #
#########################################

###########################################################################################
# Convert a phone record to a vcard format
###########################################################################################

sub convert_to_vcard {

  # AT#PBOKR=<actual location>,
  #	<speed dial>,
  #	<group:0=friend,1=family,2=collegue,3=vip,4=noname>,
  #	<ringer:0=normal,1=bell1,...21=melody16>,
  #	"12 Char Name",
  #	<whichnum2speeddial:0=home,1=office,2=mobile,etc>,
  #	<secret 0=not, 1=is, same for all>,<32 digit home>,
  #	<secret>,<32 digit office>,
  #	<secret>,<32 digit mobile>,
  #	<secret>,<32 digit pager>,
  #	<secret>,<32 digit fax>,
  #	<secret>,<32 digit other>,
  #	<secret>,
  #	,
  #	,
  #	"48 char email address",
  #	<timestamp>

  my ($entry) = @_;
  
  @fields = split(/,/,$entry);	# Split entry into array
  $fields[4] =~ s/"//g;		# Strip quote marks from name
  $fields[21] =~ s/"//g;        # Strip quote marks from email address
  $record =  "BEGIN:VCARD\nVERSION:3.0\n";
  $record .= "UID:$fields[0]\n";
  $record .= "X-SAMBRU-SPEED-DIAL-LOC:$fields[1]\n";
  $record .= "X-SAMBRU-GROUP-NUM:$fields[2]\n";
  $record .= "X-SAMBRU-RINGER:$fields[3]\n";
  $record .= "FN:$fields[4]\n";	                     # (name)
  $pref_num = beautify_number($fields[$fields[5]*2 + 7]);             # (which number to speed dial)
  $record .= "TEL;PREF:$pref_num\n";
  $record .= "X-SAMBRU-PRIVATE:$fields[6]\n";
  for ($x=7; $x<=17; $x += 2) {	# Process each phone number
    if ($fields[$x]) {	# If we have an entry
      $type = $NUM_TO_LABEL[(($x-7)/2)];
      $number = $fields[$x];
      $number = beautify_number($number);
      if ($type) {	# If there is a type
        $record .= "TEL;$type:$number\n";
      }
      else {
        $record .= "TEL:$number\n"
      }
    }
  }
  if ($fields[21]) {
    $record .= "EMAIL;INTERNET:$fields[21]\n";
  }
  $record .= "X-SAMBRU-TIMESTAMP:$fields[22]\n";
  $record .= "END:VCARD";    # no final \n because getp does that for us
  
  return $record;
}

###########################################################################################
# Convert from a vcard file format to a phone record
###########################################################################################
# FIXME: Defaults for X-SAMBRU-KEY and X-SPEED-DIAL-LOC fields...
# FIXME: Less reliance on X-SAMBRU fields...

sub convert_from_vcard {
  # This is a tad yucky.  We use X-SAMBRU fields for anything that 
  # vcards can't quite handle.  Might be better the old way using the 
  # TITLE and ORG fields, depending on your software.  
  #
  # This assumes a decently formatted vcard entry...
  
  # Suck in the whole vcard record
  # NOTE: [\r\n] to take care of DOS format file (double check this...)
  $vcard = "";
  while ( !eof(STDIN) && $vcard !~ /END:VCARD/i ) {
    $vcard .= <STDIN>;
  }

  # insert tag at begining of record entry
  $entry = "0,";    
  # Get memory location       FIXME: default
  $vcard =~ /UID:(.+)[\r\n]/mi;
  $loc = $1;
  $entry .= "$loc,";
  # Get Speed dial location   FIXME: default
  if ($vcard =~ /X-SAMBRU-SPEED-DIAL-LOC:(.+)[\r\n]/mi) {
    $entry .="$1,";
  } else {
    $entry .="$loc,";   # default same as memory location
  }
  # Get Group number
  if ($vcard =~ /X-SAMBRU-GROUP-NUM:(.+)[\r\n]/mi) {
    $entry .= "$1,";
  } else {
    $entry .= "4,";   # Default group: No name
  }
  # Get Ringer
  if ($vcard =~ /X-SAMBRU-RINGER:(.+)[\r\n]/mi) {
    $entry .= "$1,";
  } else {
    $entry .= "0,";
  }
  # Get Name
  $vcard =~ /FN:(.+)[\r\n]/mi;
  $name = $1;
  $entry .= "\"$1\",";
  # Get Preferred Phone Number   FIXME: this is pretty nasty right here
  if ($vcard =~ /TEL;PREF:(.+)[\r\n]/mi) {
    $pref_num = $1;
    if ($vcard =~ /TEL;($NUM_TO_LABEL[0]|$NUM_TO_LABEL[1]|$NUM_TO_LABEL[2]|$NUM_TO_LABEL[3]|$NUM_TO_LABEL[4]):$pref_num[\r\n]/mi) {
      $spddial = $LABEL_TO_NUM{$1}/2-4;   # FIXME: magic numbers bad
      if ($spddial eq "") {
        $spddial = 5;   # other speed dial number
      }
      $entry .= "$spddial,";
    } else { 
      $entry .= "0,";   # default speed dial number
    }
  } else {
    $entry .= "0,";     # default speed dial number
  }
  # Get Private tag
  if ($vcard =~ /X-SAMBRU-PRIVATE:(.+)[\r\n]/mi) {
    $priv = "$1";
  } else {
    $priv = "0";    
  }
  $entry .= "$priv,";
  # Get labeled numbers
  for ($x=0; $x<5; $x++) {
    if ($vcard =~ /TEL;$NUM_TO_LABEL[$x]:(.+)[\r\n]/mi) {
      $phone_number = $1;
      $phone_number =~ s/\D//g;
      $entry .= "$phone_number,$priv,";
    } else {
      $entry .= ",,";
    }
  }
  # Get unlabeled number
  if ($vcard =~ /TEL:(.+)[\r\n]/mi) {
    $phone_number = $1;
    $phone_number =~ s/\D//g;
    $entry .= "$phone_number,$priv,";
  } else {
    $entry .= ",,";
  }
  # FIXME: Voice dial number?
  $entry .= ",,";
  # get email address 
  if ($vcard =~ /EMAIL;INTERNET:(.+)[\r\n]/mi) {
    $entry .= "\"$1\",";
  } else {
    $entry .= "\"\",";
  }
  # get time stamp
  if ($vcard =~ /X-SAMBRU-TIMESTAMP:(.+)[\r\n]/mi) {
    $entry .= "$1\n";
  } else {
    $now = u2s_time(time());
    $entry .= "$now\n";
  }

  # suck up trailing empty lines
  #while (<STDIN> eq "\n") {}
  return $entry;
}

###########################################################################################
# Convert a phone record to a csv file line
###########################################################################################
# FIXME: It'd be nice to use AT#PBGRW to read the group name and write that out

# phone record format:
# 0      <actual location>,
# 1      <speed dial>,
# 2      <group:0=friend,1=family,2=collegue,3=vip,4=noname>,
# 3      <ringer:0=normal,1=bell1,...21=melody16>,
# 4      "12 Char Name",
# 5      <whichnum2speeddial:0=home,1=office,2=mobile,etc>,
# 6,7    <secret 0=not, 1=is, same for all>,<32 digit home>,
# 8,9    <secret>,<32 digit office>,
# 10,11  <secret>,<32 digit mobile>,
# 12,13  <secret>,<32 digit pager>,
# 14,15  <secret>,<32 digit fax>,
# 16,17  <secret>,<32 digit other>,
# 18,19  <secret>,<32 digit voice dial?>,
# 20      <secret?>,
# 21     "48 char email address",
# 22     <timestamp>

# csv format:
#  print "\"Key\",\"Speed Dial\",\"Group Number\",\"Ringer Number\",\"User Name\",";
#  print "\"Which Speed Dial\",\"Secret\",\"Home\",\"Office\",\"Mobile\",\"Pager\",";
#  print "\"Fax\",\"No label\",\"Voice Dial\",\"E-Mail\",\"Time Stamp\"\n";


sub convert_to_csv {
  my ($entry) = @_;
	
  @fields = split(/,/,$entry);	# Split entry into array
  
  for ($x=7; $x<=19; $x += 2) {
    $fields[$x] = beautify_number($fields[$x]);
  }

  $record =  "\"$fields[0]\",\"$fields[1]\",\"$fields[2]\",\"$fields[3]\",$fields[4],";
  $record .= "\"$fields[5]\",\"$fields[6]\",\"$fields[7]\",\"$fields[9]\",\"$fields[11]\",";
  $record .= "\"$fields[13]\",\"$fields[15]\",\"$fields[17]\",\"$fields[19]\",";
  $record .= "$fields[21],\"$fields[22]\"";
  
  return $record;
}

###########################################################################################
# convert a csv line to a phone record
###########################################################################################
# expects fields in particular order:

# csv format:
#  print "\"Key\",\"Speed Dial\",\"Group Number\",\"Ringer Number\",\"User Name\",";
#  print "\"Which Speed Dial\",\"Secret\",\"Home\",\"Office\",\"Mobile\",\"Pager\",";
#  print "\"Fax\",\"No label\",\"Voice Dial\",\"E-Mail\",\"Time Stamp\"\n";

# phone record format:
# 0      <0 = new record, 1-500 delete record>
# 1      <actual location>,
# 2      <speed dial>,
# 3      <group:0=friend,1=family,2=collegue,3=vip,4=noname>,
# 4      <ringer:0=normal,1=bell1,...21=melody16>,
# 5      "12 Char Name",
# 6      <whichnum2speeddial:0=home,1=office,2=mobile,etc>,
# 7,8    <secret 0=not, 1=is, same for all>,<32 digit home>,
# 9,10   <secret>,<32 digit office>,
# 11,12  <secret>,<32 digit mobile>,
# 13,14  <secret>,<32 digit pager>,
# 15,16  <secret>,<32 digit fax>,
# 17,18  <secret>,<32 digit other>,
# 19,20  <secret>,<32 digit voice dial?>,
# 21      <secret?>,
# 22     "48 char email address",
# 23     <timestamp>

sub convert_from_csv {
  # snag line
  $line = <STDIN>;
  if ($line =~ /^"Key","Speed Dial".*/) {   # skip file header
    $line = <STDIN>;
  }
  chomp($line);
  
  $line =~ s/"//g;    # remove quotes from all fields
  @fields = split(/,/,$line);
  
  $entry =  "0,$fields[0],$fields[1],$fields[2],$fields[3],\"$fields[4]\",$fields[5],$fields[6],";

  # add phone numbers
  for ($x=7; $x<=13; $x++) {
    $fields[$x] =~ s/\D//g;       # strip anything but digits from phone numbers
    if ($fields[$x] ne "") {
      $entry .= "$fields[$x],$fields[6],";  
    } else {
      $entry .= ",,";
    }
  }  

  $entry .= "\"$fields[14]\",";
  if ($fields[15] eq "") {
    $fields[15] = u2s_time(time());
  }
  $entry .= "$fields[15]\n";

  return $entry;
}

################################
# Schedule conversion routines #
################################

###########################################################################################
# TODO: Convert a phone schedule item to vcalendar format
###########################################################################################

# Phone format
# at#pishw=<storage location,0-19>,
#           <start time timestamp>,
#           <stop time timestamp>,
#           <event entry time stamp>,
#           <when to alarm,0=noalarm,1=ontime,2=10min,3=30min,4=1hr>,
#           "event name 32 chars or less"
# timestamps: YYYYMMDDTHHMMSS

# FIXME: put all events in one file
sub sched_to_vcard {
  my ($entry) = @_;
  
  @fields = split(/,/,$entry);	# Split entry into array
  $fields[6] =~ s/"//g;		# Strip quote marks from event name

  $record =  "BEGIN:VEVENT\n";
  $record .= "UID:$fields[0]\n";
  $record .= "DTSTART:$fields[1]\n";
  $record .= "DTEND:$fields[2]\n";
  $record .= "LAST-MODIFIED:$fields[3]\n";
  $record .= "X-SAMBRU-ALARM-CODE:$fields[4]\n";   # FIXME: use DUE: with a full computed 
                                                  # FIXME: timestamp based on start time
  $record .= "SUMMARY:$fields[6]\n";
  $record .= "END:VEVENT";                    # no final \n because opt-gets does that for us
  
  return $record;
}

###########################################################################################
# Convert a vcalendar event to phone format
###########################################################################################

sub sched_from_vcard {
  # Suck in the whole vcard record
  # NOTE: [\r\n] to take care of DOS format file (double check this...)
  $vcard = "";
  while ( !eof(STDIN) && $vcard !~ /END:VEVENT/i ) {
    $vcard .= <STDIN>;
  }
  $record = "";

  # Get key
  if ($vcard =~ /UID:(.+)[\r\n]/mi) {
    $record .= "$1,";
  } else {
    return "";
  }
  # Get start date
  $vcard =~ /DTSTART:(.+)[\r\n]/mi;
  $vstart = $1;
  $record .= "$start,";
  # Get end date
  if ($vcard =~ /DTEND:(.+)[\r\n]/mi) {
    $vend = $1;
  } else { 
    $vend = $vstart;
  }
  $record .= "$end,";
  # Get alarm time
  if ($vcard =~ /X-SAMBRU-ALARM-CODE:(.+)[\r\n]/mi) {
    $record .= "$1,";
  } else {
    $record .= "0,";  # Default: no alarm
  }
  # Get modification date
  if ($vcard =~ /LAST-MODIFIED:(.+)[\r\n]/mi) {
    $modtime = $1;
  } else {
    $modtime = u2s_time(time());  # default modification time of now
  }
  $record .= "$modtime,";
  # Get event name
  $vcard =~ /SUMMARY:(.+)[\r\n]/mi;
  $record .= "\"$1\"\n";
  
  return $record;
}


###########################################################################################
# Convert a phone schedule item to csv format
###########################################################################################
# FIXME: Convert Alarm Code to minutes or something similar

# Csv format:	
#  print "\"Key\",\"Start Time\",\"Stop Time\",\"Creation Time\",\"Alarm Code\",\"Event Name\"\n";

# Phone format
# at#pishw=<storage location,0-19>,
#           <start time timestamp>,
#           <stop time timestamp>,
#           <event entry time stamp>,
#           <when to alarm,0=noalarm,1=ontime,2=10min,3=30min,4=1hr>,
#           "event name 32 chars or less"
# timestamps: YYYYMMDDTHHMMSS

sub sched_to_csv {
  my ($entry) = @_;
  
  @fields = split(/,/,$entry);

  $record = "\"$fields[0]\",\"$fields[1]\",\"$fields[2]\",\"$fields[3]\",\"$fields[4]\",$fields[6]";

  return $record;
}

###########################################################################################
# Convert an csv line to a phone schedule record
###########################################################################################

# Csv format:	
#  print "\"Key\",\"Start Time\",\"Stop Time\",\"Creation Time\",\"Alarm Code\",\"Event Name\"\n";

# Phone format
# at#pishw=<storage location,0-19>,
#           <start time timestamp>,
#           <stop time timestamp>,
#           <event entry time stamp>,
#           <when to alarm,0=noalarm,1=ontime,2=10min,3=30min,4=1hr>,
#           "event name 32 chars or less"
# timestamps: YYYYMMDDTHHMMSS

sub csv_to_sched {
  
  # snag line
  $entry = <STDIN>;
  if ($entry =~ /^"Key","Start Time",.*/) {  # skip header line
    $entry = <STDIN>;
  }
  chomp($entry);
 
  $entry =~ s/"//g;  # kill quotes from all fields
  @fields = split(/,/,$entry);

  if ($fields[0] eq "") {
    return "";
  }

  # Create it now by default
  if ($fields[3] eq "") {
    $fields[3] = u2s_time(time());
  } 
  
  $event = "$fields[0],$fields[1],$fields[2],$fields[3],$fields[4],\"$fields[5]\"";

  return $event;
}

#################################
# Todo list conversion routines #
#################################

###########################################################################################
# Convert a phone todo item to vcalendar format
###########################################################################################
# FIXME: timezone handling could be better...

sub todo_to_vcard {

  my ($entry) = @_;
  
  @fields = split(/,/,$entry);	# Split entry into array
  $fields[5] =~ s/"//g;		# Strip quote marks from event name
  $record = "BEGIN:VTODO\n";
  $record .= "UID:$fields[0]\n";
  $record .= "PRIORITY:$fields[1]\n";  # priority
  $record .= "DUE:$fields[2]\n";
  $record .= "LAST-MODIFIED:$fields[3]\n";
  $record .= "SUMMARY:$fields[5]\n";
  $record .= "END:VTODO";    # no final \n because opt-gett does that for us

  return $record;
}

###########################################################################################
# Convert a vcalendar todo item to phone format
###########################################################################################

sub todo_from_vcard {
  # Suck in the whole vcard record
  # NOTE: [\r\n] to take care of DOS format file (double check this...)
  $vcard = "";
  while ( !eof(STDIN) && $vcard !~ /END:VTODO/i ) {
    $thisline = <STDIN>;
    #if ($thisline eq "END:VCALENDAR\n") {
    #  return "";
    #}
    $vcard .= $thisline;
  }

  $record = "";

  # Get key
  if ($vcard =~ /UID:(.+)[\r\n]/mi) { 
    $record .= "$1,";
  } else {
    return "";
  }
  # Get priority
  if ($vcard =~ /PRIORITY:(.+)[\r\n]/mi) {
    $priority = $1;
    if ($priority ne "0") {
      $priority = "1";
    }
  } else {
    $priority = "0";
  }
  $record .= "$priority,";
  #Get due date
  if ($vcard =~ /DUE:(.+)[\r\n]/mi) {
    $due = $1;
  } else {
    $due = u2s_time(time());    # Default due date of now
  }
  $record .= "$due,";
  # Get modification date
  if ($vcard =~ /LAST-MODIFIED:(.+)[\r\n]/mi) {
    $modtime = $1;
  } else {
    $modtime = u2s_time(time());  # default modification time of now
  }
  $record .= "$modtime,";
  # Get todo item 
  $vcard =~ /SUMMARY:(.+)[\r\n]/mi;
  $record .= "\"$1\"\n";

  return $record;
}

###########################################################################################
# Convert a phone todo item to an csv todo item
###########################################################################################

# Csv format:	
#  print "\Key\",\"Urgent\",\"Due Date\",\"Creation Date\",\"Task Name\"\n";

# Phone format
# at#pitdw=<storage location,0-19>,
#          <priority: 0=low,1=high>,
#          <due date timestamp>,
#          <event entry time stamp>,
#          "todo name 32 chars or less"
# timestamps: YYYYMMDDTHHMMSS

sub todo_to_csv {
  my ($entry) = @_;
  
  @fields = split(/,/,$entry);

  $record = "\"$fields[0]\",\"$fields[1]\",\"$fields[2]\",\"$fields[3]\",$fields[5]";

  return $record;
}

###########################################################################################
# convert a csv todo item to a phone todo item
###########################################################################################

# Csv format:	
#  print "\Key\",\"Urgent\",\"Due Date\",\"Creation Date\",\"Task Name\"\n";

# Phone format
# at#pitdw=<storage location,0-19>,
#          <priority: 0=low,1=high>,
#          <due date timestamp>,
#          <event entry time stamp>,
#          "todo name 32 chars or less"
# timestamps: YYYYMMDDTHHMMSS

sub csv_to_todo {
  # snag line
  $entry = <STDIN>;
  if ($entry =~ /^"Key","Urgent",.*/) {   # skip header line
    $entry = <STDIN>;
  }
  chomp($entry);

  $entry =~ s/"//g;  # kill quotes from all fields 
  @fields = split(/,/,$entry);

  if ($fields[0] eq "") {
    return "";
  }

  # create it now if no creation time stamp
  if ($fields[3] eq "") {
    $fields[3] = u2s_time(time());
  } 

  $item = "$fields[0],$fields[1],$fields[2],$fields[3],\"$fields[4]\"\n";

  return $item;
}

###########################
# Misc helper function    #
###########################

###########################################################################################
# Converts a number from the all numbers into readable organizer forms:
# ####
# x#-####
# ###-####
# ###-####x##
# ###-###-####
# ###-###-####x#####
###########################################################################################

sub beautify_number {
  my ($number) = @_;
	
  $length = length($number);
  
  if ($length <= 4) {
    $num = $number;
  } elsif ($length <= 7) {
    $num = "x" .  substr($number,0,$length-4) . "-" . substr($number,$length-4);
  } elsif ($length == 7) { 
    $num = substr($number,0,3) . "-" . substr($number,3);
  } elsif ($length < 10) {
    $num = substr($number,0,3) . "-" . substr($number,3,4) . "x" . substr($number,7);
  } elsif ($length == 10) {
    $num = substr($number,0,3) . "-" . substr($number,3,3) . "-" . substr($number,6);
  } else {     # ($length > 10) {
    $num = substr($number,0,3) . "-" . substr($number,3,3) . "-" . substr($number,6,4) . "x" . substr($number,10);
  }

  return $num;
}

####################################################################################
# convert unix time to samsung time
####################################################################################

sub u2s_time {
  my ($utime) = @_;
  @utime = localtime($utime);
  $stime = sprintf("%d%02d%02dT%02d%02d%02d",$utime[5]+1900,$utime[4]+1,$utime[3],$utime[2],$utime[1],$utime[0]);
  return $stime;
}

###########################################################################################
# If user does something silly show usage and return
###########################################################################################

sub Show_Usage {
  print "\nSamBRU v.$version - Backup and Restore Utility for a310 Samsung cell phones\n";
  print "Usage: sambru-a310.pl <options>\n";
  print "\n";
  print "options:\n";
  print "   --getp                    get phone numbers from phone\n";
  print "   --putp                    put phone numbers into phone\n";
  print "   --gets                    get schedule from phone\n";
  print "   --puts                    put schedule into phone\n";
  print "   --gett                    get todo list from phone\n";
  print "   --putt                    put todo list into phone\n";
  print "   --format <raw,vcard,csv>  format of input or output data       (default raw)\n";
  print "   --file <filename>         filename for data in/output (default STDIN/STDOUT)\n";
if ($WINDOWS) {
  print "   --port <COM port>         port to which phone is attached     (default COM2)\n";
}
if ($LINUX) {
  print "   --port <device>           device to which phone is attached (default /dev/ttyS0)\n";
}
  print "   --help                    show usage\n";
  die "\n";
}

###########################################################################################
# Check the phone book entry before uploading to the phone
###########################################################################################

sub check_entry {
  my (@fields) = @_;

  # AT#PBOKR=<actual location>,
  #	<speed dial>,
  #	<group:0=friend,1=family,2=collegue,3=vip,4=noname>,
  #	<ringer:0=normal,1=bell1,...21=melody16>,
  #	"12 Char Name",
  #	<whichnum2speeddial:0=home,1=office,2=mobile,etc>,
  #	<secret 0=not, 1=is, same for all>,<32 digit home>,
  #	<secret>,<32 digit office>,
  #	<secret>,<32 digit mobile>,
  #	<secret>,<32 digit pager>,
  #	<secret>,<32 digit fax>,
  #	<secret>,<32 digit other>,
  #	<secret>,
  #	,
  #	,
  #	"48 char email address",
  #	<timestamp>
  
  # Basic rules:
  # 0th field: 0 to create a new entry, 1-500 to erase that entry, but no other fields
  # 1st field: >= 1 and <= max_entries
  # 2nd field: >= 1 and <= max_entries   
  # 3: >= 0 && <= 4
  # 4: >= 0 && <= 21
  # 5: quoted chars, up to 12 chars
  # 6: >= 0 && <= 5, must also have a phone number in that slot
  # 7: whether entry is secret or not (0 or 1)
  # 7,9,11,13,15,17,19: must all be 0 or 1 unless previous field is empty
  # 8,10,12,14,16,18: max 32 characters of [0123456789*#pT] 
  # 20,21: must be empty
  # 22: quoted chars, up to 48 in length, warn if no @
  # 23: timestamp: /20[0-9][0-9][0-1][0-9][0-3][0-9]T[0-2][0-9][0-5][0-9][0-5][0-9]/
  
  # allow user to erase a field if desired
  if (($#fields == 0) && ($fields[0]>=$MIN_ENTRIES) && ($fields[0]<=$MAX_ENTRIES) && ($ALLOW_ENTRY_ERASE) && ($fields[0] =~ /^\d+$/))  {
    return 0;
  }  

  # Check number of fields
  if ($#fields > 23 ) {
    print "Error on entry $fields[1]:\tToo many fields ($#fields)\n";
    return 1;
  }
  # Check insert tag
  if ($fields[0] ne "0") {
    print "Error on entry $fields[1]:\tInsert tag != 0 ($fields[0])\n";
    return 1;
  }
  # Check entry number
  if (($fields[1] < $MIN_ENTRIES) || ($fields[1] > $MAX_ENTRIES) || ($fields[1] !~ /^\d+$/)) {
    print "Error on entry $fields[1]:\tInvalid location ($fields[1])\n";
    return 1;
  }
  # Check speed dial
  if (($fields[2] < $MIN_ENTRIES) || ($fields[2] > $MAX_ENTRIES) || ($fields[2] !~ /^\d+$/)) {
    print "Error on entry $fields[1]:\tInvalid speeddial ($fields[2])\n";
    return 1;
  }
  # Check group
  if (($fields[3] < 0) || ($fields[3] > 4) || ($fields[4] !~ /^\d+$/)) {
    print "Error on entry $fields[1]:\tInvalid group ($fields[3])\n";
    return 1;
  }
  # Check ringer
  if (($fields[4] < 0) || ($fields[4] > $MAX_RINGER_NUMBER) || ($fields[4] !~ /^\d+$/)) {
    print "Error on entry $fields[1]:\tInvalid ringer ($fields[4])\n";
    return 1;
  }
  # Check name entry for quotes
  if ($fields[5] !~ /^\".*\"$/) {
    print "Error on entry $fields[1]:\tName must be quoted\n";
    return 1;
  }
  # Check name entry for length - SHOULDN'T WE JUST SHORTEN IT?
  if (length($fields[5]) > $MAX_NAME_LENGTH+2) {
    print "Error on entry $fields[1]:\tName too long ($fields[5])\n";
    return 1;
  }
  # Check which to speeddial
  if (($fields[6] < 0) || ($fields[6] > 5) || ($fields[6] !~ /^\d+$/)) {
    print "Error on entry $fields[1]:\tInvalid speedial location ($fields[6])\n";
    return 1;
  }
  #if ($fields[$fields[6]*2+9] eq "") {
  #  print "Error on entry $fields[1]:\tNo number at speedial location ($fields[6])\n";
  #  return 1;
  #}
  # check secret tags
  if ($fields[7] !~ /0|1/) {
    print "Error on entry $fields[1]:\tInvalid secret field 7 ($fields[$i]).\n";
    return 1;
  }
  for ($i = 9; $i <= 19; $i += 2) {
    if ((($fields[$i-1]) && ($fields[$i] !~ /0|1/)) || (($fields[$i-1] eq "") && ($fields[$i] ne ""))) {
      print "Error on entry $fields[1]:\tInvalid secret field $i ($fields[$i]).\n";
      return 1;
    }
  }
  # check phone numbers
  for ($i = 8; $i <= 18; $i+=2) {
    # Check phone number is valid
    if ((length($fields[$i]) > $MAX_NUMBER_LENGTH) || ($fields[$i] !~ /(|^[\d\*\#pT]+$)/)) {
      print "Error on entry $fields[1]:\tInvalid phone number ($fields[$i]))\n";
      return 1;
    }			
  }
  # check blank fields   FIXME: this should be a voice dial field?
  if ($fields[20] ne "") {
    print "Error on entry $fields[1]:\tNon-blank field ($fields[20]))\n";
    return 1;
  }
  if ($fields[21] ne "") {    # FIXME: this should be a voice dial field?
    print "Error on entry $fields[1]:\tNon-blank field ($fields[21]))\n";
    return 1;
  }
  # Check email field for quotes
  if ($fields[22] !~ /^\".*\"$/) {
    print "Error on entry $fields[1]:\tEmail must be quoted\n";
    return 1;
  }
  # Check email field for length - FIXME: SHOULDN'T WE JUST SHORTEN IT?
  if (length($fields[22]) > $MAX_EMAIL_LENGTH+2) {
    print "Error on entry $fields[1]:\tEmail too long ($fields[22])\n";
    return 1;
  }
  # check timestamp
  if ($fields[23] !~ /^20[0-9][0-9][0-1][0-9][0-3][0-9]T[0-2][0-9][0-5][0-9][0-5][0-9]$/) {
    print "Error on entry $fields[1]:\tInvalid timestamp ($fields[23])\n";
    return 1;
  }

  # if we made it to here everything looks good
  return 0;
}

###########################################################################################
# Check the schedule information before uploading to the phone
###########################################################################################

sub check_sched {
  my (@fields) = @_;

  # at#pishw=<storage location,0-19>,
  #          <start time timestamp>,
  #          <stop time timestamp>,
  #          <event entry time stamp>,
  #          <when to alarm,0=noalarm,1=ontime,2=10min,3=30min,4=1hr>,
  #          "event name 32 chars or less"

  # allow user to erase a field if desired
  if (($#fields == 0) && ($fields[0]>=$MIN_SCHED) && ($fields[0]<=$MAX_SCHED) && ($ALLOW_SCHED_ERASE) && ($fields[0] =~ /^\d+$/))  {
    return 0;
  }

  # Check entry number
  if (($fields[0] < $MIN_SCHED) || ($fields[0] > $MAX_SCHED) || ($fields[0] !~ /^\d+$/ )) {
    print "Error on schedule item:\tInvalid location ($fields[0])\n";
    return 1;
  }
  # Check number of fields
  if ($#fields > 5) {
    print "Error on entry $fields[0]:\tToo many fields ($#fields)\n";
    return 1;
  }
  # Check start time field  -- FIXME: y2100 problem right here
  if ($fields[1] !~ /^20[0-9][0-9][0-1][0-9][0-3][0-9]T[0-2][0-9][0-5][0-9][0-5][0-9]$/) {
    print "Error on entry $fields[0]:\tInvalid start time ($fields[1])\n";
    return 1;
  }
  # Check end time field	-- FIXME: y2100 problem right here
  if ($fields[2] !~ /^20[0-9][0-9][0-1][0-9][0-3][0-9]T[0-2][0-9][0-5][0-9][0-5][0-9]$/) {
    print "Error on entry $fields[0]:\tInvalid end time ($fields[2])\n";
    return 1;
  }
  # check end time is after start time
  if (!((substr($fields[2],0,8) > substr($fields[1],0,8)) || ((substr($fields[2],0,8)==substr($fields[1],0,8)) && (substr($fields[2],9,6)>=substr($fields[1],9,6))))) {
    print "Error on entry $field[0]:\tEnd time not after start time ($fields[2]<$fields[1])\n";
    return 1;
  }
  # check entry time stamp
  if ($fields[3] !~ /^(19|20)[0-9][0-9][0-1][0-9][0-3][0-9]T[0-2][0-9][0-5][0-9][0-5][0-9]$/) {
    print "Error on entry $field[3]:\tTime stamp not valid ($fields[3])\n";
    return 1;
  }
  # Check alarm time type
  if (($fields[4] > 4) || ($fields[4] < 0) || ($fields[4] !~ /^\d+$/)) {
    print "Error on entry $fields[0]:\tInvalid alarm time ($fields[4])\n";
    return 1; 
  }
  # Check event subject for quotes
  if ($fields[5] !~ /^\".*\"$/) {
    print "Error on entry $fields[0]:\tName must be quoted ($fields[4])\n"; 
    return 1; 
  }
  # Check name entry for length - SHOULDN'T WE JUST SHORTEN IT?
  if (length($fields[5]) > $MAX_SCHED_LENGTH+2) { 
    print "Error on entry $fields[0]:\tName too long ($fields[4])\n"; 
    return 1; 
  }
  
  return 0; 
}


###########################################################################################
# Check the todo list record before uploading to the phone
###########################################################################################

# at#pitdw=<storage location,0-19>,
#          <priority: 0=low,1=high>,
#          <due date timestamp>,
#          <event entry time stamp>,
#          "todo name 32 chars or less"
# timestamps: YYYYMMDDTHHMMSS

sub check_todo {
  my (@fields) = @_;

  # allow user to erase a field if desired
  if (($#fields == 0) && ($fields[0]>=$MIN_TODO) && ($fields[0]<=$MAX_TODO) && ($ALLOW_TODO_ERASE))  {
    return 0;
  }

  # Check entry number
  if (($fields[0] < $MIN_TODO) || ($fields[0] > $MAX_TODO) || ($fields[0] !~ /^\d+$/ )) {
    print "Error on todo item:\tInvalid location ($fields[0])\n";
    return 1;
  }
  # Check number of fields
  if ($#fields > 4) {
    print "Error on entry $fields[0]:\tToo many fields ($#fields)\n";
    return 1;
  }
  # Check priority is 0 or 1
  if (!(($fields[1] eq "0") || ($fields[1] eq "1"))) {
    print "Error on entry $fields[0]:\tInvalid priority ($fields[1])\n";
    return 1;
  }
  # Check start time field  -- FIXME: y2100 problem right here
  if ($fields[2] !~ /^20[0-9][0-9][0-1][0-9][0-3][0-9]T[0-2][0-9][0-5][0-9][0-5][0-9]$/) {
    print "Error on entry $fields[0]:\tInvalid due date ($fields[2])\n";
    return 1;
  }
  # check entry time stamp  -- FIXME: y2100 problem
  if ($fields[3] !~ /^(19|20)[0-9][0-9][0-1][0-9][0-3][0-9]T[0-2][0-9][0-5][0-9][0-5][0-9]$/) {
    print "Error on entry $field[3]:\tTime stamp not valid ($fields[3])\n";
    return 1;
  }
  # Check todo item for quotes
  if ($fields[4] !~ /^\".*\"$/) {
    print "Error on entry $fields[0]:\tName must be quoted ($fields[4])\n"; 
    return 1; 
  }
  # Check name entry for length - SHOULDN'T WE JUST SHORTEN IT?
  if (length($fields[4]) > $MAX_TODO_LENGTH+2) { 
    print "Error on entry $fields[0]:\tName too long ($fields[4])\n"; 
    return 1; 
  }
  
  return 0; 
}

