#!/bin/perl

#
# This program searches the KnotDB database for knot types, then checks for the corresponding files.
#

use strict;
use warnings;
use File::Slurp;
use File::Copy;
use File::Path;
use Data::Dumper;
use Getopt::Long;
use Cwd;

my $line;
my %Reversible;
my %NoSymmetry;
my %FullSymmetry;
my %PlusAmphichiral;
my %MinusAmphichiral;

sub knot {

  my $string = shift;
  my @parts;
  @parts = split("_",$string);
  return $parts[0];

}

sub ind {

  my $string = shift;
  my @parts;
  @parts = split("_",$string);
  return $parts[1];

}

sub sym {

  my $string = shift;
  my @parts;
  @parts = split("_",$string);
  if (defined $parts[2]) { return $parts[2]; }
  else { return 'e'; }

}

my %symtype = ('e' => 1, 'r' => 2, 'm' => 3, 'rm' => 4, 'mr' => 4);

sub byknottype {

 knot($a) <=> knot($b) or

    ind($a) <=> ind($b) or 
      
      $symtype{sym($a)} <=> $symtype{sym($b)};

    }

sub canonicaltype {

  my $knottype = shift;
  my $basetype;

  # print "knottype = $knottype\n";

  $basetype = $knottype;
  $basetype =~ s/\_(rm|mr)//g;
  $basetype =~ s/\_m//g;
  $basetype =~ s/\_r//g;
  
  if (defined $Reversible{$basetype}) {

    $knottype =~ s/\_(rm|mr)/_m/g;
    $knottype =~ s/\_r//g;
    
  } elsif (defined $FullSymmetry{$basetype}) {

    $knottype =~ s/\_(rm|mr)//g;
    $knottype =~ s/\_r//g;
    $knottype =~ s/\_m//g;

  } elsif (defined $PlusAmphichiral{$basetype}) {

    $knottype =~ s/\_(rm|mr)/_r/g;
    $knottype =~ s/\_m//g;

  }

  return $knottype;

}


sub rmtype {

  my $knottype = $_[0];
  my $basetype;

  $basetype = $knottype;
  $basetype =~ s/\_(rm|mr)//g;
  $basetype =~ s/\_m//g;
  $basetype =~ s/\_r//g;

  #print "Checking for $knottype. We have $Reversible{basetype}, $FullSymmetry{$basetype}, $NoSymmetry{$basetype}, $PlusAmphichiral{$basetype}.\n";

  if ($knottype =~ /\_(rm|mr)/) {

    $knottype =~ s/\_(rm|mr)//g; 
    return $knottype;

  }

  if (defined $Reversible{$basetype}) {

    if ($knottype =~ /^\d+\_\d+$/) {

      $knottype = "${knottype}_m";
      return $knottype;

    }

    if ($knottype =~ /\_m/) {

      $knottype =~ s/\_m//;
      return $knottype;

    } 

    if ($knottype =~ /\_r/) {

      $knottype =~ s/\_r/_m/;
      return $knottype;

    }

  } elsif (defined $PlusAmphichiral{$basetype}) {

    if ($knottype =~ /^\d+\_\d+$/) {

      $knottype = "${knottype}_r";
      return $knottype;

    }

    if ($knottype =~ /\_m/) {

      $knottype =~ s/\_m/_r/;
      return $knottype;

    } 

    if ($knottype =~ /\_r/) {

      $knottype =~ s/\_r//;
      return $knottype;

    }

  } elsif (defined $NoSymmetry{$basetype}) {

 #   print "NoSymmetry basetype $basetype\n";

    if ($knottype =~ /^\d+\_\d+$/) {

      $knottype = "${knottype}_rm";
      return $knottype;

    }

    if ($knottype =~ /\_m/) {

      $knottype =~ s/\_m/_r/;
      return $knottype;

    } 

    if ($knottype =~ /\_r/) {

      $knottype =~ s/\_r/_m/;
      return $knottype;

    }

  } elsif (defined $FullSymmetry{$basetype}) {

    return $knottype;

  }

  die("Shouldn't get here.\n");

}
 
sub reversetype {

   my $knottype = $_[0];

   if ($knottype =~ /10\_102|10\_106|10\_107|10\_109|10\_110|10\_115|10\_117|10\_118|10\_119|10\_147|10\_148|10\_149|10\_150|10\_151|10\_153|10\_67|10\_79|10\_80|10\_81|10\_82|10\_83|10\_84|10\_85|10\_86|10\_87|10\_88|10\_90|10\_91|10\_92|10\_93|10\_94|10\_95|10\_98|8\_17|9\_32|9\_33/) { # It's not a reversible knot type

     if  ($knottype =~ /\_(rm|mr)/) {
       
       $knottype =~ s/\_(rm|mr)/_m/g;      

     } elsif  ($knottype =~ /\_r/) {  # It's already reversed

       $knottype =~ s/\_r//g;
  
     } elsif ($knottype =~ /\_m/) {

       $knottype =~ s/\_m/_rm/;

     } else {

       $knottype = "${knottype}_r";

     }

   } 

   return $knottype;

 }


