#------------------------------------------------------------------------------
#   $Date: 2005/05/21 18:08:02 $
#   RCS: $Id: SyncEngine.pm,v 1.2 2005/05/21 18:08:02 aaronkaplan Exp $
#------------------------------------------------------------------------------

package SyncEngine;

use strict;

sub new {
  my $type = shift;
  my $self = {};

  bless($self, $type);

  $self->{'host'}    = shift;
  $self->{'localDB'} = shift;
  $self->{'pilotDB'} = shift;
  
  $self->{'last-time'} = 0;

  $self;
}

sub start {
  my $self = shift;

#    print "Info: " . 
#  	 $self->{'pilotDB'}->lastSyncPC() . " - " .
#  	 $self->{'host'}->thisPC() . "\n";
#    print "      " . 
#  	 $self->{'pilotDB'}->lastSyncDate() . " - " .
#  	 $self->{'host'}->lastSyncDate() ."\n";

  my $fast = 0;

  if (defined($self->{'localDB'}->lastSyncDate()) &&
      defined($self->{'pilotDB'}->lastSyncDate()) &&
      $self->{'localDB'}->lastSyncDate() eq $self->{'pilotDB'}->lastSyncDate())
  { $fast = 1; }

  if ($fast) {
    $self->{'host'}->output("Doing Fast Sync!\n");
  } else {
    $self->{'host'}->output("Doing Full Sync!\n");
  }

#   $fast = 0;

#   if ($fast) {
#     $self->{'host'}->output("Doing Fast Sync!\n");
#   } else {
#     $self->{'host'}->output("Doing Full Sync!\n");
#   }
    
  ## let them know what sort of sync they will be doing as well
  ## as the other database they will be syncing with..
  $self->{'localDB'}->setup($fast, $self->{'pilotDB'});
  $self->{'pilotDB'}->setup($fast, $self->{'localDB'});

  $self->getLocal();
  $self->getPilot();

  $self->sync();

  $self->{'pilotDB'}->finish();
  $self->{'localDB'}->finish();
}

sub getLocal {
  my $self    = shift;
  my $localDB = $self->{'localDB'};

  $self->{'local-new'} = $localDB->getNew();
  $self->{'local-mod'} = $localDB->getMod();
  $self->{'local-del'} = $localDB->getDel();
}

sub getPilot {
  my $self    = shift;
  my $pilotDB = $self->{'pilotDB'};

  $self->{'pilot-new'} = $pilotDB->getNew();
  $self->{'pilot-mod'} = $pilotDB->getMod();
  $self->{'pilot-del'} = $pilotDB->getDel();
}

