#!/usr/bin/perl -w
#
# Copyright (C) 2003 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 renumber devices in a gschem (part of gEDA)
# schematic.  This program works on single as well as multi-sheet 
# schematics.
#
# Usage:
#    renum.pl file1.sch [file2.sch [file3.sch ...]]
#

# 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;

#  my $clear; # reset all refdes
&GetOptions(("help|h" => \&usage, 
	     "nocopy" => \$nocopy,
	     "pgskip:100" => \$pgskip,
	     "verbose" => \$verbose,
	     "version" => \&version,
             "clear"   => \$clear,
	     "gentle" => \$gentle,
	     "force" => \$force,
	     ));

usage() if $Getopt::Long::error;


# By default pgskip is empty.  But if the user gives
# the --pgskip option with no value, then assign pgskip
# to 100.  
if (defined($pgskip) && ($pgskip == 0)) {  
    $pgskip = 100; 
}


# This makes --gentle the default behavior.  To 
# force refdes renumbering, use --force.
$gentle = 1;
if ( defined($force) || defined($clear) ) {
    undef($gentle);
}

# Print usage string in event user didn't call out any args
usage() unless @ARGV;

# Make sure the input schematic exists and we can read it.
# Also count number of files to open.
$i=0;
while(@ARGV) {
  $fname[$i]=shift(@ARGV);
  die_sdb(1, "Schematic file $fname[$i] does not exist or can not be read")
    unless -r $fname[$i];
  $i++;
}
$filecnt = $i;  # number of files to open
if($verbose) {print "Found $filecnt files to process\n";}

# To refdes an entire design, we normally just loop through all files
# and process each one in order.  However if --gentle option is set, 
# we need to make two passes through all files.  First to create a hash
# of the highest refdeses used, and second to update unnumbered 
# refdeses.  The hash is used to know where to start the refdes numbering.
#
# It gets more complicated.  If --pgskip is not set, then we only need
# to find the highest refdes overall.  But if --pgskip is set, then 
# we need to know the highest refdes on each *page*.  This is accomplished
# on the first scan by creating the variable $highdev{$i, $pre} when --pgskip
# is defined, but using the variable $highdev{$pre} when it is not.
#
if (defined($gentle)) {  # First scan through design to build hash
    for($i=0; $i < $filecnt; $i++) {   # $i is the file index.
	print "Scanning input file #".($i+1).": $fname[$i]\n";
	open(NETLIST,"$fname[$i]") or die_sdb(1, "Can't open $fname[$i]: $!\n");

	while($line = <NETLIST>) {  # Iterate over lines found in .sch file.
	    unless( $line =~ /^refdes/) {
		next;
	    }
	    # Found refdes line.  Eat the newline.
	    $line =~ s/\n//;

	    # Now extract component prefix and suffix.
	    if($verbose) {print ("Matching $line ....\n");}
	    $line =~ /refdes=([a-zA-Z_]+)(\d+|\??)/i;
	    my $pre = $1;
	    my $suf = $2 ? $2 : "?";
	    if($verbose) {print "Refdes line \"$line\" has pre=$pre suf=$suf\n";}

	    # Now put highest refdes found into $highdev hash.
	    if (defined($pgskip)) {
		if ($suf eq "?") {
		    # Part has no pre-existing refdes.
		    # Skip to next part.
		    next;
		} elsif (!defined($highdev{$i, $pre})) { 
                    # refdes is not cached, but refdes exists on part.  
		    # Create new cache entry
		    if ($suf < $pgskip) {
			# if pgskip=100, on pg 1, and suf = 23, set highdev=123.
			$highdev{$i, $pre} = ($i+1)*$pgskip + $suf;
		    } elsif ( ( ($i+1)*$pgskip < $suf) && 
			      ( ($i+2)*$pgskip > $suf) ) { 
			# if pgskip=100, we're on pg 2, and suf = 204, set highdev=204.
			$highdev{$i, $pre} = $suf;
		    } else {
			# I don't know what to do!
			die_sdb(3, "Encountered refdes incompatable with --pgskip setting.  Refdes=$pre$suf.\n");
		    }
			
		} elsif ($highdev{$i, $pre} < $suf) {
		    # part has refdes and refdes is higher than cached
                    # value.  Store this new value as highest value.
		    $highdev{$i, $pre} = $suf;
		} elsif ($highdev{$i, $pre} >= $suf) {
		    # part has refdes, but refdes is lower than cached
                    # high value.  Leave it alone.
		    next;
		} else {
		    # Should never get here
		    die_sdb(4, "Invalid refdes with --pgskip set!  Exiting....\n");
		}

		if($verbose) {
		    print "On page ".($i+1).", caching highest refdes $pre$highdev{$i, $pre}\n";
		}
	    } else {
		if ($suf eq "?") {
		    # Part has no pre-existing refdes.
		    next;
		} elsif (!defined($highdev{$pre}) ) { 
                    # refdes is not cached, and refdes exists on part.  Create new
                    # cache entry
		    $highdev{$pre} = $suf;
		} elsif ($highdev{$pre} < $suf) {
		    # part has refdes and refdes is higher than cached
                    # value.  Store this new value as highest value.
		    $highdev{$pre} = $suf;
		} elsif ($highdev{$pre} >= $suf) {
		    # part has refdes, but refdes is lower than cached
                    # high value.  Leave it alone.
		    next;
		} else {
		    # Should never get here
		    die_sdb(4, "Invalid refdes!  Exiting....\n");
		}
		if($verbose) {
		    print "Caching highest refdes $pre$highdev{$pre}\n";
		}
	    }
	    
	}  # while($line = <NETLIST>)
	close(NETLIST);
    }  # for($i=0; $i < $filecnt; $i++)
} # if (defined($gentle))


