#!/usr/bin/perl -w
# Code derived from the C version at <http://www.cryptome.org/vcrpp.c>
# plus improvements
# Yes, this is just translated C code.  A perl guru can make it more perlish.

use Tk;
require Tk::BrowseEntry;

my $g_decyear;
my $g_decmonth;
my $g_decday;
my $g_decpluscode;
my $g_decchannel;
my $g_decchannelname;
my $g_decstart;
my $g_decstartm;
my $g_decduration;
my $g_decdurationm;

my $g_encyear;
my $g_encmonth;
my $g_encday;
my $g_encpluscode;
my $g_encchannel;
my $g_encchannelname;

my $g_today_year;
my $g_today_month;
my $g_today_day;

my $i;

my $NDIGITS=10;

$g_encpluscode = "";
$g_encchannel = "2";
$g_encstart = "1900";
$g_encduration = "30";
$g_decpluscode = "3113";


# The following tables belong in the VCRPLUS work section, but
# are here so they get initialized before getting into main.
# This should get fixed when the work routines become a module.

# First, the two (well, three) tables
# Primary table of 4 * 48 = 192 entries
# Index is "magic" from the pluscode
# Value is 24*2*<hour of start> + (0 or 1) half-hours of start +
# half-hours of duration - 1 [= 0-3]) * 48
# for durations = 30, 60, 90, 120 minutes and starts = any half hour 0000-2330
# Also used as a quasi-random number generator in the 7 and 8 digit codes.

my @g_ttbl = (
 0x25, 0x20, 0x27, 0x21, 0x1F, 0x23, 0x24, 0x1D,
 0x26, 0x52, 0x1C, 0x29, 0x22, 0xB0, 0x28, 0x1E,
 0xB8, 0xBA, 0x58, 0xB4, 0x56, 0x5C, 0x5A, 0xAC,
 0x4E, 0xBC, 0x17, 0x16, 0x2E, 0x50, 0x8A, 0x2A,
 0x19, 0x1B, 0x13, 0x4A, 0x2B, 0x48, 0xA4, 0x54,
 0x2C, 0x18, 0x10, 0x11, 0xB2, 0x12, 0x2D, 0x15,
 0xB6, 0x0F, 0x5E, 0x44, 0x0E, 0x1A, 0x9E, 0x46,
 0x4C, 0x14, 0xA0, 0x2F, 0xAA, 0xA8, 0xA2, 0x0D,
 0x84, 0x0C, 0x0B, 0x00, 0xBF, 0x8C, 0x7A, 0x42,
 0x81, 0x80, 0x7D, 0x88, 0x85, 0x3C, 0x78, 0x01,
 0x93, 0x30, 0x82, 0x90, 0x40, 0x3E, 0xBB, 0x0A,
 0x7F, 0xA7, 0xA6, 0x71, 0x8D, 0x72, 0x8B, 0xB1,
 0x5F, 0x92, 0x7C, 0x03, 0x97, 0x7E, 0xAE, 0xBE,
 0x86, 0x70, 0x09, 0x06, 0xAB, 0x74, 0x6E, 0x02,
 0x8F, 0x07, 0x04, 0xBD, 0x08, 0x9C, 0x98, 0x05,
 0x6D, 0x31, 0x5D, 0x32, 0x91, 0x8E, 0x51, 0x41,
 0x60, 0xB7, 0xA3, 0x89, 0x3A, 0x53, 0x94, 0x87,
 0x73, 0xB3, 0x9D, 0x55, 0x4D, 0x77, 0x61, 0xA1,
 0x75, 0xAD, 0x62, 0x9F, 0xB9, 0x66, 0x96, 0x7B,
 0x79, 0x65, 0x5B, 0x47, 0xB5, 0x3D, 0x3B, 0x34,
 0xAF, 0x3F, 0x6C, 0x83, 0x38, 0x6F, 0x69, 0x39,
 0x63, 0xA9, 0x33, 0x95, 0x57, 0x36, 0xA5, 0x64,
 0x37, 0x9A, 0x43, 0x35, 0x59, 0x68, 0x4F, 0x99,
 0x4B, 0x49, 0x67, 0x45, 0x6A, 0x9B, 0x6B, 0x76
);

# Second table controls 7 and 8 digit pluscodes

my @g_LengthEightTable = (
 0x14, 0x02, 0x23, 0x03, 0x41, 0x14, 0x14, 0x32,
 0x34, 0x03, 0x32, 0x40, 0x10, 0x01, 0x12, 0x32,
 0x40, 0x21, 0x24, 0x41, 0x23, 0x14, 0x23, 0x41,
 0x02, 0x04, 0x30, 0x24, 0x41, 0x40, 0x01
);

my @g_LengthSevenTable = (
 0x03, 0x31, 0x12, 0x12, 0x30, 0x03, 0x03, 0x21,
 0x23, 0x12, 0x21, 0x31, 0x03, 0x30, 0x01, 0x21,
 0x31, 0x10, 0x13, 0x30, 0x12, 0x03, 0x12, 0x30,
 0x31, 0x13, 0x21, 0x13, 0x30, 0x31, 0x30
);

