# Originally written by Alan Harder.
# See ChangeLog for list of modifications and contributors.

package SyncFood;

use PilotSync;
use Data::Dumper;
use Tk;

my ($gConfigDlg, $gCancel, $RCFILE);
my $VERSION = $PilotMgr::VERSION;
my $FoodFields = ['name', 'info', 'note',
		  'foodTypeBits', 'locationBits',
		  'hours_open', 'hours_close',
		  'extraBits', 'rating'];

sub conduitInit
{
    $RCFILE = 'SyncFood/SyncFood.prefs';
    &loadPrefs;

    $PREFS->{'foodFile'} = "$ENV{HOME}/.foodDb"
	unless (exists $PREFS->{'foodFile'});
}

sub loadPrefs
{
    $PREFS = {}, return unless (-r "$RCFILE");
    use vars qw($PREFS);
    do "$RCFILE";
}

sub conduitQuit
{
    &savePrefs;
}

sub savePrefs
{
    $Data::Dumper::Purity = 1;
    $Data::Dumper::Deepcopy = 1;
    $Data::Dumper::Indent = 0;

    unless (open(FD, ">$RCFILE"))
    {
	PilotMgr::msg("Unable to save preferences to $RCFILE!");
	return;
    }

    print FD Data::Dumper->Dumpxs([$PREFS], ['PREFS']), "1;\n";
    close FD;
}

sub conduitInfo
{
    return { 'database' =>
		{
		    'name' => 'FoodDB',
		    'creator' => 'FooD',
		    'type' => 'Data',
		    'flags' => 8,	# = dmHdrAttrBackup
		    'version' => 2,
		},
	     'version' => $VERSION,
	     'author' => 'Alan Harder',
	   };
}

sub conduitConfigure
{
    my ($this, $wm) = @_;

    &createConfigDlg($wm) unless (defined $gConfigDlg and $gConfigDlg->Exists);

    $gConfigDlg->Popup(-popanchor => 'c', -overanchor => 'c',
		       -popover => $wm);
}

sub createConfigDlg
{
    my ($wm) = @_;
    my (@frame);

    $gConfigDlg = $wm->Toplevel(-title => 'Configuring SyncFood');
    $gConfigDlg->withdraw;
    $gConfigDlg->transient($wm);

    unshift(@frame, $gConfigDlg);
    unshift(@frame, $frame[0]->Frame(-relief => 'ridge', -bd => 2));

    $frame[0]->Label(-text =>
      "SyncFood v$VERSION\nAlan Harder")
	     ->pack;

    unshift(@frame, $frame[0]->Frame);
    $frame[0]->Label(-text => 'FoodFile:')->pack(-side => 'left');

    $frame[0]->Entry(-relief => 'sunken', -width => 40,
		  -textvariable => \$PREFS->{'foodFile'})
	  ->pack(-fill => 'x', -expand => 1);
    (shift @frame)->pack(-fill => 'x', -expand => 1);

    $frame[0]->Button(-text => 'Dismiss',
		      -command => ['withdraw', $gConfigDlg])->pack;

    (shift @frame)->pack(-fill => 'x', -expand => 1);
    PilotMgr::setColors($gConfigDlg);
}

sub conduitSync
{
    my ($this, $dlp, $info) = @_;

    $gCancel = 0;

    PilotSync::doSync(	$dlp,
			&conduitInfo->{'database'},
			$FoodFields,
			['foodTypes', 'locations', 'extraBools'],
			'food_id',
			'SyncFood/food.db',
			$PREFS->{'foodFile'},
			sub{ return $_[0]->{'name'}; },
			\&readFile,
			\&writeFile,
			\&newFoodId,
			\&translateFood,
			\&translateAppInfo,
			\$gCancel);

}

sub conduitCancel
{
    $gCancel = 'SyncFood Cancelled!';
}

