#------------------------------------------------------------------------------
#   $Date: 2006/05/01 19:40:48 $
#   RCS: $Id: localBBDB.pm,v 1.3 2006/05/01 19:40:48 aaronkaplan Exp $
#------------------------------------------------------------------------------

package localBBDB;

use strict;
use POSIX qw(strftime);

use bbdb::bbdb;
use bbdb::bbdbRecord;

use SyncUtil::localHashDB;
use SyncBBDB::pilotBBDBMap;

@localBBDB::ISA = qw(localHashDB);

## Phone number stuff...
my $parea = '1?\s*[-.\(]?\s*([2-9][0-9][0-9])[-.\)\s]+';
my $pmain = '([1-9][0-9][0-9])[-.\s]*([0-9][0-9][0-9][0-9])';
my $pext  = '[-.x\s]+(\d+)';

my $phoneRE1 = "^\\s*" . $parea . $pmain . $pext . "\\s*\$";
my $phoneRE2 = "^\\s*" . $parea . $pmain . "\\s*\$";
my $phoneRE3 = "^\\s*" . $pmain . $pext  . "\\s*\$";
my $phoneRE4 = "^\\s*" . $pmain . "\\s*\$";
my $phoneRE5 = "^[-0-9 EeXxtT.()+]*\$";


sub new {
    my $type = shift;
    my $self = localHashDB->new(shift, shift);

    bless ($self, $type);

    $self->{'bbdbFile'}  = shift;
    $self->{'translate'} = shift;
    $self->{'no-hash-fields'} = ['timestamp'];
    return $self;
}

sub dupRec
{
  my $self = shift;
  my $lrec  = shift;
  my $prec  = shift;

  my $ret = new bbdbRecord($lrec->ePStr());
  $ret->{'notes'}{'creation-date'} = strftime("%Y-%m-%d", localtime);
  delete $ret->{'notes'}{'pilot-id'};  ## disassociate from any pilot recs

  push(@{$self->{'BBDB'}->{'records'}},  $ret);
  push(@{$self->{'BBDB'}->{'sync recs'}},$ret);

  return $ret;
}

sub finish
{
  my $self = shift;

  $self->localHashDB::finish();

  my $file = new FileHandle(">$self->{'bbdbFile'}");
  $self->{'BBDB'}->eprint($file);
  undef $file;
}

sub name
{
  my $self = shift;
  return "Emacs BBDB";
}

sub setup {
  my $self = shift;

  # This should give us a rough idea of the number of records;
  my $nids = $self->{'hashObj'}->getNumIds();
  $nids = 100 if ($nids == 0); # Make sure this isn't 0

  $self->{'in bbdb read'}  = $nids;
  $self->{'in bbdb count'} = 0;

  $self->{'BBDB'} = new bbdb($self->{'bbdbFile'}, $self);

  if ($self->{'in bbdb count'} eq 0) {
      if (! -f $self->{'bbdbFile'}) {
	  ### FIXX: should probably ask the user if he wants to proceed...
	  $self->getHost()->output("BBDB file not found: ".
				   $self->{'bbdbFile'}."\n");
      }
      else {
	  ### FIXX: should probably ask the user if he wants to proceed...
	  $self->getHost()->output("BBDB file was found but is empty: ".
				   $self->{'bbdbFile'}."\n");
      }
  }

  delete $self->{'in bbdb read'};
  delete $self->{'in bbdb count'};

  $self->localHashDB::setup(shift, shift);
}

sub getRecords {
  my $self = shift;
  if (!defined $self->{'sync recs'}) {
      $self->{'sync recs'} = [];
      foreach my $rec (@{$self->{'BBDB'}->{'records'}}) {
	  # Skip adding the record if I'm supposed to ignore it.
	  push(@{$self->{'sync recs'}}, $rec)
	      unless (defined $rec->{'notes'}{'pilot-ignore'});
      }
  }

  return $self->{'sync recs'};
}

sub getPilotIDs {
    my $self = shift;
    my $lrec  = shift;

    my $map = new pilotBBDBMap($lrec, $self->{'translate'});
    my @ret;
    foreach my $rec (@{$map->{'records'}}) {
	push(@ret, $rec->{'id'});
    }

    return \@ret;
}

sub deleteRec {
  my $self = shift;
  my $id   = shift;

  my $rec  = $self->{'hash'}{$id};
  return if (not defined $rec);

  # clear out the hash map entries...
  $self->{'hashObj'}->setLocalHash($id, undef);
  $self->{'hashObj'}->setPilotHash($id, undef);

  my $map  = new pilotBBDBMap($rec, $self->{'translate'});
  my $mrec = $map->delMapForID($id);
  if (scalar(@{$map->{'records'}})) {
      # Other mappings still exist so don't delete the record.
      $rec->{'notes'}{'pilot-id'} = $map->toString();
      return $rec;
  }

  # Delete the record from the list to be synced.
  foreach my $r (@{$self->getRecords()}) {
    if ((defined $r) && ($r == $rec)) {
      $r = undef;
      last;
    }
  }

  # really delete the record from BBDB.
  foreach my $r (@{$self->{'BBDB'}->{'records'}}) {
    if ((defined $r) && ($r == $rec)) {
      $r = undef;
      last;
    }
  }


  return $rec;
}