my @inv_ttbl;

# Localize this table with local station names/mapped-channels

my @g_chan_names;
$g_chan_names[ 2] = "WXXA2";
$g_chan_names[ 3] = "WXXB3";
$g_chan_names[ 4] = "WXXC4";
$g_chan_names[ 5] = "WXXD5";
$g_chan_names[ 6] = "WXXE6";
$g_chan_names[ 7] = "WXXF7";
$g_chan_names[ 8] = "WXXG9";
$g_chan_names[ 9] = "WXXH9";
$g_chan_names[10] = "WXXI10";
$g_chan_names[11] = "WXXJ11";
$g_chan_names[12] = "WXXK12";
$g_chan_names[12] = "WXXL13";
$g_chan_names[33] = "HBO";
$g_chan_names[34] = "ESPN";
$g_chan_names[35] = "AMC";
$g_chan_names[37] = "DSC";
$g_chan_names[38] = "NIK";
$g_chan_names[39] = "A&E";
$g_chan_names[41] = "SHO";
$g_chan_names[42] = "CNN";
$g_chan_names[43] = "TBS";
$g_chan_names[44] = "USA";
$g_chan_names[45] = "MAX";
$g_chan_names[46] = "LIF";
$g_chan_names[47] = "FAM";
$g_chan_names[48] = "MTV";
$g_chan_names[49] = "TNN";
$g_chan_names[51] = "TLC";
$g_chan_names[52] = "TNT";
$g_chan_names[53] = "DIS";
$g_chan_names[54] = "BRV";
$g_chan_names[57] = "BET";
$g_chan_names[58] = "TMC";
$g_chan_names[59] = "FSN";
$g_chan_names[62] = "VH1";
$g_chan_names[63] = "E";
$g_chan_names[69] = "H&G";
$g_chan_names[70] = "APL";
$g_chan_names[73] = "HIS";
$g_chan_names[75] = "COM";
$g_chan_names[76] = "TCM";
$g_chan_names[79] = "CNBC";
$g_chan_names[80] = "STARZ";
$g_chan_names[82] = "NEC";
$g_chan_names[85] = "FX";
$g_chan_names[86] = "CRT";
$g_chan_names[89] = "SCI";
$g_chan_names[90] = "MSNBC";
$g_chan_names[91] = "FNC";
$g_chan_names[99] = "QVC";
$g_chan_names[103] ="HAL";
;

# Code starts here

($g_today_year, $g_today_month, $g_today_day) = (localtime) [5,4,3];
$g_today_year += 1900;
$g_today_month += 1;

$g_encyear      = $g_today_year;
$g_encmonth     = $g_today_month;
$g_encday       = $g_today_day;

$g_decyear      = $g_today_year;
$g_decmonth     = $g_today_month;

$mw = MainWindow->new;
$mw->title("VCRplus+ Decoder/Encoder");
$mw->Label(-text =>"VCRplus+ Decoder/Encoder")->pack(
                                                 -side => 'top',
						 -anchor => 'center');;

$mw->Button(-text => "Quit",
    -command => sub {exit})->pack(-side => 'bottom',
                                  -expand => 1,
				  -fill => 'x');

my $df = $mw->Frame(-borderwidth => 2,
                    -relief => 'groove')
	      ->pack(-side => 'left',
		     -anchor => 'n',
	             -padx => 10,
		     -pady => 10);

my $ef = $mw->Frame(-borderwidth => 2,
                    -relief => 'groove')
	      ->pack(-side => 'right',
	             -anchor => 'n',
       		     -padx => 10,
       		     -pady => 10);

# The Decode frame

$df->Label(-text => "Decode from a Pluscode")
           ->pack(-side => "top",
	   -anchor=> 'w');

my $be_d_y = $df->BrowseEntry(
	-label => "Year:",
	-width => 4,
	-variable => \$g_decyear);

foreach $i (1990..2037) {
    $be_d_y->insert('end', "$i");
}

$be_d_y->pack(
    -side => 'top',
    -anchor => 'w');


my $be_d_m = $df->BrowseEntry(
	-label => "Month:",
	-width => 2,
	-variable => \$g_decmonth);

foreach $i (1..12) {
    $be_d_m->insert('end', "$i");
}

$be_d_m->pack(
    -side => 'top',
    -expand => 1,
    -anchor => 'w');



my $df1 = $df->Frame(-borderwidth => 2,
                    -relief => 'groove')
	      ->pack(-side => 'top',
		     -expand => 1,
	             -anchor => 'w');


$df->Button(-text => "Decode",
    -command => \&do_decode_gui )->pack(-side => 'top',
				    -pady => 10);



my $df2 = $df->Frame(-borderwidth => 2,
                    -relief => 'groove')
	      ->pack(-side => 'top',
		     -expand => 1,
	             -anchor => 'w');


my $df3 = $df->Frame(-borderwidth => 2,
                    -relief => 'groove')
	      ->pack(-side => 'top',
		     -expand => 1,
	             -anchor => 'w');


my $df4 = $df->Frame(-borderwidth => 2,
                    -relief => 'groove')
	      ->pack(-side => 'top',
		     -expand => 1,
	             -anchor => 'w');