sub mirrortype { 

    my $knottype = $_[0];

# We first check to see if the knottype is mirror symmetric.

    if ($knottype =~ /4\_1|6\_3|8\_3|8\_9|8\_12|8\_17|8\_18|10\_17|10\_33|10\_37|10\_43|10\_45|10\_79|10\_81|10\_88|10\_99|10\_109|10\_115|10\_118|10\_123/)  {

      return $knottype;

    } elsif ($knottype =~ /\_m/) {

      $knottype =~ s/\_m//g;

    } elsif ($knottype =~ /\_(rm|mr)/) {

      $knottype =~ s/\_(rm|mr)/\_r/g;

    } elsif ($knottype =~ /\_r/) { 

      $knottype =~ s/\_r/_rm/;

    } else {

      $knottype = "${knottype}_m";
      
    }
    
    return $knottype;
}

require './bin/tightknot_common.pl';

print STDERR "ckFiles 1.0: A consistency checker and updater for the KnotDB.pl database.\n";

my $checkknottypes = '';
my $force_sort = '';
my $force_tex = '';
my $force_regex = '';

GetOptions ( 'knottypes' => \$checkknottypes, 'forcesort' => \$force_sort, 'forcetex' => \$force_tex, 'forceregex=s' => \$force_regex);

# We start by loading the database.

my $cwd;
$cwd = getcwd;

print "Building table of symmetry data...\n";

my @lines;
open(CHART,"${cwd}/data/knotchart-prime.txt") or die("Couldn't open knotchart\n");
@lines = <CHART>;
close(CHART);

foreach $line (@lines) {

  my $knot;
  my $sym;

  $line =~ /(.+)\s(.+)/;
  
  $knot = $1;
  $sym = $2;
  
  $sym =~ s/reversible/Reversible/;
  $sym =~ s/none/NoSymmetry/;
  $sym =~ s/full/FullSymmetry/;
  $sym =~ s/amphichiral/PlusAmphichiral/;
  $sym =~ s/\s//g;
  
  $knot =~ s/m//g;
  $knot =~ s/r//g;
  $knot =~ s/\s//g;
  
  if ($sym eq "Reversible") {

    $Reversible{$knot} = 1;

  } elsif ($sym eq "NoSymmetry") {

    $NoSymmetry{$knot} = 1;

  } elsif ($sym eq "FullSymmetry") {

    $FullSymmetry{$knot} = 1;

  } elsif ($sym eq "PlusAmphichiral") {

    $PlusAmphichiral{$knot} = 1;

  } else {

    die("Shouldn't get here. $sym was none of Reversible, NoSymmetry, FullSymmetry, PlusAmphichiral for $knot.\n");

  }
  
}

print "Loaded symmetry data. Now loading Knot DB...\n";


my $DBFILE = read_file("${cwd}/knotdb/KnotDB.pl") or die("Couldn't open $cwd/knotdb/KnotDB.pl");
my %knots;
eval($DBFILE);

my %wknots = %knots;
my $numkeys = keys ( %wknots );
my $key;
my $rcond;

foreach $key (keys %knots) {

  if (defined $knots{$key}{summands} && $key !~ /#/) {

    $knots{$key}{summands} = undef;

  }

}

print "We have $numkeys entries in the original database.\n";
print "Checking list of knot types to make sure each composite is in canonical form.\n";

my $nonCanonflag = 0;

foreach $key (keys %knots) {

  if ($key !~ /vect/ && $knots{$key}{comp} == 1 && defined $knots{$key}{summands}) {

    # This is a composite knot

    my @canonicalSummands;
    @canonicalSummands  = map { canonicaltype($_) } @{$knots{$key}{summands}};
    @canonicalSummands  = sort byknottype @canonicalSummands;

    my $thisType = join("_#_",@{$knots{$key}{summands}});
    my $canonType = join("_#_",@canonicalSummands);

    unless ($thisType eq $canonType) {

      print "mismatch between canonical type $canonType and knot type $thisType.\n";
      print "moving files out of ./tars into ./vects to fix them by hand and removing the tar from svn.\n";

      copy("${cwd}/tars/kl_${thisType}.tar.gz","${cwd}/vects/kl_${thisType}.tar.gz") or die("Couldn't copy ${cwd}/tars/kl_${thisType}.tar.gz -> ${cwd}/vects/kl_${thisType}.tar.gz\n");
      `svn rm ${cwd}/tars/kl_${thisType}.tar.gz`;

      $nonCanonflag = 1;

    }

  }

}
    
print "All knot types for composite knots are in canonical form.\n";    

print "Now creating a list of contents of tarfiles in $cwd/tars ... ";

my %files;
my @tars;
my $thisvectfile;
my $thistar;
my $tarprefix;