# OK, now we can read through the netlist file again, assign refdeses,
# and write the output file.
for($i=0; $i < $filecnt; $i++) {   # $i is the file index.
    print "Now processing input file #".($i+1).": $fname[$i]\n";
    open(NETLIST,"$fname[$i]") or die_sdb(1, "Can't open $fname[$i]: $!\n");
    
    # open output netlist
    $outfname="$fname[$i].renum";
    open(OUTNET,">$outfname") or die_sdb(2, "Can't open $outfname: $!\n");
    
    # Iterate over lines found in .sch file.
    while($line = <NETLIST>) {  
	unless( $line =~ /^refdes/) {
	    print OUTNET $line;
	    next;
	}

	# Found refdes line.  Eat the newline.
	$line =~ s/\n//;
	
	# Now extract component prefix and suffix.
	if($verbose) {print ("Processing $line ....\n");}
	$line =~ /refdes=([a-zA-Z_]+)(\d+|\??)/i;
	my $pre = $1;
	my $suf = $2 ? $2 : "?";
	if($verbose) {print "Refdes line \"$line\" has pre=$pre suf=$suf\n";}

	# Now finally update refdes
	if ($clear) {
	    # Just overwrite all refdeses upon clear
	    if($verbose) {print ("Clearing refdes=$pre$suf\n");}
	    print OUTNET "refdes=$pre?\n";
	} elsif (defined($pgskip) && defined($gentle)) {
	    # If highdev exists, then start devcnt there, otherwise, start
	    # devcnt at correct value for each page.
	    if (!defined($devcnt{$i, $pre}) && defined($highdev{$i, $pre})) {
		$devcnt{$i, $pre} = $highdev{$i, $pre};
	    } elsif (!defined($devcnt{$i, $pre}) && !defined($highdev{$i, $pre})) {
		$devcnt{$i, $pre} = ($i+1)*$pgskip ;
	    }
	    
	    if ($suf eq "?") {
		$devcnt{$i, $pre}++;
		if ($devcnt{$i, $pre} >= ($i+2)*$pgskip) {
		    print STDERR "ERROR:  You are numbering more than $pgskip elements with\n";
		    print STDERR "prefix $pre on this sheet.  You will need to either\n";
		    print STDERR "reduce this number or specify a larger step with the\n";
		    print STDERR "--pgskip argument.\n";
		    die_sdb(3, "");
		}
		print "Renumbering $line to $pre$devcnt{$i, $pre}\n" if($verbose);
		print OUTNET "refdes=$pre$devcnt{$i, $pre}\n";
	    } else {
		print "Leaving line=$line alone\n" if($verbose);
		print OUTNET "refdes=$pre$suf\n";
	    }

	} elsif (defined($pgskip) && !defined($gentle)) {
	    if (!defined($devcnt{$i, $pre})) {
		$devcnt{$i, $pre} = ($i+1)*$pgskip ;
	    }
	    $devcnt{$i, $pre}++;
	    if ($devcnt{$i, $pre} >= ($i+2)*$pgskip) {
		print STDERR "ERROR:  You are numbering more than $pgskip elements with\n";
		print STDERR "prefix $pre on this sheet.  You will need to either\n";
		print STDERR "reduce this number or specify a larger step with the\n";
		print STDERR "--pgskip argument.\n";
		die_sdb(3, "");
	    }
	    print "Renumbering $line to $pre$devcnt{$i, $pre}\n" if($verbose);
	    print OUTNET "refdes=$pre$devcnt{$i, $pre}\n";

	} elsif (!defined($pgskip) && defined($gentle)) {
	    if (!defined($devcnt{$pre}) && defined($highdev{$pre})) {
		$devcnt{$pre} = $highdev{$pre};
	    } elsif (!defined($devcnt{$pre}) && !defined($highdev{$pre})) {
		$devcnt{$pre} = 0;
	    }
	    if ($suf eq "?") {
		$devcnt{$pre}++;
		print "Renumbering $line to $pre$devcnt{$pre}\n" if($verbose);
		print OUTNET "refdes=$pre$devcnt{$pre}\n";
	    } else {
		print "Leaving line=$line alone\n" if($verbose);
		print OUTNET "refdes=$pre$suf\n";
	    }
	} elsif (!defined($pgskip) && !defined($gentle)) {
	    if (!defined($devcnt{$pre})) {
		$devcnt{$pre} = 0 ;
	    }
	    $devcnt{$pre}++;
	    print "Renumbering $line to $pre$devcnt{$pre}\n" if($verbose);
	    print OUTNET "refdes=$pre$devcnt{$pre}\n";

	} else {
	    die_sdb(4, "Invalid state when trying to update refdes!  Exiting.....\n");
	}
    }  # while($line = <NETLIST>)
    close(NETLIST);
    close(OUTNET);

    
    # make this an option to not overwrite the original
    if( $nocopy ) {
	print "Leaving page #",$i+1," output in $outfname\n";
	}
    else {
	system("mv $outfname $fname[$i]");
    }
}