my $df5 = $df->Frame(-borderwidth => 2,
                    -relief => 'groove')
	      ->pack(-side => 'top',
		     -expand => 1,
	             -anchor => 'w');



my $df6 = $df->Frame(-borderwidth => 2,
                    -relief => 'groove')
	      ->pack(-side => 'top',
		     -expand => 1,
	             -anchor => 'w');


my $lab_d_pc = $df1->Label(
        -text => "PlusCode:")->pack(
	    -side => "left",
	    -expand => 1,
	    -anchor=> 'w');
my $ent_d_pc = $df1->Entry(
	-width => 8,
	-textvariable => \$g_decpluscode)->pack(
	    -side => 'right',
	    -expand => 1,
	    -anchor => 'e');


my $lab_d_ch = $df2->Label(-text => "Channel:")->pack(-side => "left", -anchor=> 'w');
my $ent_d_ch = $df2->Entry(
	-width => 3,
	-textvariable => \$g_decchannel)->pack(
	    -side => 'right',
	    -expand => 1,
	    -anchor => 'e');


my $lab_d_chnam = $df3->Label(-text => "Channel Name:")->pack(-side => "left", -anchor=> 'w');
my $ent_d_chnam = $df3->Entry(
	-width => 8,
	-textvariable => \$g_decchannelname)->pack(
	    -side => 'right',
	    -expand => 1,
	    -anchor => 'e');

my $lab_d_day = $df4->Label(-text => "Day:")->pack(-side => "left", -anchor=> 'w');
my $ent_d_day = $df4->Entry(
	-width => 2,
	-textvariable => \$g_decday)->pack(
	    -side => 'right',
	    -expand => 1,
	    -anchor => 'e');



my $lab_d_start = $df5->Label(-text => "Start (hhmm):")->pack(-side => "left", -anchor=> 'w');
my $ent_d_start = $df5->Entry(
	-width => 4,
	-textvariable => \$g_decstart)->pack(
	    -side => 'right',
	    -expand => 1,
	    -anchor => 'e');

my $lab_d_duration = $df6->Label(-text => "Duration (hmm):")->pack(-side => "left", -anchor=> 'w');
my $ent_d_duration = $df6->Entry(
	-width => 3,
	-textvariable => \$g_decduration)->pack(
	    -side => 'right',
	    -expand => 1,
	    -anchor => 'e');


# The Encode frame

$ef->Label(-text => "Encode to a Pluscode")->pack(-side => "top", -anchor=> 'n');



my $ef1 = $ef->Frame(-borderwidth => 2,
                    -relief => 'groove')
	      ->pack(-side => 'top',
		     -expand => 1,
	             -anchor => 'w');


my $ef2 = $ef->Frame(-borderwidth => 2,
                    -relief => 'groove')
	      ->pack(-side => 'top',
		     -expand => 1,
	             -anchor => 'w');


my $ef3 = $ef->Frame(-borderwidth => 2,
                    -relief => 'groove')
	      ->pack(-side => 'top',
		     -expand => 1,
	             -anchor => 'w');


my $ef4 = $ef->Frame(-borderwidth => 2,
                    -relief => 'groove')
	      ->pack(-side => 'top',
		     -expand => 1,
	             -anchor => 'w');

my $ef5 = $ef->Frame(-borderwidth => 2,
                    -relief => 'groove')
	      ->pack(-side => 'top',
		     -expand => 1,
	             -anchor => 'w');


my $be_e_ch = $ef->BrowseEntry(
	-label => "Channel:",
	-width => 3,
	-variable => \$g_encchannel);

foreach $i (1..127) {
    $be_e_ch->insert('end', "$i");
}

$be_e_ch->pack(
    -side => 'top',
    -anchor => 'w');

$ef->Button(-text => "Encode",
    -command => \&do_encode_gui )->pack(-side => 'top',
				    -pady => 10);

my $ef6 = $ef->Frame(-borderwidth => 2,
                    -relief => 'groove')
	      ->pack(-side => 'top',
		     -expand => 1,
	             -anchor => 'w');


my $lab_e_pc = $ef6->Label(
        -text => "PlusCode:")->pack(
	    -side => "left",
	    -expand => 1,
	    -anchor=> 'w');
my $ent_e_pc = $ef6->Entry(
	-width => 8,
	-textvariable => \$g_encpluscode)->pack(
	    -side => 'right',
	    -expand => 1,
	    -anchor => 'e');

my $be_e_y = $ef1->BrowseEntry(
	-label => "Year:",
	-width => 4,
	-variable => \$g_encyear);

foreach $i (1990..2037) {
    $be_e_y->insert('end', "$i");
}

$be_e_y->pack(
    -side => 'top',
    -anchor => 'w');

my $be_e_m = $ef2->BrowseEntry(
	-label => "Month:",
	-width => 2,
	-variable => \$g_encmonth);

foreach $i (1..12) {
    $be_e_m->insert('end', "$i");
}

$be_e_m->pack(
    -side => 'top',
    -anchor => 'w');