sub translateFood
{
    my ($rec, $dir) = @_;
    my ($txt, $val);

    unless ($dir)
    {
	# raw -> expanded
	return if ($rec->{'deleted'});
	$txt = $rec->{'raw'};

	$val = substr($txt, 0, 2);
	$rec->{'foodTypeBits'} = 256 * ord($val) + ord(substr($val, 1, 1));
	$val = substr($txt, 2, 2);
	$rec->{'locationBits'} = 256 * ord($val) + ord(substr($val, 1, 1));
	$rec->{'extraBits'} = ord(substr($txt, 4, 1));
	$val = ord(substr($txt, 5, 1));
	$val -= 256 if ($val > 127);
	$rec->{'rating'} = $val;
	$rec->{'hours_open'} = [];
	foreach (0..6)
	{
	    $val = substr($txt, 6 + 2 * $_, 2);
	    $val = 256 * ord($val) + ord(substr($val, 1, 1));
	    $val -= 65536 if ($val > 32767);
	    push(@{$rec->{'hours_open'}}, $val);
	}
	$rec->{'hours_close'} = [];
	foreach (0..6)
	{
	    $val = substr($txt, 20 + 2 * $_, 2);
	    $val = 256 * ord($val) + ord(substr($val, 1, 1));
	    $val -= 65536 if ($val > 32767);
	    push(@{$rec->{'hours_close'}}, $val);
	}

	$txt = substr($txt, 34);
	$val = index($txt, "\000");
	$rec->{'name'} = substr($txt, 0, $val);
	$txt = substr($txt, $val+1);
	$val = index($txt, "\000");
	$rec->{'info'} = substr($txt, 0, $val);
	$txt = substr($txt, $val+1);
	$rec->{'note'} = substr($txt, 0, index($txt, "\000"));
    }
    else
    {
	# expanded -> raw
	$rec->{'raw'} = '';
	$val = $rec->{'foodTypeBits'};
	$rec->{'raw'} .= chr($val/256) . chr($val%256);
	$val = $rec->{'locationBits'};
	$rec->{'raw'} .= chr($val/256) . chr($val%256);
	$rec->{'raw'} .= chr($rec->{'extraBits'});
	$val = $rec->{'rating'};
	$val += 256 if ($val < 0);
	$rec->{'raw'} .= chr($val);
	foreach (0..6)
	{
	    $val = $rec->{'hours_open'}->[$_];
	    $val += 65536 if ($val < 0);
	    $rec->{'raw'} .= chr($val/256) . chr($val%256);
	}
	foreach (0..6)
	{
	    $val = $rec->{'hours_close'}->[$_];
	    $val += 65536 if ($val < 0);
	    $rec->{'raw'} .= chr($val/256) . chr($val%256);
	}

	$rec->{'raw'} .= $rec->{'name'} . "\000" .
			 $rec->{'info'} . "\000" .
			 $rec->{'note'} . "\000";
    }
}

sub translateAppInfo
{
    my ($appinfo, $dir) = @_;
    my ($txt, $i, $val, $idx) = ($appinfo->{'raw'});

    unless ($dir)
    {
	# raw -> expanded
	$appinfo->{'foodTypes'} = [];
	$appinfo->{'locations'} = [];
	$appinfo->{'extraBools'} = [];

	for ($i=0; $i < 36; $i++)
	{
	    $val = substr($txt, $i*21, 21);
	    $idx = index($val, "\000");
	    $val = substr($val, 0, $idx) if ($idx >= 0);
	    if ($i < 16)	{ push(@{$appinfo->{'foodTypes'}}, $val); }
	    elsif ($i < 32)	{ push(@{$appinfo->{'locations'}}, $val); }
	    else		{ push(@{$appinfo->{'extraBools'}}, $val); }
	}
    } else {
	# expanded -> raw
	$appinfo->{'raw'} = '';

	for ($i=0; $i < 16; $i++)
	{
	    $appinfo->{'raw'} .= $appinfo->{'foodTypes'}->[$i] . ("\000" x
				 (21 - length($appinfo->{'foodTypes'}->[$i])));
	}
	for ($i=0; $i < 16; $i++)
	{
	    $appinfo->{'raw'} .= $appinfo->{'locations'}->[$i] . ("\000" x
				 (21 - length($appinfo->{'locations'}->[$i])));
	}
	for ($i=0; $i < 4; $i++)
	{
	    $appinfo->{'raw'} .= $appinfo->{'extraBools'}->[$i] . ("\000" x
				 (21- length($appinfo->{'extraBools'}->[$i])));
	}
    }
}