exit(0);


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

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

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

  print "Usage:\n\n";
  print "\t$pname [--nocopy] [--pgskip [number] ] file1 [file2 [file3 ... ] ]\n";
  print "\t$pname --help | -h\n";
  print "\t$pname --version\n";
  print "\n";
  print "$pname reads a gschem schematic file or files and renumbers all reference\n";
  print "designators.  The reference designators are numbered starting with 1 and the\n";
  print "old schematic file is replaced by the modified schematic file.\n";
  print "\n";
  print "$pname accepts the following options:\n";
  print "\n";
  print "    --help, -h  Displays this help message.\n";
  print "\n";
  print "    --nocopy    If given, this flag leaves the modified files in new files\n";
  print "                whose names are generated by appending a \".renum\" to the\n";
  print "                original file names.  The default is to overwrite the original.\n";
  print "\n";
  print "    --pgskip    When this flag is used, components on the first schematic sheet\n";
  print "                are numbered starting with 101.  On the second sheet, they start\n";
  print "                with 201, etc  Specifying a value gives the step between pages.\n";
  print "                For example --pgskip 10 will start with 11, 21, 31, etc.\n";
  print "\n";
  print "    --gentle    This flag tells refdes_renum to leave any refdeses\n";
  print "                alone if they already have numbers.  Use this option to number\n";
  print "                new components in a schematic which has already been numbered.\n";
  print "                Note that --gentle is set by default!\n";
  print "\n";
  print "    --force     Set this flag to renumber all refdeses, whether they are already\n";
  print "                numbered or not.\n";
  print "\n";
  print "    --verbose   Enables verbose output.\n";
  print "\n";
  print "    --version   Shows the version of this program.\n";
  print "\n\n";
  print "Return codes:\n\n";
  print "\t$pname returns the following codes to the shell upon completion:\n";
  print "\n";
  print "          0   Program ran successfully.\n";
  print "          1   Error opening or reading input file.\n";
  print "          2   Error opening or writing output file.\n";
  print "          3   Too many components for --pgskip setting.\n";
  print "          4   Internal error (program bug encountered).\n";
  print "\n";
  print "Usage examples:\n\n";
  print "\tTo renumber the reference designators in a single page schematic\n";
  print "\tmysch.sch:\n";
  print "\n";
  print "\t\t$pname mysch.sch\n";
  print "\n\n";
  print "\tTo renumber the reference designators in schematic pages pg1.sch,\n";
  print "\tpg2.sch and pg3.sch, with the reference designators on pg1.sch\n";
  print "\tbeginning at 101, the designators on pg2.sch beginning at 201,\n";
  print "\tand the designators on pg3.sch beginning at 301:\n";
  print "\n";
  print "\t\t$pname --pgskip pg1.sch pg2.sch pg3.sch\n";
  print "\n\n";
  print "$pname was written by Dan McMahill <dmcmahill\@netbsd.org>\n";
  print "\n\n";
  exit(0);
}

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