my $be_e_d = $ef3->BrowseEntry(
	-label => "Day:",
	-width => 2,
	-variable => \$g_encday);

foreach $i (1..31) {
    $be_e_d->insert('end', "$i");
}

$be_e_d->pack(
    -side => 'top',
    -anchor => 'w');

my $lab_e_start = $ef4->Label(-text => "Start (hhmm):")->pack(-side => "left", -anchor=> 'w');
my $ent_e_start = $ef4->Entry(
	-width => 4,
	-textvariable => \$g_encstart)->pack(
	    -side => 'right',
	    -expand => 1,
	    -anchor => 'e');

my $lab_e_duration = $ef5->Label(-text => "Duration (hmm):")->pack(-side => "left", -anchor=> 'w');
my $ent_e_duration = $ef5->Entry(
	-width => 3,
	-textvariable => \$g_encduration)->pack(
	    -side => 'right',
	    -expand => 1,
	    -anchor => 'e');

$ent_d_pc->focus;
MainLoop;


# Above is all the GUI stuff.  The VCRPLUS logic follows.


# Here are the real work routines, taking args and returning
# results as per the usual subroutine interface.
# This is what should be split off into a module.

# Glue routines to move args/vals from/to globals

sub do_decode_gui {

    ($g_decday, $g_decstartm, $g_decdurationm, $g_decchannel) =
	vcrpp_decode($g_decyear, $g_decmonth, $g_decpluscode);

    $g_decstart = (100 * (int ($g_decstartm / 60))) +
		     ($g_decstartm % 60);

    $g_decduration = (100 * (int ($g_decdurationm / 60))) +
		     ($g_decdurationm % 60);

# Report name of channel, if known
    $g_decchannelname = ("Ch " . $g_decchannel);
    if ($g_chan_names[$g_decchannel]) {
        $g_decchannelname = $g_chan_names[$g_decchannel];
    }
}

sub do_encode_gui {

    $g_encpluscode =
	vcrpp_encode($g_encyear, $g_encmonth, $g_encday,
	             $g_encstart, $g_encduration, $g_encchannel);
}

#******************************************************************
#* Utility routines
#******************************************************************

sub
count_digits()
{
    my $val;
    my $ndigits;
    ($val) = @_;

    if    ($val < 1) {$ndigits = 0}
    elsif ($val < 10) {$ndigits = 1}
    elsif ($val < 100) {$ndigits = 2}
    elsif ($val < 1000) {$ndigits = 3}
    elsif ($val < 10000) {$ndigits = 4}
    elsif ($val < 100000) {$ndigits = 5}
    elsif ($val < 1000000) {$ndigits = 6}
    elsif ($val < 10000000) {$ndigits = 7}
    else                     {$ndigits = 8};

    $ndigits;
}

sub
set_pwr()
{
    my $ndigits = $_;
    my $pwr = 0x7fffffff;

    if (0 == $ndigits)  {$pwr = 1};
    if (1 == $ndigits)  {$pwr = 10};
    if (2 == $ndigits)  {$pwr = 100};
    if (3 == $ndigits)  {$pwr = 1000}; 
    if (4 == $ndigits)  {$pwr = 10000};
    if (5 == $ndigits)  {$pwr = 100000};
    if (6 == $ndigits)  {$pwr = 10000000};
    if (7 == $ndigits)  {$pwr = 100000000}; 
    if (8 == $ndigits)  {$pwr = 1000000000};
    if (9 == $ndigits)  {$pwr = 10000000000};
    $pwr;
}

# Split the code into an array of digits, lsb at low index

sub
split_digits()
{
    my $n;
    my @a;
    my $aref;

    ($n, $aref) = @_;

    my $i;
    my $digit;;

    for ($i = 0; $i < $NDIGITS; $i++) {
       	$$aref[$i] = 0;
    }

    for ($i = 0; $i < $NDIGITS; $i++) {
	$digit = $n % 10;
	$$aref[$i] = $digit;
	$n = ($n - $digit) / 10;
    }
}

# Inverse of the decoder's initial transform.  For encoder.

sub
encfunc1 ()
{
#input/output
    my $val;

#locals
    my $ndigits;
    my $pwr;

    ($val) = @_;

    $ndigits = 0;
    $pwr = 1;
    while ($val >= $pwr) {
	$ndigits++;
	$pwr *= 10;
    }
    if ($ndigits > 8) {
	return 0;
    }
    $pwr /= 10;

    do {
	$val = &encode_final_transform($val, 9371) % ($pwr * 10);
    } while ($val < $pwr);

    return ($val);
}