sub sync {
  my $self = shift;

  my $host    = $self->{'host'};
  my $localDB = $self->{'localDB'};
  my $pilotDB = $self->{'pilotDB'};

  # build a hash of 'deleted' records.  We may override these if
  # they are also modified on the other device...
  my %lDelHash;
  foreach my $id (@{$self->{'local-del'}}) {
    $lDelHash{$id} = 1;
    $self->update();
  }

  my %pDelHash;
  foreach my $id (@{$self->{'pilot-del'}}) {
    $pDelHash{$id} = 1;
    $self->update();
  }
  
  my %lModHash;
  foreach my $rec (@{$self->{'local-mod'}}) {
      my @ids = @{$localDB->getPilotIDs($rec)};

      foreach my $id (@ids) {
	  delete $pDelHash{$id}; ## Don't delete this if it was also modified
	  $lModHash{$id} = $rec;
      }

    $self->update();
  }

  my $totalRecs = 0;
  $totalRecs += scalar(@{$self->{'pilot-new'}});
  $totalRecs += scalar(@{$self->{'pilot-mod'}});
  $totalRecs += scalar(@{$self->{'pilot-del'}});

  $totalRecs += scalar(@{$self->{'local-new'}});
  $totalRecs += scalar(@{$self->{'local-mod'}});
  $totalRecs += scalar(@{$self->{'local-del'}});

  my $cnt=0;

  if (scalar @{$self->{'pilot-new'}}) {
    $host->{'prefix'} = "";
    $host->output("Adding Records to " . $localDB->name() . ":\n");

    $host->{'prefix'} = "    ";
    foreach my $rec (@{$self->{'pilot-new'}}) {
      $host->status("Syncing New Records", int(100 * $cnt++/$totalRecs));

      $pilotDB->output($rec);
      $localDB->syncRec($rec);
    }
  }

  if (scalar @{$self->{'local-new'}}) {
    $host->{'prefix'} = "";
    $host->output("Adding Records to " . $pilotDB->name() . ":\n");

    $host->{'prefix'} = "    ";
    foreach my $rec (@{$self->{'local-new'}}) {
      $host->status("Syncing New Records", int(100 * $cnt++/$totalRecs));

      $localDB->output($rec);
      $pilotDB->syncRec($rec);
    }
  }

  $host->{'prefix'} = "ALERT: ";

  if (scalar @{$self->{'pilot-mod'}}) {
    $host->{'prefix'} = "";
    $host->output("Updating Records in " . $localDB->name() .":\n");
    $host->{'prefix'} = "    ";

    foreach my $prec (@{$self->{'pilot-mod'}}) {
      $host->status("Merging Records", int(100 * $cnt++/$totalRecs));

      my $id = $pilotDB->getPilotID($prec);
      my $lrec = $localDB->getRec($id);
      
      delete $lDelHash{$id};	## Don't delete this if it was also modified
      
      if (not defined $lModHash{$id}) {

	## just modified on palm...
	$pilotDB->output($prec);
	$localDB->syncRec($prec, $lrec);
      } else {

	## modified both places, so copy records and update both...
	$host->{'prefix'} = "Duplicating Record: ";
	$pilotDB->output($prec);
	$host->{'prefix'} = "    ";

	# The order and pairing is important here...
	my $lcpy = $localDB->dupRec($lrec, $prec);
	my $pcpy = $pilotDB->dupRec($prec, $lrec, $lcpy);
	
	$localDB->syncRec($pcpy, $lcpy);
	$pilotDB->syncRec($lrec, $prec);

	# Don't Sync for local already done both...
	delete $lModHash{$id};
      }
      
      $self->update();
    }
  }

  if (scalar keys %lModHash) {
    $host->{'prefix'} = "";
    $host->output("Updating Records in " . $pilotDB->name() . ":\n");
    $host->{'prefix'} = "    ";

    foreach my $id (keys %lModHash) {
      $host->status("Merging Records", int(100 * $cnt++/$totalRecs));

      my $lrec = $localDB->getRec($id);
      my $prec = $pilotDB->getRec($id);

      # This can happen if two records are merged.
      # this probably means I need to be more careful to
      # only do one operation per local Rec...
      next if (!defined $lrec);

      $localDB->output($lrec);
      
      ## just modified locally...
      $pilotDB->syncRec($lrec, $prec);
    }
  }

  if (scalar keys %pDelHash) {
    $host->{'prefix'} = "";
    $host->output("Deleting Records from " . $localDB->name() . ":\n");
    $host->{'prefix'} = "    ";

    foreach my $id (keys %pDelHash) {
      $host->status("Deleting Records", int(100 * $cnt++/$totalRecs));

      my $rec = $localDB->getRec($id);
      if (defined $rec) 
	{
	  $localDB->output($rec);
	  $localDB->deleteRec($id);
	}
    }
  }

  if (scalar keys %lDelHash) {
    $host->{'prefix'} = "";
    $host->output("Deleting Records from " . $pilotDB->name() . ":\n");
    $host->{'prefix'} = "    ";

    foreach my $id (keys %lDelHash) {
      $host->status("Deleting Records", int(100 * $cnt++/$totalRecs));

      my $rec = $pilotDB->getRec($id);
      if (defined $rec) 
	{
	  $pilotDB->output($rec);
	  $pilotDB->deleteRec($id);
	}
    }
  }

  $host->status("Update Completed", 100);
}

sub dump {
  my $self = shift;

  my $host    = $self->{'host'};
  my $localDB = $self->{'localDB'};
  my $pilotDB = $self->{'pilotDB'};

  $host->output("New Local\n");
  my @lnew = @{$self->{'local-new'}};
  foreach my $rec (@lnew) {
    $localDB->output($rec);
  }

  $host->output("Modified Local\n");
  my @lmod = @{$self->{'local-mod'}};
  foreach my $rec (@lmod) {
    $localDB->output($rec);
  }
  
  $host->output("Deleted Local\n");
  my @ldel = @{$self->{'local-del'}};
  foreach my $id (@ldel) {
    my $rec = $pilotDB->getRec($id);
    if (defined $rec) {
      $pilotDB->output($rec);
    } else {
      $host->output("ID: $id\n");
    }
  }

  # Pilot stufff

  $host->output("New Pilot:\n");
  my @pnew = @{$self->{'pilot-new'}};
  foreach my $rec (@pnew)	{
    $pilotDB->output($rec);
  }

  $host->output( "Mod Pilot:\n");
  my @pmod = @{$self->{'pilot-mod'}};
  foreach my $rec (@pmod)	{
    $pilotDB->output($rec);
  }
  
  $host->output( "Del Pilot:\n");
  my @pdel = @{$self->{'pilot-del'}};
  foreach my $id (@pdel)	{
    my $rec = $localDB->getRec($id);
    if (defined $rec) {
      $localDB->output($rec);
    } else {
      $host->output("ID: $id\n");
    }
  }
}

sub update {
  my $self = shift;
  my $ctime = time();
  # at least once a second give host some time...
  if ($self->{'last-time'} != $ctime)
    {
      $self->{'host'}->update();
      $self->{'last-time'} = $ctime;
    }
}

1;