sub version {
  open(PROG,"$0") or die_sdb(4, "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(0);
    }
  }
  die_sdb(4, "Could not determine version of \"$0\"\n\n");
}

#-------------------------------------------
# die_sdb(rc, string)
#
# Local version of die.  Sets return code
# for shell, then calls die(string)
#--------------------------------------------
sub die_sdb {
    my $rc = $_[0];
    my $string = $_[1];
    $! = $rc;
    die($string);
}

# ----------------------------------------------
#
# Change Log
#
# $Log$
# Revision 1.8  2007-04-17 20:19:23  pcjc2
# Merge changes from noscreen branch
#
# Revision 1.2.6.2  2007/04/17 16:19:01  pcjc2
# Sync with trunk
#
# Revision 1.7  2007/04/15 23:20:31  sdb
# Made --gentle the default behavior of refdes_renum.
#
# Revision 1.7  2007/04/15 SDB
# Made --gentle default behavior.  Now user must use --force
# to get old behavior, which renumbers all refdeses whether
# already numbered or not.
#
# Revision 1.6  2007/04/15 00:42:22  sdb
# Added --gentle flag to refdes_renum, which won't renumber refdeses
# which already have a number.
#
# Revision 1.5  4.9.2007 sdb.
# Added --gentle flag, and include logic to handle case
# where user mixes --gentle with --pgskip.
#
# Revision 1.4  2007/02/24 18:43:17  pcjc2
# Merge changes to date from noscreen branch.
#
# Revision 1.2.6.1  2007/02/11 23:59:10  pcjc2
# Sync with trunc
#
# Revision 1.3  2007/02/10 20:46:17  sdb
# Added --clear option to clear refdeses.  (* jcl *)
#
# Revision 1.2  2005/12/21 00:09:56  danmc
# - Fix a bug where when using the --pgskip option, components which were
#   present on page n, but not on pages n+1 through n+j, and present again
#   on page n+j+1 got numbered in a strange way.  For example, J101 on page
#   1, no connectors on page 2, J201 on page 3 instead of J301.  Noted by
#   Stuart Brorson.
#
# - While here allow the user to change the default increment from 100 to whatever
#   they want.
#
# - Also fix a bug where exactly 101 components with the same refdes prefix
#   would cause a duplicate refdes on the next page.
#
# Revision 1.1  2003/02/21 03:21:12  ahvezda
# Added scripts/refdes_renum written by Dan McMahill
#
#
#