sub
twiddle_tt()
{
#input
    my $tt;

#outputs
    my $t;
    my $d;
    my $x1;
    my $x2;

# locals
    my $t1;
    my $t2;
    my $b;

    $tt = $_[0];

    $x1 = 0;
    $x2 = 0;
    $t1 = 0;
    $t1 = 0;
    $d  = 0;
    $t  = 0;
    $b  = 0;

    if (($tt >= 768) && ($tt <= 3647)) {
	$t1 = (($tt - 768) % 10) + 1;
	$t2 = $t1 * 5;
	if ($t1 >= 6) {
	    $x2 = $t2 - 25;
	} else {
	    $x1 = $t2;
	}
	$tt -= 768;
	$tt = int ($tt / 10);
    } 
    elsif (($tt >= 3648) && ($tt <= 6527)) {
	$t1 = (($tt - 3648) % 6) + 1;
	if ($t1 == 1) {
	    $x1 = 15;
	} else {
	    $x2 = (5 * $t1) - 5;
	}
	$tt = int (($tt - 3648) / 6) + 288;
    }
    elsif (($tt > 6527) && ($tt <= 13727)) {
	$x1 = 5 + (((($tt-6528) % 25) % 5) * 5);
	$x2 = 5 + (int((($tt-6528) % 25) / 5) * 5);
	$tt -= 6528;
	$tt = int ($tt / 25);
    }
    elsif ($tt > 13727) {
	$x2 = 5 + ((($tt - 13728) % 5) * 5);
	$x1 = 15;
	$tt -= 13728;
	$tt = int ($tt / 5) + 288;
    }

    if ($tt < 192) {
	# Lookup in table
	$b = $g_ttbl[$tt];
	$t = $b % 48;  		# Half hours after midnight
	$d = int ($b / 48);     # Half hours of duration - 1

    } else {
	$t = 47 - (($tt - 192) % 48);   # Half hours after midnight
	$d = int ($tt / 48);            # Half hours of duration - 1
    }

    @_ = ($t, $d, $x1, $x2);
}

sub
interleave ()
{
#inputs
    my $tval;
    my $cval;

    ($tval, $cval) = @_;

#outputs
    my $top5 = 0;
    my $bot3  = 0;

    if ($tval & (1 <<  0)) {($bot3 |= (1 <<  0))};
    if ($cval & (1 <<  0)) {($bot3 |= (1 <<  1))};
    if ($tval & (1 <<  1)) {($bot3 |= (1 <<  2))};
    if ($cval & (1 <<  1)) {($bot3 |= (1 <<  3))};
    if ($tval & (1 <<  2)) {($bot3 |= (1 <<  4))};

    if ($tval & (1 <<  3)) {($top5 |= (1 <<  0))};
    if ($cval & (1 <<  2)) {($top5 |= (1 <<  1))};
    if ($cval & (1 <<  3)) {($top5 |= (1 <<  2))};
    if ($tval & (1 <<  4)) {($top5 |= (1 <<  3))};
    if ($tval & (1 <<  5)) {($top5 |= (1 <<  4))};
    if ($tval & (1 <<  6)) {($top5 |= (1 <<  5))};
    if ($cval & (1 <<  4)) {($top5 |= (1 <<  6))};
    if ($tval & (1 <<  7)) {($top5 |= (1 <<  7))};
    if ($cval & (1 <<  5)) {($top5 |= (1 <<  8))};
    if ($tval & (1 <<  8)) {($top5 |= (1 <<  9))};
    if ($tval & (1 <<  9)) {($top5 |= (1 << 10))};
    if ($tval & (1 << 10)) {($top5 |= (1 << 11))};
    if ($cval & (1 <<  6)) {($top5 |= (1 << 12))};
    if ($tval & (1 << 11)) {($top5 |= (1 << 13))};
    if ($tval & (1 << 12)) {($top5 |= (1 << 14))};
    if ($tval & (1 << 13)) {($top5 |= (1 << 15))};
    if ($cval & (1 <<  7)) {($top5 |= (1 << 16))};

# Untested below. Channels over 127.
    if ($top5 & (1 << 16)) {
    	if ($tval & (1 << 11)) {($top5 |= (1 << 12))};
    	if ($tval & (1 << 12)) {($top5 |= (1 << 13))};
    	if ($tval & (1 << 13)) {($top5 |= (1 << 14))};
    	if ($cval & (1 <<  6)) {($top5 |= (1 << 15))};
    }
# Untested above. Channels over 127.

# Return shuffled bits
    @_ = ($top5, $bot3);
}

