#!/usr/bin/perl -w
#
# Copyright (C) 2003, 2006, 2010 Dan McMahill
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02111-1301 USA.


# This script is used to process annotation files from the PCB
# program (http://pcb.gpleda.org) to backannotate
# changes to gEDA schematics.
#
# It is heavily based on the pads_backannotate program which is also 
# part of gEDA

# for parsing input options
use Getopt::Long;

# for ceil function
use POSIX;

# don't allow -he to be interpreted as --help
$Getopt::Long::autoabbrev=0;

# make the options be case sensitive
$Getopt::Long::ignorecase=0;

&GetOptions( ('h|help' => \&usage,
	      'n|nocopy' => \$nocopy,
	      'v|verbose' => \$verbose,
	      'V|version' => \&version
	     ) );

usage() if $Getopt::Long::error;
usage() unless @ARGV;

# Annotation file name
$eco = shift( @ARGV );

# if no schematic names follow, exit
usage() unless @ARGV;

# make sure the input netlist exists and we can open it
$i = 0;
while( @ARGV ) {
  $fname[$i] = shift( @ARGV );
  die "Schematic file $fname[$i] does not exist or can not be read"
    unless -r $fname[$i];
  $i++;
}

$filecnt = $i;


if( $verbose ){ print "Loading PCB annotation file file: $eco\n"; }
my $eco_state="DEFAULT";
open( ECO, "$eco" ) or die "Can't open PCB annotation file $eco !\n";

my $ver = 0;
while( $line = <ECO> ) {
    if( $line =~ /^\*FILEVERSION\* / ) {
	$ver = $line;
	$ver =~ s/^\*FILEVERSION\*\s*//;
	$ver =~ s/[^0-9]*$//;
    } elsif( $line =~ /^\*VERSION\* 20060814/ ) {
	$ver = 20060814;
    }
}
close( ECO );

if( $ver == 0) {
    print "ERROR:  Unable to determine annotation file version.\n";
    exit( 1 );
}

print "Annotation file version is $ver\n" if $verbose ;
if( $ver != 20061031 && $ver != 20060814 ) {
    print "ERROR:  This version of the program is unable to process\n";
    print "        PCB annotation files of version $ver.\n";
    exit( 1 );
}


# reopen and parse the file this time
open( ECO, "$eco" ) or die "Can't open PCB annotation file $eco !\n";
while( $line = <ECO> ) {
    #  if( $verbose ){ print "$line\n"; }
    if( $line =~ /^\*COMMENT\*/ ) {
	print "Skipping comment line: $line\n";
	next;
    } elsif( $line =~ /^\*VERSION\* 20060814/ || $line =~ /^\*FILEVERSION\* [0-9]*/) {
	# the version line.  Note that the very first version of the annotation
	# file used *VERSION* but it was quickly changed to FILEVERSION to avoid
	# confusing it with the program version.
	next;
    } elsif( $line =~ /^\*RENAME\*/ ) {
	# rename refdes in design (forward/backward annotation)
	#$eco_state="RENAME";
	print " *RENAME* (Refdes Renumber)\n" if $verbose;
	$line =~ s/^\*RENAME\*\s*//;
	parseRENAME($line);
	next;
    } elsif( $line =~ /^\*WARN\*/ ) {
	# A warning generated by PCB
	print "Found the following warning in the annotation file:\n";
	print "$line\n";
	next;
    } elsif( $line =~ /^\*/ ) {
	print "WARNING:  Unknown command line:\n";
	print "          $line\n";
	$eco_state="DEFAULT";
	next;
    } else {
	# this must be a data line
	#if( $verbose ){ print "Processing data line: $line"; }
    }

}
close( ECO );

for($i=0; $i < $filecnt; $i++) {
  print "Processing schematic file #", $i+1, ": $fname[$i]\n";
  open(NETLIST,"$fname[$i]") or
    die "Can't open schematic $fname[$i]: $!\n";

  # open output netlist
  $outfname="$fname[$i].new";
  open(OUTSCH,">$outfname") or die "Can't open $outfname: $!\n";

    while($line = <NETLIST>) {
	$line = executeRENAME($line);
	print OUTSCH "$line";
    }
    close(NETLIST);
    close(OUTSCH);

    if( $nocopy ) {
      print "Leaving page #",$i+1," output in $outfname\n";
    }
    else {
      system("mv $outfname $fname[$i]");
    }

}