sub syncRec
{
  my $self = shift;
  my $prec = shift;
  my $lrec = shift;

  my $pilotdb = $self->{'pilotDB'};

  my $is_new_rec = 0;
  if (not defined $lrec) {
      $lrec = new bbdbRecord();
      $is_new_rec = 1;
      push(@{$self->{'BBDB'}->{'records'}},  $lrec);
      push(@{$self->{'BBDB'}->{'sync recs'}},$lrec);
  }

  my $map = new pilotBBDBMap($lrec, $self->{'translate'});

  my $mapRec = $map->getMapForID($prec->{'id'});
  if (!defined $mapRec) {
      # new Pilot Record, need a new mapping record...
      $mapRec = new pilotBBDBRecMap($prec->{'id'},
				    $pilotdb->categoryStr($prec),
				    "Main");
      push(@{$map->{'records'}}, $mapRec);
  }

  $map->updateMappingFromPilot($pilotdb);

  # The fields that look like they are new at this point, which means
  # they exist in bbdb but don't have any mappings to the palm or
  # as 'skipped' fields, are the fields that were deleted on the palm.
  my %toDel = %{$map->getNewBBDBFields()};
  foreach my $f (keys %toDel) {
      $f =~ m/([^:]+):(.*)/;
      my $type = $1;
      my $name = $2;

      # for net and AKA I just skip deleting them since it is
      # particularly unclear if the intent is to remove the
      # mapping for the palm records.
      if ($type eq "addr") {
	  $lrec->del_addr($name);
      } elsif ($type eq "phone") {
	  $lrec->del_phone($name);
      } elsif ($type eq "note" && $name ne 'notes') {
	  delete $lrec->{'notes'}{$name};
      }
  }

  my $lname = $pilotdb->getField($prec, 'Last');
  $lrec->{'lname'} = $lname if ($lname);
  my $fname = $pilotdb->getField($prec, 'First');
  $lrec->{'fname'} = $fname if ($fname);
  my $org = $pilotdb->getField($prec, 'Company');
  $lrec->{'org'}   = $org if ($org);

  my $title = $pilotdb->getField($prec, 'Title');
  $lrec->{'notes'}{'title'} = $title if ($title);

  my $notes = $pilotdb->getField($prec, 'Note');
  $lrec->{'notes'}{'notes'} = $notes if ($notes);

  foreach $mapRec (@{$map->{'records'}}) {
      $prec = $pilotdb->getRec($mapRec->{'id'});

      $self->{'hashObj'}->setLocalHash($mapRec->{'id'}, undef);

      $mapRec->{'category'} = $pilotdb->categoryStr($prec);

      my $addr;
      if (defined $mapRec->{'name'}) {
	  $addr = $lrec->{'addrH'}{$mapRec->{'name'}};
	  if (not defined $addr) {
	      $addr = new bbdbAddr("nil");
	      $addr->{'name'}    = $mapRec->{'name'};
	  }
      } else {
	  $addr = new bbdbAddr("nil");
	  $addr->{'name'} = "Main";
      }

      my $paddr = $pilotdb->getField($prec, 'Address');
      my @streets = split("\xA", $paddr) if ($paddr);

      $addr->{'street'} = \@streets;
      $addr->{'city'}    = $pilotdb->getField($prec, 'City');
      $addr->{'state'}   = $pilotdb->getField($prec, 'State');

      my $zip            = $pilotdb->getField($prec, 'Zip');
      my @zips;

      if ($zip) {
	  if ($self->{'BBDB'}{'version'} ge 6) {
	      # Ok for version 6 and up we can just slam the zip in
	      # we store it as the first element of an array...
	      push(@zips, $zip);
	  } elsif ($zip =~ m/^[-0-9\s]*$/) {
	      # US Zip???
	      @zips = split(/[-\s]+/,$zip);
	  } else {
	      # intl zip.
	      @zips = split(/[\s]+/,$zip);
	      if (scalar(@zips) < 2) {
		  # BBDB requires at least two fields for string rep of zip.
		  # So we will push on an empty string.... (UHGG!)
		  push(@zips,"");
	      }
	  }
      }
      $addr->{'zip'} = \@zips;
      $addr->{'country'}   = $pilotdb->getField($prec, 'Country');

      # if we have any data push it back to BBDB
      if (scalar(@{$addr->{'street'}}) ||
	  scalar(@{$addr->{'zip'}})    ||
	  $addr->{'city'} || $addr->{'state'} ||$addr->{'country'})
      {
	  $lrec->add_addr($addr);
	  $mapRec->{'name'} = $addr->{'name'};
      } elsif (defined $mapRec->{'name'}) {
	  # deleted addr case...
	  $lrec->del_addr($mapRec->{'name'});

	  # Delete the association with the address info.
	  # After all they didn't delete the Pilot record.
	  delete $mapRec->{'name'};
      }

      foreach my $ent (@{$mapRec->{'mappings'}}) {
	  my $btype = $ent->{'bbdb-type'};
	  my $bname = $ent->{'bbdb-name'};
	  my $pname = $ent->{'pilot'};

	  # print STDERR "Type: $btype\n";

	  my $val = $pilotdb->getField($prec, $pname);
	  if ($btype eq "phone") {
	      ## Phone number.
	      my $phone;
	      if      ($val =~ m/$phoneRE1/) {
		  ## (415) 555-1212 x123
		  $phone = new bbdbPhone("\"$bname\" $1 $2 $3 $4");
	      } elsif ($val =~ m/$phoneRE2/) {
		  ## (415) 555-1212
		  $phone = new bbdbPhone("\"$bname\" $1 $2 $3");
	      } elsif ($val =~ m/$phoneRE3/) {
		  ## 555-1212 x123
		  $phone = new bbdbPhone("\"$bname\"  0 $1 $2 $3");
	      } elsif ($val =~ m/$phoneRE4/) {
		  ## 555-1212
		  $phone = new bbdbPhone("\"$bname\"  0 $1 $2");
	      } else
	      {  ## Anything else (International)
		  $phone = new bbdbPhone("\"$bname\"  \"$val\"");
	      }
	      $lrec->add_phone($phone);
	  } elsif ($btype eq "note") {
	      if ($bname ne 'notes') {
	          $lrec->{'notes'}{$bname} = $val;
	      }
	      # $lrec->{'notes'}{'notes'} was already handled above.
	  } elsif ($btype eq "net") {
	      my @email = @{$lrec->{'email'}};
	      if (!scalar(@email)) {
		  $lrec->{'email'} = [ $val ];
	      } elsif ($email[0] ne $val) {
		  unshift(@{$lrec->{'email'}}, $val);
	      }
	  } elsif ($btype eq "aka") {
	      my @aka = split(/, */,$val);
	      $lrec->{'aka'} = \@aka;
	  } else {
	      print STDERR "Unknown bbdb-type: $btype\n";
	  }
      }
  }

  $lrec->{'notes'}{'timestamp'} = strftime("%Y-%m-%d", localtime);
  $lrec->{'notes'}{'creation-date'} = strftime("%Y-%m-%d", localtime)
      if ($is_new_rec);

  $lrec->{'notes'}{'pilot-id'} = $map->toString();

  # push the changes back to the various pilot records.
  # this will also update the hash values.
  $pilotdb->syncRec($lrec);

  return $lrec;
}