sub
inv_twiddle_tt(int startm, int durnm)
{
#inputs
    my $startm;
    my $durnm;
#output
    my $tt = 0;;
#locals
    my $x1 = 0;
    my $x2 = 0;
    my $t1 = 0;
    my $t2 = 0;
    my $t = 0;
    my $d = 0;
    my $b = 0;
    my $i = 0;
    my $bstart = 0;
    my $bdurn = 0;
    my $basett = 0;

    ($startm, $durnm) = @_;

    if (($durnm < 5) ||
        ($durnm > (60*8)) ||
    	($startm < 0) ||
	($startm > (23*60)+59) ) {
	    return (-1);
    }

    if ($durnm  % 5) { $durnm  = $durnm + 5  - ($durnm  % 5); }
    if ($durnm  > (8 * 60)) { $durnm  = 8 * 60; }
    if ($startm % 5) { $startm = $startm - ($startm % 5); }

    $bstart = $startm - ($startm % 30);
    $bdurn  = $durnm + 29;
    $bdurn  = $bdurn - ($bdurn % 30);
    $t = int($bstart / 30);
    $d = int($bdurn / 30);

    $x1 = $bdurn - $durnm;
    $x2 = $startm - $bstart;

    if ($d <= 4) {
	for ($i = 0; $i < 192; $i++) {
	    $basett = $i;
	    last if ($g_ttbl[$i] == ($t + (($d - 1) * 48)));
	}
    } else {
	$basett = (48 * ($d - 1)) + 47 - $t;
    }

    if (($x1 == 0) && ($x2 == 0)) {
	return $basett;
    }

    elsif (($d <= 6) && (($x1 == 0) || ($x2 == 0))) {
	$tt = 768 + (10 * $basett);
	if ($x1) {
	    $tt += int($x1 / 5) - 1;
	}
	elsif ($x2) {
	    $tt += int($x2 / 5) + 4;
	}
	return $tt;
    }

    elsif (($d > 6) && ( (($x1 == 15) && ($x2 == 0)) ||
                     (($x1 ==  0) && ($x2 != 0)) ) ) {

	$tt = 3648 + (6 * ($basett - 288));
	$tt += int($x2 / 5);
	return $tt;
    }

    elsif (($d <= 6) && ($x1) && ($x2)) {
	$tt = 6528 + (25 * $basett);
	$tt += int($x1 / 5) - 1;
	$tt += (int($x2 / 5) - 1) * 5;
	return $tt;
    }

    elsif (($d > 6) && ($x1) && ($x2)) {
	$tt = 13728 + (5 * ($basett - 288));
	$tt += int($x2 / 5) - 1;
	return $tt;
    }
    return -1;
}

sub
map_top()
{
#inputs
    my $year;
    my $month;
    my $day;
    my $top5;
    my $rem;
#outputs
    my $mtout;
    my $remout;
#locals
    my $year_mod16;
    my $year_mod100;
    my $ndigits;
    my $nd;
    my $j;

    my $k;
    my $t1;
    my $t2;
    my $t3;
    my $ym;
    my $datum;
    my $year_today;
    my @n;
    my $nref;

    ($year, $month, $day, $top5, $rem) = @_;


    $nref = \@n;

    $year_today = $year;

    $year_mod100 = $year % 100;
    $year_mod16  = $year_mod100 % 16;

    &split_digits($top5, $nref);
    $ndigits = &count_digits($top5) + 3;
    $nd = $ndigits - 3 - 1;

    $rem  = ($rem + $n[0] + $n[1] + $n[2] + $n[3] + $n[4]) & 0x1f;

    if ($ndigits <= 6) {
	if ($nd >= 0) {
            do {
    	        $k = 0;
                do {
    	            $n[$nd] = ($n[$nd] + $day) % 10;
    	            if ($nd > 0) {
    		        for ($j = $nd - 1; $j >= 0; $j--) {
    		            $n[$j] = ($n[$j] + $n[$j+1]) % 10;
    		        }
    	            }
    		    $rem += $n[0];
                } while (++$k <= $year_mod16);
            } while ($n[$nd] == 0);
	}
        $rem = ($rem + ($day * ($month + 1))) & 0x1f;

    } else { # ndigits > 6

        $ym = ($year_mod100 * 12) + $month;   # 1 to 1200

        do {
	    $k = 1;
    	    do {
		$t1 = ($ym + 310 - $k) % 31;
    		$t1 = ($ndigits == 7) ?
		      $g_LengthSevenTable[$t1] : $g_LengthEightTable[$t1];
                $t2 = ($t1 & 0x0f);
                $t3 = (($t1 >> 4) & 0x0f);
    	     	$t1 = $n[$t2] + (10 * $n[$t3]);
		do {
		    $t1 = ($g_ttbl[$t1] - $ym + 1920) % 192;
		} while ($t1 > 99);
    		$n[$t2] = $t1 % 10;
    		$n[$t3] = int ($t1 / 10);
    	    } while (++$k <= 31);
        } while ($n[$nd] == 0);
    }

    $mtout =  10000*$n[4] + 1000*$n[3] + 100*$n[2] + 10*$n[1] + $n[0];
    @_ = ($mtout, $rem);
}