print "\n---- Gate Swapping ----\n";
executeSWPGATES();
print "\n---- Pin  Swapping ----\n";
executeSWPPINS();
print "\nBackannotation finished ", scalar localtime, "\n";

exit;


#######################################################################
#
# Subroutines
#
#######################################################################

#---------------------------------
# executeRENAME(line)
#---------------------------------

sub executeRENAME {
  my $line = shift(@_);

  return $line unless defined %cmd_rename;
  return $line unless $line =~ /^refdes=/;

  # pick out the reference designator
  $refdes = $line;
  $refdes =~ s/^refdes=//;
  $refdes =~ s/[\r\n]*$//;

  # see if its listed in our hash of reference designators to be
  # renamed
  return $line unless exists $cmd_rename{$refdes};

  print "executeRENAME():  Renaming $refdes to $cmd_rename{$refdes}\n" 
      if $verbose;
  return "refdes=$cmd_rename{$refdes}\n";

}

#---------------------------------
# executeSWPGATES()
#---------------------------------

sub executeSWPGATES {
  my $key;

  foreach $key (keys %cmd_swap_gates ) {
    print "Please manually swap gates $key and  $cmd_swap_gates{$key}\n";
  }
}

#---------------------------------
# executeSWPPINS()
#---------------------------------

sub executeSWPPINS {
  my $key;
  my @pins;

  foreach  $key (keys %cmd_swap_pins ) {
    @pins = split '\.',,$cmd_swap_pins{$key};
    print "Please manually swap pins $pins[0] and $pins[1] on $key\n";
  }
}

#---------------------------------
# parseRENAME(line)
#---------------------------------

sub parseRENAME {
  my $line = shift(@_);
  my @refs;
  @refs = split ' ',,$line;

  $refs[0] =~ s/"//g; # "
  $refs[1] =~ s/"//g; # "

  print "parseRENAME():  Scheduling rename of $refs[0] to $refs[1]\n" 
      if $verbose;
  $cmd_rename{$refs[0]} = $refs[1];

}

#---------------------------------
# parseSWPGATES(line)
#---------------------------------

sub parseSWPGATES {
  my $line = shift(@_);
  my @refs;
  @refs = split ' ',,$line;

  print "parseSWPGATES():  Scheduling swap of gate $refs[0] with $refs[1]\n" 
      if $verbose;
  $cmd_swap_gates{$refs[0]} = $refs[1];

}

#---------------------------------
# parseSWPPINS(line)
#---------------------------------

sub parseSWPPINS {
  my $line = shift(@_);
  @refs = split ' ',,$line;
  @pins = split '\.',,$refs[1];

  print "parseSWPPINS():  Scheduling swap of pins on $refs[0] : pins ",
  "$pins[0] and $pins[1]\n" if $verbose;
  $cmd_swap_pins{$refs[0]} = $refs[1];

}

#---------------------------------
# usage()
#
# prints program usage
#---------------------------------

sub usage {
  my $pname = $0;
  $pname =~ s/.*\///g;

  print <<EOF;

Usage:

  $pname [-n|--nocopy] [-v|--verbose] change.log file1.sch [file2.sch ...]
  $pname -h|--help\n
  $pname -V|--version\n

$pname reads a PCB annotation file and backannotates changes\n
to a gEDA schematic.

$pname accepts the following options:

  -h
  --help      Displays this help message.

  -n
  --nocopy    If given, this flag leaves the modified files in new files
              whose names are generated by appending a \".new\" to the
              original file names.  The default is to overwrite the original.

  -v
  --verbose   Enables verbose output.

  -V
  --version   Shows the version of this program.


$pname was written by Dan McMahill <dmcmahill\@netbsd.org>


EOF

  exit;
}

#---------------------------------
# version()
#
# prints program version
#---------------------------------

sub version {
  open(PROG,"$0") or die "Could not open \"$0\" to find version\n\n";
  my $pname = $0;
  $pname =~ s/.*\///g;

  while($line = <PROG>) {
    if( $line =~ /^#\s*\$Id.*\$/) {
      @words = split ' ',,$line;
      $version = $words[3];
      $date = $words[4];
      print "$pname ($0):  Version $version, $date\n";
      exit;
    }
  }
  print "Could not determine version of \"$0\"\n\n";
  exit;
}