@tars = <$cwd/tars/*.tar.gz>;
#print "First tar: ".$tars[1]."\n\n";

foreach $thistar (@tars) {

  $thistar =~ /tars\/(.+)\.tar.gz/;
  $tarprefix = "./".$1."/";

#  print "tarprefix: $tarprefix \n";

  open(TARCONTENTS,"tar -tzf $thistar | ");
  while (<TARCONTENTS>) {
      if (/vect/) { 
	$thisvectfile = $_;
	$thisvectfile =~ s/$tarprefix//g;
	chomp($thisvectfile);

	$files{$thisvectfile}{tar} = $thistar;
	$files{$thisvectfile}{filename} = $thisvectfile;
	$files{$thisvectfile}{tarprefix} = $tarprefix;

      }
    }

#  print `tar -tzf $thistar`;
#  print keys(%files);

}

print "done.\n";
print "Checking for db keys without files in a tar...";

foreach $key (keys %wknots) {

  if ($key =~ /vect/) {

    unless (defined $files{$key}) {

	if ($key !~ /r10\_f25/ && $key !~ /.+\_\#\_\d+\_\d+\.vect/ && $key !~ /.+\_\#\_\d+\_\d+_[mr].vect/) {

	  print "$key doesn't have a corresponding file.\n";

	}

      delete $knots{$key};

    }

  }

}

print "..done\n";

print "Updating ${cwd}/knotdb/KnotDB to eliminate file entries w/o corresponding files...\n";

rename "${cwd}/knotdb/KnotDB.pl","${cwd}/knotdb/KnotDB.old.pl" or die("Unable to rename old knot database.");
open(KNOTDB,">${cwd}/knotdb/KnotDB.pl") or die "Unable to create Knot database.";
print KNOTDB Data::Dumper->Dump([\%knots],['*knots']),"\n";
close KNOTDB;

print "Looking for knot records which refer to pruned files...\n";

foreach $key (keys %knots) {

  if ($key !~ /vect/) {

    unless (defined $files{$knots{$key}{filename}}) {

      print "knot type $key refers to $knots{$key}{filename}, which doesn't exist.\n";

    }

    $knots{$key}{allfiles} = [ ];

  }

}


if ($checkknottypes) {
 
    my $changed_tars = 0;

    print "Checking all files in tars to make sure that the filename agrees with knot type...\n";
    
    chdir("${cwd}/tars") or die("Couldn't chdir to ${cwd}/tars\n");
    
    foreach $thistar (@tars) {
	
	`tar -xzf $thistar`;  # First, we unpack the tar.
	
	$thistar =~ /tars\/(.+)\.tar.gz/;
	$tarprefix = "./".$1."/";
	
	chdir("${tarprefix}") or die("Couldnt chdir to ${tarprefix}\n");
	
	my @kfiles;
	@kfiles = <*.vect>; # collect the vects from this tar.
	
	print "Checking files for knot type $tarprefix...";
	
      KEY:foreach $key (@kfiles) {
	  
#	print "key = $key\n";
	  
	  my $knot_type;
	  unless( $key =~ /kl_(.+)_(.+).vect/ ) { die("Can't pull knot type from filename $key\n"); }
	  $knot_type = $1;
	  
#	print "knot_type = $knot_type.\n";
	  
	  if ($key =~ /vect/ && $knot_type !~ /\d+\_\d+\_\d+/ && $key !~ /open/ && $key !~ /kl_0_1_/ && $key !~ /vvhires/) {  # a closed knot (no links) 
	      
	      print "$key -> ";
	      
	      my @knottypes;
	      open(KNOTTYPE, "ulimit -t 15; knottype -qk $key 2>&1 |");
	      while (<KNOTTYPE>) {
		  if ($_ =~ m/\|(.+)\|/) {
		      
		      my $ktype;
		      $ktype = $1;
		      
		      # Convert the tags into m r or mr
		      
		      $ktype =~ s/\s\(1,1,e\)//g;
		      $ktype =~ s/\s\(-1,1,e\)/_m/g;
		      $ktype =~ s/\s\(1,-1,e\)/_r/g;
		      $ktype =~ s/\s\(-1,-1,e\)/_mr/g;
		      
		      $ktype =~ s/></_#_/g;
		      $ktype =~ s/<//g;
		      $ktype =~ s/>//g;
		      
		      push(@knottypes,$ktype);
		      
		      print "$ktype (measured by HOMFLY) ->";
		      
		  } elsif ($_ =~ m/Unable to classify/) {
		      
		      push(@knottypes,$_);
		      
		  } elsif ($_ =~ m/Fortran runtime error|Killed/) {
		      
		      print "file crashes knottype -k. Saving a copy in ./badvects and purging this knot from tar. \n";
		      
		      print "copying to ./badvects...";
		      copy("$key","${cwd}/badvects/$key") or die("Couldn't copy $key to ${cwd}/badvects/$key\n");
		      print "done\n";

		      print "deleting old file...";
		      unlink("$key") or die("Couldn't unlink $key.\n");
		      print "done\n";
		      
		      print "recreating tar file $thistar from $tarprefix...\n\t";
		      chdir("..") or die("Couldn't chdir to ..\n");
		      print `tar -czf $thistar $tarprefix`;
		      chdir($tarprefix) or die("Couldn't chdir back to $tarprefix..\n");
		      print "\t...done\n";

		      $changed_tars = 1;
		    
		      next KEY;
		      
		  }
		  
	      }
	      
	      close KNOTTYPE;
	      
	      my $ktstring;
	      
	      $ktstring = join("",@knottypes);
	      
	      if ($ktstring !~ /^$knot_type$/ && $ktstring !~ /Unable to classify/) {
		
		  if ($ktstring =~ /^${knot_type}_m$/) {
		      
		      print " is mirrored -> ";
		      
		      my $newfname;
		      $newfname = $key;
		      $newfname =~ s/\.vect/\.[-1,1,e]\.vect/;
		      
		      `whittenvect -qo \"(-1,1,e)\" ./$key `;
		      
		      print "$newfname -> ";
		      
		      rename ("$key","$key.bak");
		      move ("$newfname","$key") or die("Couldn't copy $newfname over $key.\n");
		      
		      print "$key -> ";
		      
		      $knots{$key}{writhe} = undef; # remove writhing number from DB
		      
		      print "writhe set to undef -> ";
		      
		      print "recreating tar file $thistar from $tarprefix...\n\t";
		      chdir("..") or die("Couldn't chdir to ..\n");
		      print `tar -czf $thistar $tarprefix`;
		      chdir($tarprefix) or die("Couldn't chdir back to $tarprefix..\n");
		      print "\t...done\n"; 
		      
		      $changed_tars = 1;
		 
		  } else {
		      
		      print "knot type actually $ktstring! Saving a copy in ./badvects and purging this knot from tar. \n"; 
		      
		      print "copying to ./badvects...";
		      copy("$key","${cwd}/badvects/$key$ktstring") or die("Couldn't copy $key to ${cwd}/badvects/$key$ktstring\n");
		      print "done\n";
		      
		      print "deleting old file...";
		      unlink("$key") or die("Couldn't unlink $key.\n");
		      print "done\n";
		      
		      print "recreating tar file $thistar from $tarprefix...\n\t";
		      chdir("..") or die("Couldn't chdir to ..\n");
		      print `tar -czf $thistar $tarprefix`;
		      chdir($tarprefix) or die("Couldn't chdir back to $tarprefix..\n");
		      print "\t...done\n"; 

		      $changed_tars = 1;
		      
		      next KEY;
		      
		  }
		  
	      } else {
		  
		  print "ok"
		      
	      }
	    
	      print "\n";
	      
	  }
	  
      }
	
	chdir("${cwd}/tars") or die("Couldn't chdir back to ${cwd}/tars\n");
	rmtree("$tarprefix") or die("Couldn't rmtree the directory $tarprefix\n");
	
    }

    if ($changed_tars == 1) {

	die("Moved files to badvects which crashed the HOMFLY code. Rerun ckFiles to account for deleted files.\n");

    }
    
} 

print "Searching ./tars for files which don't have records in the DB...\n";

chdir("$cwd/tars/") or die("Couldn't chdir to ./tars\n");

foreach (keys %files) {

  unless (defined $knots{$_}) {

    print "File $_ is present in tar, but has no entry in DB. Moving to ./vects for proper processing...";

    # We will move a copy of the file out to ./vects for incorporation into the DB.

    `tar -xzf $files{$_}{tar}`;
    move($files{$_}{tarprefix}.$_,"${cwd}/vects/$_") or die("Couldn't move $files{$_}{tarprefix} $_ to ${cwd}/vects/$_ \n");
    print "done\n";

    print "recreating tar file $files{$_}{tar} from $files{$_}{tarprefix}...";
    `tar -czf $files{$_}{tar} $files{$_}{tarprefix}`;
    print "...done\n"; 

    rmtree($files{$_}{tarprefix}) or die("Couldn't rmtree $files{$_}{tarprefix}\n");

  }

}     

print "Searching ./vects for files which don't have records in the DB...\n";

chdir("${cwd}/vects/") or die("Couldn't chdir to ${cwd}/vects\n");
my @vglob;
@vglob = <*.vect>;
chdir("..");
my $thisfile;

print "Found ".scalar @vglob." new VECT files in ${cwd}/vects to process.\n";

my($ropelength,$edges,$edgelength,$upperbound,$residual,$mrlocs,$writhe);

NEWFILE: foreach $thisfile (@vglob) {

  unless (defined $knots{$thisfile}) {

    unless (defined($knots{$thisfile})) {
 
      print "Found file $thisfile which does not have an entry in ${cwd}/knotdb/KnotDB. Computing data...\n";

    } 
    
 #   if ($thisfile =~ /$force_regex/) {#

#      print "File $thisfile matches $force_regex, so we are recomputing its data.\n";

#    }
      
    #my @parts;
    #@parts = ($thisfile =~ /kl_(\d+_\d+(?:_\d+)?)_([A-Za-z1-9_.-]+).vect/i);

    my %parsehash;
    %parsehash = parsefilename($thisfile);

    print "File appears to have knot type $parsehash{knottype}.\n";
    
    $ropelength = undef;
    $edges = undef;
    $edgelength = undef;
    $upperbound = undef;
    $residual = undef;
    $rcond = undef;
    $mrlocs = undef;
    $writhe = undef;

    print "Running ropelength . . .";
    open(ROPELENGTH,"ropelength ${cwd}/vects/$thisfile |");
    while (<ROPELENGTH>) {
      if (/:Ropelength:\s*(\S+)/) { 
	$ropelength = $1;
      } elsif (/Edges:\s*(\S+)\s*Average Edgelength:\s*(\S+)/) {
	$edges = $1;
	$edgelength = $2;
      }
    }
    close ROPELENGTH;
    print "$ropelength.\n";
    
    print "Running roundout_rl ... ";
    open(ROUNDOUT,"roundout_rl -a 0.001 ${cwd}/vects/$thisfile |");
    while (<ROUNDOUT>) {
      if (/e\^2-Upper Bound: (\d\S+)/) { # Depending on the radius one being second
	$upperbound = $1;
      }
    }
    print "$upperbound \n";
    
    print "Running residual.pl ... ";
    open(RESIDUAL,"residual.pl ${cwd}/vects/$thisfile | ");
    while (<RESIDUAL>) {
      if (/Residual: (\S+)/) { 
	$residual = $1;
      }
    }
    close RESIDUAL;
    print "$residual \n";

    print "Running rcond ... ";
    open(RESIDUAL,"rcond ${cwd}/vects/$thisfile | ");
    while (<RESIDUAL>) {
      if (/Rcond: (\S+)/) { 
	$rcond = $1;
      }
    }
    close RESIDUAL;
    print "$rcond \n";

    print "Running mrlocs... \n";
    $mrlocs = undef;

    open(MRLOCS,"mrlocs ${cwd}/vects/$thisfile -r 0.5 --nofile | ");
    while (<MRLOCS>) {
	if ($_ =~ m/mrlocs: (\S+) mrlocs found\./) { 
	    $mrlocs = $1;
        }
    }
    close MRLOCS;

    print "Running writhecomp...\n";
    open(WRITHE,"kpwrithe --file=${cwd}/vects/$thisfile | ");
    while (<WRITHE>) {
	if ($_ =~ m/writhe:\s+(\S+)/) { 
	    $writhe = $1;
        }
    }
    close WRITHE;

    unless (defined $ropelength && defined $residual && defined $upperbound && defined $rcond && defined $writhe) {

        warn("Ropelength, residual, roundout, rcond or writhecomp call failed for $thisfile.\n");

	# First, this file should not be present in tar.

	print "Deleting $thisfile from svn.\n";
	`svn rm ${cwd}/vects/$thisfile 2>&1`;
	unlink("${cwd}/vects/$thisfile");

	next NEWFILE;

    }

    my %fname_stuff;

    %fname_stuff = parsefilename( $thisfile );

    $knots{$thisfile} = { 
		  knot_type => $parsehash{knottype},
		  edges => $edges,
		  edgelength => $edgelength,
		  ropelength => $ropelength,
		  upperbound => $upperbound,
		  residual => $residual,
		  rcond => $rcond,
		  mrlocs => $mrlocs,
		  writhe => $writhe,
		  cr => $fname_stuff{cr},
		  comp => $fname_stuff{comp},
		  index => $fname_stuff{index}
		 };

    if (scalar @{ $parsehash{summands}} > 1) {

      @{$knots{$thisfile}{summands}} = @{ $parsehash{summands}};

    }			   

    print "Added record $knots{$thisfile}. Updating ${cwd}/knotdb/KnotDB.pl...\n";
   
    rename "${cwd}/knotdb/KnotDB.pl","${cwd}/knotdb/KnotDB.wold.pl" or die("Unable to rename ${cwd}/knotdb/KnotDB.pl to ${cwd}/knotdb/KnotDB.wold.pl \n");
    open(KNOTDB,">${cwd}/knotdb/KnotDB.pl") or die "Unable to create Knot database.";
    print KNOTDB Data::Dumper->Dump([\%knots],['*knots']),"\n";
    close KNOTDB;

    print "Now running addFile to add $thisfile to tars....";
    print "file = $thisfile\n";
    my $newcwd;
    $newcwd = getcwd;
    print "newcwd = $newcwd\n";
    print `perl ${cwd}/bin/addFile.pl $thisfile`;
    print "done\n";

  }

}

rename "${cwd}/knotdb/KnotDB.pl","${cwd}/knotdb/KnotDB.wold.pl" or die("Unable to rename ${cwd}/knotdb/KnotDB.pl to ${cwd}/knotdb/KnotDB.wold.pl \n");
open(KNOTDB,">${cwd}/knotdb/KnotDB.pl") or die "Unable to create Knot database ${cwd}/knotdb/KnotDB.pl";
print KNOTDB Data::Dumper->Dump([\%knots],['*knots']),"\n";
close KNOTDB;

print "Sorting files by knot type and updating knot records... \n";
foreach $key (keys %knots) {

  if ($key =~ /vect/ && $key !~ /open/ && $key !~ /nomr/ && $key !~ /stf/) {  

    # remember that an open guy is not in contention for the top spot
    # nor are runs with no mr or an alternate stiffness

    push @{$knots{$knots{$key}{knot_type}}{allfiles}}, $key;

  }

}

print "Updating ${cwd}/knotdb/KnotDB.pl to reflect added list of files of each knot type.\n";
rename "${cwd}/knotdb/KnotDB.pl","${cwd}/knotdb/KnotDB.vold.pl" or die("Could not rename.\n");
open(KNOTDB,">${cwd}/knotdb/KnotDB.pl") or die "Unable to create Knot database.";
print KNOTDB Data::Dumper->Dump([\%knots],['*knots']),"\n";
close KNOTDB;


print "Sorting files within each knot type by upperbound...\n";

foreach $key (keys %knots) {

  if ($key !~ /vect/) {

    @{$knots{$key}{allfiles}} = sort {$knots{$a}{upperbound} <=> $knots{$b}{upperbound}} @{$knots{$key}{allfiles}};

  }

}

print "Updating ${cwd}/knotdb/KnotDB.pl to reflect sort of files in each knot type.\n";
rename "${cwd}/knotdb/KnotDB.pl","${cwd}/knotdb/KnotDB.vvold.pl" or die("Could not rename.\n");
open(KNOTDB,">${cwd}/knotdb/KnotDB.pl") or die "Unable to create Knot database.";
print KNOTDB Data::Dumper->Dump([\%knots],['*knots']),"\n";
close KNOTDB;

print "Now updating the filename record in each knot type...\n";
my $updateflag = 0;

foreach $key (keys %knots) {

  if ($key !~ /vect/) {

    if (scalar @{$knots{$key}{allfiles}} == 0) { # There are no longer any files for this knot type

      print "Deleting $key, which has no files...\n";
      delete $knots{$key};

    } else {

      my $bestfile;
      $bestfile = @{$knots{$key}{allfiles}}[0];
      
      my $okfile;
      my $searchfile;
      
      foreach $searchfile ( @{$knots{$key}{allfiles}} ) {
	
	if ($knots{$searchfile}{residual} < 0.01 && $knots{$searchfile}{edges}/$knots{$searchfile}{upperbound} > 8.0) {
	  
	  $okfile = $searchfile;
	  last;
	  
	}
	
      }
      
      if (defined $okfile && $okfile ne $bestfile) {
	
	print "\nWarning!\nbest ropelength in file $bestfile, but acceptable residual/rop in $okfile.\n";
	
	if ($knots{$okfile}{upperbound} - $knots{$bestfile}{upperbound} < 0.01) {
	  
	  print "we gain ropelength of ".($knots{$okfile}{upperbound} - $knots{$bestfile}{upperbound})." by this switch\n\n";
	  
	$bestfile = $okfile;
	  
	} else {
	  
	  print "ropelength gain of ".($knots{$okfile}{upperbound} - $knots{$bestfile}{upperbound})." UNACCEPTABLE! (no switch made)\n\n";
	  
	}
	
      }

      my @files;
      @files = @{$knots{$key}{allfiles}};
      
      if ($knots{$key}{filename} ne $bestfile ) {
	
	print "Updating $key ($knots{$key}{upperbound}) to file $bestfile ($knots{$bestfile}{upperbound}).\n";
	$updateflag = 1;
	
	delete $knots{$key};
	
	$knots{$key}{upperbound} = $knots{$bestfile}{upperbound};
	$knots{$key}{ropelength} = $knots{$bestfile}{ropelength};
	$knots{$key}{edges} = $knots{$bestfile}{edges};
	$knots{$key}{edgelength} = $knots{$bestfile}{edgelength};
	$knots{$key}{residual} = $knots{$bestfile}{residual};
	$knots{$key}{filename} = $bestfile;
	$knots{$key}{rcond} = $knots{$bestfile}{rcond};
	$knots{$key}{mrlocs} = $knots{$bestfile}{mrlocs};
	$knots{$key}{writhe} = $knots{$bestfile}{writhe};
	$knots{$key}{cr} = $knots{$bestfile}{cr};
	$knots{$key}{comp} = $knots{$bestfile}{comp};
	$knots{$key}{index} = $knots{$bestfile}{index};
	
	if (defined $knots{$bestfile}{summands}) { @{$knots{$key}{summands}} = @{$knots{$bestfile}{summands}}; }
	
	@{$knots{$key}{allfiles}} = @files;

	# We also need to update the ./best/directory.

	my $bestname;
	my $tarname;
	my $xname;
	my $cdir;

	$bestname = "${cwd}/best/".$key."_best.vect";
	$tarname = "kl_${key}.tar.gz";
	$xname = "./kl_${key}/".$knots{$key}{filename};
	$cdir = getcwd;

	chdir("${cwd}/tars") or die("Couldn't switch to ${cwd}/tars\n");
	`tar -xzf $tarname $xname`;
	copy($xname,$bestname) or die("File can't be copied to ".$bestname);
	rmtree("./kl_${key}/") or die("Couldn't remove ./kl_${key}/\n");
	chdir($cdir) or die("Couldn't switch back to ${cwd}/tars\n");
	
      }
      
    }
  }
}

if ($updateflag > 0) {

  open TEXTFILE, ">${cwd}/best/ropelength_data.txt" or die("Couldn't open ${cwd}/best/ropelength_data.txt\n");
  
  print TEXTFILE "# This file contains summary data from \" Knot Tightening By Constrained Gradient Descent \" by Ashton, Cantarella, Piatek, Rawdon.\n";
  print TEXTFILE "# Knot type Polygonal Ropelength Ropelength Bound Edges Residual \n";
  
  foreach $key ( keys %knots ) {
    
    if ($key !~ /vect/) {

      print TEXTFILE "$key \t $wknots{$key}{ropelength} \t $wknots{$key}{upperbound} \t $wknots{$key}{edges} \t $wknots{$key}{residual} \n";

    }
     
  }
  
  close TEXTFILE;
  
  chdir($cwd) or die("Couldn't change back to $cwd\n");
  
  print "Now making archive for distribution...\n";
  
  print `mkdir ropelengthdata`;
  print `cp ./best/*.vect ./ropelengthdata/`;
  print `cp ./best/*.txt ./ropelengthdata/`;
  print `tar -czf ropelengthdata.tgz ./ropelengthdata/`;
  print `rm -fr ropelengthdata`;

}
  
      
print "Updating ${cwd}/knotdb/KnotDB.pl to reflect new winning file in each knot type.\n";
rename "${cwd}/knotdb/KnotDB.pl","${cwd}/knotdb/KnotDB.vvvold.pl" or die("Could not rename.\n");
open(KNOTDB,">${cwd}/knotdb/KnotDB.pl") or die "Unable to create Knot database.";
print KNOTDB Data::Dumper->Dump([\%knots],['*knots']),"\n";
close KNOTDB;
 
print "Scanning for knot types without a sort order...\n";
my $unsorted;
$unsorted = 0;

foreach ( keys %knots ) {

    if ($_ !~ /vect/) {  # this is a filename and not a knot type

	unless (defined ($knots{$_}{sortorder})) {

	    $unsorted++;
	    
	}

    }

}

if ($unsorted ne 0 || $force_sort) {

    print "Found $unsorted files without sort keys. Resorting...";

    my @sortedorder;
    @sortedorder = sorted_knot_types(\%knots,"includecomposites");

    print "Now assigning sortorder keys to knot types...\n";

    my $i;
    foreach $i (0..$#sortedorder) {

	$knots{$sortedorder[$i]}{sortorder} = $i;

    }

} else {

    print "Every knot type has a sort order. Will not resort.\n";

}


print "Updating ${cwd}/knotdb/KnotDB.pl to reflect sort order of knot types.\n";
rename "${cwd}/knotdb/KnotDB.pl","${cwd}/knotdb/KnotDB.vvvvold.pl" or die("Could not rename.\n");
open(KNOTDB,">${cwd}/knotdb/KnotDB.pl") or die "Unable to create Knot database.";
print KNOTDB Data::Dumper->Dump([\%knots],['*knots']),"\n";
close KNOTDB;

print "Scanning for knot types without TeX description....\n";

foreach (keys ( %knots )) {

  if ($_ !~ /vect/) {

    unless (defined($knots{$_}{tex}) && !($force_tex) ) {

      print "\t$_ now has TeX ";

      my %retstuff;

      %retstuff = parsefilename( $knots{$_}{filename} );
      $knots{$_}{tex} = $retstuff{tex};

      print "$retstuff{tex}.\n";

    }

  }

}

print "Updating ${cwd}/knotdb/KnotDB.pl to reflect TeX tags.\n";
rename "${cwd}/knotdb/KnotDB.pl","${cwd}/knotdb/KnotDB.vvvold.pl" or die("Could not rename.\n");
open(KNOTDB,">${cwd}/knotdb/KnotDB.pl") or die "Unable to create Knot database.";
print KNOTDB Data::Dumper->Dump([\%knots],['*knots']),"\n";
close KNOTDB;

if (scalar @vglob > 0) {

  die("Added new files to the tar structure. Rerun ckFiles.pl to continue processing.\n");

}

# We are now sure that the %files hash contains a record for everybody in the DB.

print "Comparing knot types with their mirrors to make sure we have corresponding winners...\n";

my $thiskey;

foreach $thiskey (keys ( %knots )) {

  if ($thiskey !~ /vect/) {

      if (defined $knots{$thiskey}{summands} && $knots{$thiskey}{comp} == 1) {

	  my @msums;
	  my $ktype;
	  my $smd;
	  
	  foreach $smd (@{$knots{$thiskey}{summands}}) {
	      
	      my $mirror;
	      $mirror = &mirrortype($smd);
	      push(@msums,$mirror);
	      
	  }
	  
	  $ktype = join("_#_",sort byknottype @msums); # we need to sort the summands here!
	  
	  print ("Comparing $thiskey to $ktype... ");
      
	  if (!defined $knots{$ktype} or $knots{$thiskey}{upperbound} < $knots{$ktype}{upperbound} - 0.00001) {

	    if (defined $knots{$ktype}) {
		  
		  print "\n\t $thiskey wins $knots{$thiskey}{ropelength} to $knots{$ktype}{ropelength}.\n";

		} else {

		  print "\n\t $ktype not in the database: adding this file as first example!\n";

		}

	    my %rv;
	    %rv = &parsefilename($knots{$thiskey}{filename});
	    
	    my $tag;
	    $tag = $rv{tag}."mir";
		  
	    my $newname;
	    $newname = "kl_${ktype}_${tag}.vect";
		  
	    print "\t generating new file for $ktype with name $newname \n";
		  
	    my $comp = $rv{comp};
	    my @warray;
		  
	    push(@warray,-1);
		  
	    foreach (1..$comp) {
		      
	      push(@warray,"1");
	      
	    }
	    
	    push(@warray,"e");
	    my $wmirror = "(".join(",",@warray).")";

	    # We now need to extract the original file from its tar

	    chdir("${cwd}/tars") or die("Couldn't chdir to ${cwd}/tars\n");
	    `tar -xzf $files{$knots{$thiskey}{filename}}{tar}`;  # First, we unpack the tar.
	    $tarprefix = $files{$knots{$thiskey}{filename}}{tarprefix};
	    
	    print "whittenvect -o \"$wmirror\" --output=${cwd}/vects/$newname ${tarprefix}$knots{$thiskey}{filename} \n";
	    `whittenvect -o \"$wmirror\" --output=${cwd}/vects/$newname ${tarprefix}$knots{$thiskey}{filename}`;
		  
	    warn ("RERUN ckFiles.pl to incorporate new file $newname into database!\n");
		  
	  } else {

	    print("ok\n");

	  }       

      }

  }

}

print "Comparing knot types with their reverses to make sure we have corresponding winners...\n";

my $thiskey;

foreach $thiskey (keys ( %knots )) {

  if ($thiskey !~ /vect/) {

      if (defined $knots{$thiskey}{summands} && $knots{$thiskey}{comp} == 1) {

	  my @rsums;
	  my $ktype;
	  my $smd;
	  
	  foreach $smd (@{$knots{$thiskey}{summands}}) {
	      
	      my $reverse;
	      $reverse = &reversetype($smd);
	      push(@rsums,$reverse);
	      
	  }
	  
	  $ktype = join("_#_",sort byknottype @rsums);
	  
	  print ("Comparing $thiskey to $ktype... ");

	  if (!defined $knots{$ktype} or $knots{$thiskey}{upperbound} < $knots{$ktype}{upperbound} - 0.00001) {

	    if (defined $knots{$ktype}) {
	      
	      print "\n\t $thiskey wins $knots{$thiskey}{ropelength} to $knots{$ktype}{ropelength}.\n";
	      
	    } else {
	      
	      print "\n\t $ktype not in the database: adding this file as first example!\n";
	      
	    }
	    
	    my %rv;
	    %rv = &parsefilename($knots{$thiskey}{filename});
	    
	    my $tag;
	    $tag = $rv{tag}."rev";
	    
	    my $newname;
	    $newname = "kl_${ktype}_${tag}.vect";
	    
	    print "\t generating new file for $ktype with name $newname \n";
	    
	    # We now need to extract the original file from its tar
	    
	    chdir("${cwd}/tars") or die("Couldn't chdir to ${cwd}/tars\n");
	    `tar -xzf $files{$knots{$thiskey}{filename}}{tar}`;  # First, we unpack the tar.
	    $tarprefix = $files{$knots{$thiskey}{filename}}{tarprefix};
	    
	    print "whittenvect -o \"(1,-1,e)\" --output=${cwd}/vects/$newname ${tarprefix}$knots{$thiskey}{filename} \n";
	    `whittenvect -o \"(1,-1,e)\" --output=${cwd}/vects/$newname ${tarprefix}$knots{$thiskey}{filename}`;
	    
	    warn ("RERUN ckFiles.pl to incorporate new file $newname into database!\n");
	    
	  } else {
	    
	    print("ok\n");
	    
	  }       

      }

  }

}

print "Comparing knot types with their reverse mirrors to make sure we have corresponding winners...\n";

my $thiskey;

foreach $thiskey (keys ( %knots )) {

  if ($thiskey !~ /vect/) {

      if (defined $knots{$thiskey}{summands} && $knots{$thiskey}{comp} == 1) {

	  my @rmsums;
	  my $ktype;
	  my $smd;
	  
	  foreach $smd (@{$knots{$thiskey}{summands}}) {
	      
	      my $rm;
	      $rm = &rmtype($smd);
	      push(@rmsums,$rm);
	      
	  }
	  
	  $ktype = join("_#_",sort byknottype @rmsums);
	  
	  print ("Comparing $thiskey to $ktype... ");

	  if (!defined $knots{$ktype} or $knots{$thiskey}{upperbound} < $knots{$ktype}{upperbound} - 0.00001) {

	    if (defined $knots{$ktype}) {
	      
	      print "\n\t $thiskey wins $knots{$thiskey}{ropelength} to $knots{$ktype}{ropelength}.\n";
	      
	    } else {
	      
	      print "\n\t $ktype not in the database: adding this file as first example!\n";
	      
	    }

	    my %rv;
	    %rv = &parsefilename($knots{$thiskey}{filename});
	    
	    my $tag;
	    $tag = $rv{tag}."rmd";
	    
	    my $newname;
	    $newname = "kl_${ktype}_${tag}.vect";
	    
	    print "\t generating new file for $ktype with name $newname \n";
	    
	    # We now need to extract the original file from its tar
	    
	    chdir("${cwd}/tars") or die("Couldn't chdir to ${cwd}/tars\n");
	    `tar -xzf $files{$knots{$thiskey}{filename}}{tar}`;  # First, we unpack the tar.
	    $tarprefix = $files{$knots{$thiskey}{filename}}{tarprefix};
	    
	    print "whittenvect -o \"(-1,-1,e)\" --output=${cwd}/vects/$newname ${tarprefix}$knots{$thiskey}{filename} \n";
	    `whittenvect -o \"(-1,-1,e)\" --output=${cwd}/vects/$newname ${tarprefix}$knots{$thiskey}{filename}`;
	    
	    warn ("RERUN ckFiles.pl to incorporate new file $newname into database!\n");
	    
	  } else {
	    
	    print("ok\n");
	    
	  }       
	  
      }

  }

}

# We now remove any tar directories that we unpacked in /tars and haven't put away.

my @tglob;

@tglob = <${cwd}/tars/*>;

foreach (@tglob) {

  if (-d $_ ) {

    rmtree($_);

    print "Removing tar directory $_\n";

  }

}

if ($nonCanonflag == 1) {

  warn("Removed tars corresponding to non-canonical composites and put them in ./vects. This data will be lost unless you fix and rename them by hand!\n");

}

unlink("${cwd}/A.dat");
unlink("${cwd}/A.mat");
unlink("${cwd}/A.sparse");
unlink("${cwd}/b.dat");
unlink("${cwd}/b.mat");