sub toString
{
  my $self = shift;
  my $rec  = shift;

  my %hold;
  foreach my $f (@{$self->{'no-hash-fields'}}) {
      $hold{$f} = $rec->{'notes'}{$f} if (defined $rec->{'notes'}{$f});
      delete $rec->{'notes'}{$f};
  }

  my $str = $rec->ePStr();

  foreach my $f (keys %hold) {
    $rec->{'notes'}{$f} = $hold{$f};
  }

  return $str;
}

sub output
{
  my $self = shift;
  my $rec  = shift;
  my $fn   = $rec->{'fname'};
  my $ln   = $rec->{'lname'};
  my $org  = $rec->{'org'};
  my $str;

  $fn  = "" if !defined($fn);
  $ln  = "" if !defined($ln);
  $org = "" if !defined($org);

  $str = $fn;
  $str .= " "  if ($str && $ln);
  $str .= $ln;
  $str .= ", " if ($str && $org);
  $str .= $org;

  if (!$str)
  {
      $str = "<No Name>";
      if (defined $rec->{'notes'}{'pilot-id'}) {
	  my $map = new pilotBBDBMap($rec, $self->{'translate'});
	  my @ids;
	  foreach my $mrec (@{$map->{'records'}}) {
	      push(@ids, $mrec->{'id'});
	  }
	  $str .= " - " . join(", ", @ids);
      }
  }

  $self->getHost()->output($str . "\n");
}


sub update {
    my $self = shift;
    if (!defined $self->{'in bbdb read'}) {
	return $self->localHashDB::update();
    }

    # we are in the bbdb read so call status instead of 'update'.
    $self->{'host'}->status("Reading BBDB Records",
			    int(100*(($self->{'in bbdb count'}++)/
				     $self->{'in bbdb read'})));
}

1;