sub
inv_map_top(year, month, day, top5)
{
#inputs
    my $year;
    my $month;
    my $day;
    my $top5;

# output
    my $mtout;

#locals
    my $year_mod16;
    my $year_mod100;
    my $ndigits;
    my $nd;
    my $i;
    my $j;
    my $k;
    my $t1;
    my $t2;
    my $t3;
    my $ym;
    my @n;
    my $nref;

    ($year, $month, $day, $top5) = @_;
    $nref = \@n;

    $year_mod100 = $year % 100;
    $year_mod16  = $year_mod100 % 16;
    &split_digits($top5, $nref);
    $ndigits = &count_digits($top5) + 3;
    $nd = $ndigits - 3 - 1;

    SWITCH: {
      if (($ndigits >= 0) && ($ndigits <= 3)) {
	$mtout = $top5;
	last SWITCH;
      }
      elsif (($ndigits >= 4) && ($ndigits <= 6)) {
        do {
    	    $k = 0;
            do {
		if ($nd > 0) {
		    for ($j = 0; $j < $nd; $j++) {
			$n[$j] = ($n[$j] + 100 - $n[$j+1]) % 10;
		    }
		}
    	        $n[$nd] = ($n[$nd] + 100 - $day) % 10;
            } while (++$k <= $year_mod16);
        } while ($n[$nd] == 0);

        $mtout =  10000*$n[4] + 1000*$n[3] + 100*$n[2] + 10*$n[1] + $n[0];
	last SWITCH;
      }
      elsif (($ndigits >= 7) && ($ndigits <= 8)) {
	$ym = ($year_mod100 * 12) + $month;   # 1 through 1200

	for ($i = 0; $i < 100; $i++) {
	    $j = $i;
	    do {
		$j = ($g_ttbl[$j] - $ym + 1920) % 192;
	    } while ($j > 99);
	    $inv_ttbl[$j] = $i;
	}
        do {
	    $k = 31;
    	    do {
		$t1 = ($ym + 310 - $k) % 31;
    		$t1 = ($ndigits == 7) ?
		     $g_LengthSevenTable[$t1] : $g_LengthEightTable[$t1];
                $t2 = ($t1 & 0x0f);
                $t3 = (($t1 >> 4) & 0x0f);
    	        $t1 = $n[$t2] + (10 * $n[$t3]);
		$t1 = $inv_ttbl[$t1];
    		$n[$t2] = $t1 % 10;
    		$n[$t3] = int($t1 / 10);
    	    } while (--$k >= 1);
        } while ($n[$nd] == 0);
        $mtout =  10000*$n[4] + 1000*$n[3] + 100*$n[2] + 10*$n[1] + $n[0];
        last SWITCH;
      }
      else {
	$mtout = (-1);
	last SWITCH;
      }
    }
    return $mtout;
}

sub
bit_shuffle ()
{
#inputs
    my $code;

#outputs
    my $tval;
    my $dval;
    my $cval;

#locals
    my  $tt;
    my	$cc;
    my  $x1;
    my  $x2;
    my  $nn;
    my  $top5;
    my  $rem;
    my  $t;
    my  $d;
    my  $outtime;
    my  $outdur;

# Setup arg and locals
    $code = $_[0];
    $tt = 0;
    $cc = 0;
    $x1 = 0;
    $x2 = 0;
    $nn = $code - 1;
    $top5 = int ($nn / 1000);
    $rem = int ($nn % 1000)  & 0x1f;

    if ($rem  & (1 <<  0)) { $tt |= (1 <<  0)};
    if ($rem  & (1 <<  2)) { $tt |= (1 <<  1)};
    if ($rem  & (1 <<  4)) { $tt |= (1 <<  2)};
    if ($top5 & (1 <<  0)) { $tt |= (1 <<  3)};
    if ($top5 & (1 <<  3)) { $tt |= (1 <<  4)};
    if ($top5 & (1 <<  4)) { $tt |= (1 <<  5)};
    if ($top5 & (1 <<  5)) { $tt |= (1 <<  6)};
    if ($top5 & (1 <<  7)) { $tt |= (1 <<  7)};
    if ($top5 & (1 <<  9)) { $tt |= (1 <<  8)};
    if ($top5 & (1 << 10)) { $tt |= (1 <<  9)};
    if ($top5 & (1 << 11)) { $tt |= (1 << 10)};
    if ($top5 & (1 << 13)) { $tt |= (1 << 11)};
    if ($top5 & (1 << 14)) { $tt |= (1 << 12)};
    if ($top5 & (1 << 15)) { $tt |= (1 << 13)};

    if ($rem  & (1 <<  1)) { $cc |= (1 <<  0)};
    if ($rem  & (1 <<  3)) { $cc |= (1 <<  1)};
    if ($top5 & (1 <<  1)) { $cc |= (1 <<  2)};
    if ($top5 & (1 <<  2)) { $cc |= (1 <<  3)};
    if ($top5 & (1 <<  6)) { $cc |= (1 <<  4)};
    if ($top5 & (1 <<  8)) { $cc |= (1 <<  5)};
    if ($top5 & (1 << 12)) { $cc |= (1 <<  6)};
    if ($top5 & (1 << 16)) { $cc |= (1 <<  7)};

# Following not verified - haven't seen a code with the high bit on here
    if ($top5 & (1 << 16)) {
    	if ($top5 & (1 << 12)) { $tt |= (1 << 11)};
    	if ($top5 & (1 << 13)) { $tt |= (1 << 12)};
    	if ($top5 & (1 << 14)) { $tt |= (1 << 13)};
    	if ($top5 & (1 << 15)) { $cc |= (1 <<  6)};
    }

    ($t, $d, $x1, $x2) = &twiddle_tt($tt);

    $outtime = (30 * $t) + $x2;
    $outdur  = (($d + 1) * 30) - $x1;

    @_ = ($cc + 1, $outdur, $outtime);
}