sub readFile
{
    my ($FOODFILE) = @_;
    my ($db, $reclist, $rec, $i) = ({ 'NEXT_ID'=>0,
				      '__RECORDS' => [],
				      '__APPINFO' => {  'foodTypes' => [],
							'locations' => [],
							'extraBools' => [] }});
    $reclist = $db->{'__RECORDS'};


    unless (open(FD, "<$FOODFILE"))
    {
      # Don't do a sync if master data file exists (then we'll end up
      # deleting all records!)
      if (-f "SyncFood/food.db")
      {
          PilotMgr::msg(
              "**ERROR: Unable to open $FOODFILE.  Aborting SyncFood!");
          croak("NODATA");
      }
      return $db;
    } 

    for ($i=0; $i < 16; $i++)
    {
	chomp($rec = <FD>);
	push(@{$db->{'__APPINFO'}->{'foodTypes'}}, $rec);
    }
    for ($i=0; $i < 16; $i++)
    {
	chomp($rec = <FD>);
	push(@{$db->{'__APPINFO'}->{'locations'}}, $rec);
    }
    for ($i=0; $i < 4; $i++)
    {
	chomp($rec = <FD>);
	push(@{$db->{'__APPINFO'}->{'extraBools'}}, $rec);
    }

    while (!eof(FD))
    {
	$rec = {};
	chomp($rec->{food_id} = <FD>);
	$db->{NEXT_ID} = $rec->{food_id}+1
	    if ($rec->{food_id} >= $db->{NEXT_ID});
	$rec->{name} = &readString(FD);
	$rec->{info} = &readString(FD);
	($rec->{foodTypeBits}, $rec->{locationBits},
	 $rec->{extraBits}, $rec->{rating}) =
	    split(',', (chomp($_ = <FD>),$_)[1]);
	$rec->{hours_open} = [split(',', (chomp($_ = <FD>),$_)[1])];
	$rec->{hours_close} = [split(',', (chomp($_ = <FD>),$_)[1])];
	$rec->{note} = &readString(FD);

	push(@$reclist, $rec);
	$db->{$rec->{food_id}} = $#$reclist;
    }
    close(FD);
    return $db;
}

sub newFoodId
{
    my ($db) = @_;

    return $db->{NEXT_ID}++;
}

sub writeFile
{
    my ($FOODFILE, $db) = @_;
    my ($rec, $i);

    unless (open(FD, ">$FOODFILE"))
    {
	PilotMgr::msg("Unable to write to $FOODFILE.  Help!");
	return;
    }

    for ($i=0; $i < 16; $i++)
    {
	print FD $db->{'__APPINFO'}->{'foodTypes'}->[$i], "\n";
    }
    for ($i=0; $i < 16; $i++)
    {
	print FD $db->{'__APPINFO'}->{'locations'}->[$i], "\n";
    }
    for ($i=0; $i < 4; $i++)
    {
	print FD $db->{'__APPINFO'}->{'extraBools'}->[$i], "\n";
    }

    foreach $rec (@{$db->{'__RECORDS'}})
    {
	print FD $rec->{food_id},"\n";
	&writeString(FD, $rec->{name});
	&writeString(FD, $rec->{info});
	print FD (defined($rec->{foodTypeBits}) ? $rec->{foodTypeBits} : 0),
	    ',', (defined($rec->{locationBits}) ? $rec->{locationBits} : 0),
	    ',', (defined($rec->{extraBits}) ? $rec->{extraBits} : 0),
	    ',', (defined($rec->{rating}) ? $rec->{rating} : -1), "\n";
	foreach (0..6)
	{
	    print FD (defined($rec->{hours_open}->[$_]) ?
		$rec->{hours_open}->[$_] : -1);
	    print FD ',' if ($_ < 6);
	}
	print FD "\n";
	foreach (0..6)
	{
	    print FD (defined($rec->{hours_close}->[$_]) ?
		$rec->{hours_close}->[$_] : -1);
	    print FD ',' if ($_ < 6);
	}
	print FD "\n";
	&writeString(FD, $rec->{note});
    }
    close(FD);
}

sub writeString
{
    my ($fd, $str) = @_;
    my ($n);

    if (!defined $str or !length $str)
    {
	print $fd "0\n";
	return;
    }

    $n = ($str =~ tr/\n/\n/) + 1;
    print $fd "$n\n$str\n";
}

sub readString
{
    my ($fd) = @_;
    my ($str, $n, $i) = ("");

    chomp($n = <$fd>);
    return $str unless ($n > 0);

    for ($i = 1; $i < $n; $i++)
    {
	$str .= <$fd>;
    }
    chomp($_ = <$fd>);
    $str .= $_;

    return $str;
}

1;