sub vcrpp_decode {
#args
    my $year;
    my $month;
    my $pluscode;

#results
    my $channel;
    my $day;
    my $start;
    my $duration;

#locals
    my $s1_out;
    my $bot3;
    my $top5;
    my $quo;
    my $rem;
    my $mtout;
    my $tval;
    my $dval;
    my $cval;
    my $day_out;
    my $modnews;

# Read the inouts
    ($year, $month, $pluscode) = @_;

    $year = $year % 100;

    if ($month<1 || $month>12) {
	die "Invalid month\n";
    }

    if (($pluscode < 1) || ($pluscode > 99999999)) {
	die "Invalid pluscode(TM), not 1 thru 99999999\n";
    }
    $s1_out = func1($pluscode);
    $bot3 = ($s1_out % 1000);
    $quo = ($bot3 - 1) >> 5;
    $rem = ($bot3 - 1) & 0x1f;
    $day = $quo + 1;
    $top5 = int ($s1_out / 1000);

    ($mtout, $rem) =  &map_top($year, $month, $day, $top5, $rem);
    $modnews = $mtout * 1000;
    $modnews += ($day << 5) + $rem - 31;
    ($cval, $dval, $tval) = &bit_shuffle($modnews);

    @_ = ($day, $tval, $dval, $cval);
}


sub vcrpp_encode {

#args
    my $year;
    my $month;
    my $day;
    my $starttime;
    my $starttimem;
    my $duration;
    my $durationm;
    my $channel;

#results
    my $newspaper;

#locals
    my $j = 0;
    my $s1_out = 0;
    my $bot3 = 0;
    my $top5 = 0;
    my $quo = 0;
    my $rem = 0;
    my $cval = 0;
    my $tval = 0;
    my $big_top5 = 0;
    my $big_rem = 0;
    my $mtout = 0;

    ($year, $month, $day, $starttime, $duration, $channel) = @_;
    $year = $year % 100;

    return 0 if $starttime > 2355;
    return 0 if $starttime < 0;
    return 0 if ($starttime % 100) > 55;
    return 0 if ($starttime % 5) != 0;
    return 0 if $duration > 800;
    return 0 if $duration < 0;
    return 0 if ($duration % 100) > 55;
    return 0 if ($duration % 5) != 0;
    return 0 if $day < 1;
    return 0 if $day > 31;
    return 0 if $month < 1;
    return 0 if $month > 12;
    return 0 if $year < 0;
    return 0 if $channel < 1;
    return 0 if $channel > 127;

    $starttimem = (60 * int($starttime / 100) + ($starttime % 100));
    $durationm = (60 * int($duration / 100) + ($duration % 100));

    $cval = $channel - 1;
    $tval = &inv_twiddle_tt($starttimem, $durationm);
    if ($tval < 0) {
	    return (0);
    }

    ($big_top5, $big_rem) = &interleave ($tval, $cval);

    $top5 = &inv_map_top($year, $month, $day, $big_top5);
    if ($top5 < 0) {
	return 0;
    }
    ($mtout, $rem) = &map_top($year, $month, $day, $top5, 0);
    $quo = $day - 1;
    $rem = ($big_rem + 320 - $rem) % 32;
    $bot3 = $rem + 1 + (32 * $quo);
    $s1_out = $bot3 + (1000 * $top5);

    $newspaper = &encfunc1($s1_out);
    return ($newspaper);
}

# func1, for decoding only

sub
func1()
{
    my $code;
    my $x;
    my $aref;
    my @a;

    my $sum;
    my $i;
    my $j;
    my $ndigits = -1;
    my $nd = 0;

    ($code)= @_;
    $x= $code;
    $aref = \@a;

    &split_digits($x, $aref);
    $ndigits = &count_digits($x);
    $nd = $ndigits - 1;

    do {
	if ($nd >= 0) {
    	    $i = 0;
    	    do {
    	        $j = 1;
    	        if ($nd >= 1) {
    		    do {
    		        $a[$j] = ($a[$j-1] + $a[$j]) % 10;
        	    } while (++$j <= $nd);
    	        }
    	    } while (++$i <= 2);
	}
    } while ($a[$nd] == 0);

    $sum = 0;
    $j = 1;
    for ($i = 0; $i < $NDIGITS; $i++) {
	$sum += $j * $a[$i];
	$j *= 10;
    }
    return $sum;
}
 
sub
encode_final_transform ()
{
    my $x;
    my $y;

    ($x, $y) = @_;

    my     ($i, $j, $digit, $sum);
    my     (@a, @b, @out);

    for ($i=0; $i<9; $i++) {
	$digit = $x % 10;
	$a[$i] = $digit;
	$x = ($x - $digit) / 10;
    }

    for ($i=0; $i<9; $i++) {
	$digit = $y % 10;
	$b[$i] = $digit;
	$y = ($y - $digit) / 10;
    }

    for ($i=0; $i<17; $i++) {
	$out[$i] = 0;
    }

    for ($i=0; $i<=8; $i++) {
	for ($j=0; $j<=8; $j++) {
	    $out[$i+$j] += $b[$j] * $a[$i];
	}
    }

    $j = 1;
    $sum = 0;
    for ($i=0; $i<=8; $i++) {
	$sum += $j * ($out[$i] % 10);
	$j *= 10;
    }
    return ($sum);
}
