#!/usr/bin/perl -w

use strict;

#
# Some helpers to create gaf objects
#

package gaf;
use List::Util qw(max min);

sub version {
	print "v 20061020 1\n";
}

our $xoffset = 0;
our $yoffset = 0;
our $xsign = 1;
our $ysign = 1;
our $swapxy = 0;
our @stack;

sub moveto {
	($xoffset, $yoffset) = @_;
}

sub rmoveto {
	$xoffset += $_[0];
	$yoffset += $_[1];
}

sub gsave {
	push @stack, [$xoffset, $yoffset, $xsign, $ysign, $swapxy];
}

sub grestore {
	($xoffset, $yoffset, $xsign, $ysign, $swapxy) = @{pop @stack};
}

sub flipx { if ($swapxy) { $ysign = -$ysign } else { $xsign = -$xsign } }
sub flipy { if ($swapxy) { $xsign = -$xsign } else { $ysign = -$ysign } }
#sub swapxy { $swapxy = !$swapxy; ($xsign, $ysign) = ($ysign, $xsign) }
sub rot90 { $swapxy = !$swapxy; ($xsign, $ysign) = ($ysign, -$xsign) }

sub xy {
	my @xy = ($_[0] * $xsign, $_[1] * $ysign);
	@xy = reverse @xy if $swapxy;
	($xy[0] + $xoffset, $xy[1] + $yoffset)
}

sub xywh {
	my ($x,$y,$w,$h) = @_;
	# find other corner in absolute space
	my ($x2, $y2) = ($x + $w, $y + $h);
	# transform both corners
	($x, $y) = xy($x, $y);
	($x2, $y2) = xy($x2, $y2);
	# convert back to x/y/w/h format
	(min($x, $x2), min($y, $y2), abs($x - $x2), abs($y - $y2))
}

sub angle {
	($_[0] + ($swapxy ? 90 : 0)) % 360;
}

sub round {
	my $size = shift;

	$size = int(($size + 99) / 100) * 100;
}

sub line {
	my %v = (
		x1 => 0, y1 => 0, x2 => 0, y2 => 0,
		color => 3,
		width => 0,
		capstyle => 0,
		dashstyle => 0,
		dashlength => -1,
		dashspace => -1,
		@_
	);
	printf "L %d %d %d %d %d %d %d %d %d %d\n",
		xy($v{x1}, $v{y1}),
		xy($v{x2}, $v{y2}),
		$v{color}, $v{width},
		$v{capstyle}, $v{dashstyle},
		$v{dashlength}, $v{dashspace};
}

sub box {
	my %v = (
		x => 0, y => 0,
		color => 3,
		lwidth => 0,
		capstyle => 0,
		dashstyle => 0,
		dashlength => 0,
		dashspace => 0,
		filltype => 0,
		fillwidth => -1,
		angle1 => -1,
		pitch1 => -1,
		angle2 => -1,
		pitch2 => -1,
		@_
	);
	printf "B %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
		xywh($v{x}, $v{y},
		     $v{width}, $v{height}),
		$v{color}, $v{lwidth},
		$v{capstyle}, $v{dashstyle},
		$v{dashlength}, $v{dashspace},
		$v{filltype}, $v{fillwidth},
		$v{angle1}, $v{pitch1},
		$v{angle2}, $v{pitch2};
}


sub circle {
	my %v = (
		x => 0, y => 0,
		color => 3,
		width => 0,
		capstyle => 0,
		dashstyle => 0,
		dashlength => 0,
		dashspace => 0,
		filltype => 0,
		fillwidth => -1,
		angle1 => -1,
		pitch1 => -1,
		angle2 => -1,
		pitch2 => -1,
		@_
	);
	printf "V %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
		xy($v{x}, $v{y}),
		$v{radius},
		$v{color}, $v{width},
		$v{capstyle}, $v{dashstyle},
		$v{dashlength}, $v{dashspace},
		$v{filltype}, $v{fillwidth},
		$v{angle1}, $v{pitch1},
		$v{angle2}, $v{pitch2};
}


# 2 _____    5    _   8
#  |_   _|____  _| |_
# 1  | |/ _ \4\/ / __|7
#    | |  __/>  <| |_
# 0  |_|\___/3/\_\\__|6

sub text {
	my %v = (
		x => 0, y => 0,
		color => 9,
		size => 10,
		visibility => 1,
		show_name_value => 1,
		angle => 0,
		alignment => 0,
		@_
	);
	my @dummy = split('\n', $v{str});
	$v{num_lines} = scalar(@dummy);

	#$v{alignment} =~ y/012678/678012/ if $xsign < 0;
	#$v{alignment} =~ y/258036/036258/ if $ysign < 0;

	$v{alignment} =~ y/012678/678012/ if $xsign < 0;
	$v{alignment} =~ y/258036/036258/ if $ysign < 0;

	# wrong!
	#$v{alignment} =~ y/258147036/876543210/ if $swapxy;

	printf "T %d %d %d %d %d %d %d %d %d\n%s\n",
		xy($v{x}, $v{y}),
		$v{color}, $v{size},
		$v{visibility}, $v{show_name_value},
		angle($v{angle}), $v{alignment},
		$v{num_lines}, $v{str};
}

sub textwidth {
	my $s = shift;

	$s =~ s/.*?=//;
	my $narrow = ($s =~ y/I1i:;!.,\/][}{|//d);
	length($s) * 115 + $narrow * 50
}

sub hidden_attribute {
	my %v = (
		x => 0,
		y => 0,
		visibility => 0,
		show_name_value => 0,
		color => 5,
		@_
	);
	text(%v);
}

sub pin {
	my %v = (
		x1 => 0, y1 => 0, x2 => 0, y2 => 0,
		color => 1,
		pintype => 0,
		whichend => 1,
		@_
	);
	printf "P %d %d %d %d %d %d %d\n",
		xy($v{x1}, $v{y1}),
		xy($v{x2}, $v{y2}),
		$v{color}, $v{pintype},
		$v{whichend};
}

sub attributes {
	if (@_) {
		print "{\n";
		foreach my $a (@_) {
			text(size => 8, color => 5, %{$a});
		}
		print "}\n";
	}
}

#
# Every pin in this symbol is represented by an instance of Pin
#
package Pin;

our @realsides = ('top', 'right', 'bottom', 'left');
our @sides = (@realsides, 'labels', 'attr');

our %counts;
our $npins;

sub new {
	my $class = shift;
	my $s = shift;
	my $l = shift;

	no warnings "uninitialized";
	my $idx = $counts{$s}++;
	my $pinseq = ++$npins;

	bless { side => $s,
		label => $l,
		idx => $idx,
		bus => 0,	# default
		pinnumber => 0,	# default
		pinseq => $pinseq,
		style => 'pin',
		@_		# overrides defaults
	}, $class
}

sub print {
	my $self = shift;

	printf "%3d %s %d %d %s `%s'\n", $self->{pinnumber}, $self->{side}, $self->{idx}, $self->{bus}, $self->{style}, $self->{label};
}

sub width {
	my $self = shift;

	gaf::textwidth($self->{label});
}

sub label {
	my $self = shift;

	my $l = $self->{label};
	# keep "primary" pin designator by edge
	$l = join('/', reverse split('/', $l)) if $self->{side} =~ /right|top/;
	$l
}

#
# A collection of pins is inserted into a Layout.  Subclasses of Layout
# do the heavy lifting.
#
package Layout;

sub new {
	my $class = shift;
	my $pins = shift;

	bless { pins => $pins,
		@_		# overrides defaults
	}, $class
}

#
# Layout for one side of my layout style
#
package Layout::BjjSide;
use List::Util qw(max min);

sub new {
	my $class = shift;
	my $side = shift;
	my $pins = shift;

	bless { side => $side,
		pins => $pins,
		@_		# overrides defaults
	}, $class
}

sub busses {
	my $self = shift;

	my %seen = (0=>1);	# ignore bus 0
	grep {!$seen{$_}++} map { $_->{bus} } @{$self->{pins}};
}

sub nbus {
	my $self = shift;

	scalar (grep { $_->{bus} == 0 } @{$self->{pins}}) +
	scalar ($self->busses)
}

sub height {
	my $self = shift;

	return 0 unless $self->npins;
	my $nbus = $self->nbus;
	($self->npins - 1) * $Layout::Bjj::pinspacing +
	($nbus - 1) * $Layout::Bjj::pinspacing;
}

sub width {
	my $self = shift;

	max(0, map { $_->width } @{$self->{pins}})
}

sub npins {
	my $self = shift;

	scalar @{$self->{pins}}
}

sub init_y {
	my $self = shift;

	my $lastbus;
	my $y = 0;
	my $ps = $Layout::Bjj::pinspacing;

	foreach my $pin (@{$self->{pins}}) {
		$y += $ps if (defined $lastbus and $pin->{bus} != $lastbus);
		$lastbus = $pin->{bus} || -1;

		$pin->{y} = $y;
		$y += $ps;
	}
	$y -= $ps;
	# flip since gschem has math-like coords (not screen-like)
	foreach my $pin (@{$self->{pins}}) {
		$pin->{y} = $y - $pin->{y};
	}
}

sub render {
	my $self = shift;

	foreach my $pin (@{$self->{pins}}) {
		gaf::pin(y1 => $pin->{y}, y2 => $pin->{y}, x2 => 300);
		gaf::attributes(
			{ str => "pinlabel=" . $pin->label,
			  y => $pin->{y}, x => -50, alignment => 7 },
			{ str => "pinnumber=" . $pin->{pinnumber},
			  y => $pin->{y} + 25, x => 50,
			  alignment => $gaf::swapxy ? 2 : 0, size => 8 },
			{ str => "pinseq=" . $pin->{pinseq},
			  y => $pin->{y} + 25, x => 50,
			  alignment => $gaf::swapxy ? 2 : 0, size => 8,
			  visibility => 0 }
		);
	}
}

#
# Layout in my style by treating each side as a Layout::BjjSide
#
package Layout::Bjj;
use vars qw(@ISA);
@ISA = qw(Layout);
use List::Util qw(max min);

our $boxpadx = 0;
our $boxpady = 100;
our $gapx = 200;
our $gapy = 200;
our $pinspacing = 200;

sub new {
	my $class = shift;

	my $self = $class->SUPER::new(@_);
	my $pins = $self->{pins};
	my %sides;
	for my $side (@Pin::sides) {
		my @pinsside = grep { $_->{side} eq $side } @{$pins};
		$self->{$side} = Layout::BjjSide->new($side, \@pinsside);
	}
	$self
}

sub print {
	my $self = shift;

	foreach my $s (keys %{$self->{sides}}) {
		my $side = $self->{sides}->{$s};
		print $side->{side}, " has ", join(" ",$side->busses), "\n";
	}
}

sub layout {
	my $self = shift;

	# do initial distribution along sides
	map { $self->{$_}->init_y } @Pin::realsides;

	my $name_center = $self->{bottom}->npins > 0;
	$self->{name_center} = $name_center;

	my $width = max($self->{left}->width + $self->{right}->width +
			($name_center ? $self->{labels}->width : 0),
			$self->{top}->height, $self->{bottom}->height);
	$width += $gapx if ($self->{left}->npins and $self->{right}->npins);

	my $height = max($self->{top}->width + $self->{bottom}->width,
			$self->{left}->height, $self->{right}->height,
			($name_center ? $self->{labels}->height : 0));
	$height += $gapy if ($self->{top}->npins and $self->{bottom}->npins);

	$width = gaf::round($width) + $boxpadx * 2;
	$height = gaf::round($height) + $boxpady * 2;
	$self->{width} = $width;
	$self->{height} = $height;

	my $yoffset = $self->{labels}->height;
	$self->{yoffset} = $yoffset;
}

sub render {
	my $self = shift;

	gaf::gsave;

	# Move a bit to make room for lefthand pins and labels
	gaf::moveto(300, $self->{yoffset});
	gaf::box(width => $self->{width}, height => $self->{height});

	# Labels appear at the lower left of the part box if possible
	gaf::gsave;
	my $a;
	if ($self->{name_center}) {
		gaf::rmoveto($self->{width} / 2,
			     ($self->{height} + $self->{labels}->height) / 2);
		$a = 4;
	} else {
		gaf::rmoveto(0, -50);
		$a = 2;
	}
	foreach my $label (@{$self->{labels}->{pins}}) {
		gaf::text(str => $label->{label}, color => 5, alignment => $a);
		gaf::rmoveto(0, -$pinspacing);
	}
	gaf::grestore;

	# Attributes are hidden next to the labels (note that they
	# may extend into negative coords)
	gaf::gsave;
	gaf::rmoveto(-100, -50);
	foreach my $attr (@{$self->{attr}->{pins}}) {
		gaf::hidden_attribute(str => $attr->{label}, alignment => 8);
		gaf::rmoveto(0, -$pinspacing);
	}
	gaf::grestore;

	gaf::gsave;
	gaf::rmoveto($self->{width}, $boxpady + $pinspacing / 2);
	$self->{right}->render;
	gaf::grestore;

	gaf::gsave;
	gaf::rmoveto(0, $boxpady + $pinspacing / 2);
	gaf::flipx;
	$self->{left}->render;
	gaf::grestore;

	gaf::gsave;
	gaf::rmoveto($self->{width} - ($boxpadx + $pinspacing / 2), $self->{height});
	gaf::rot90;
	$self->{top}->render;
	gaf::grestore;

	gaf::gsave;
	gaf::rmoveto($self->{width} - ($boxpadx + $pinspacing / 2), 0);
	gaf::rot90;
	gaf::flipy;
	$self->{bottom}->render;
	gaf::grestore;

	gaf::grestore;
}

package ::main;

my $side = "none";
my $bus = 0;
my $nbus = 0;
my @pins;

#
# Parse input
#
while(<>)
{
	chomp;
	next if /^#/;

	if (/^\[(.*)\]/) {
		$side = $1;
		$bus = 0;
		next;
	}

	if (/^\.bus/) {
		$bus = ++$nbus;
		next;
	}

	if (/^$/) {
		$bus = 0;
		next;
	}

	my $s = $side;
	$s = "attr" if $side eq "labels" and s/^!\s+//;

	my $pin;

	if ($side eq "labels") {
		$pin = Pin->new($s, $_, style => $s);
	} else {
		die "missing pin number" unless s/(\d+)\s*//;
		my $n = $1;

		my $style = "";
		$style = $1 if s/^([!>])\s+//;

		$pin = Pin->new($s, $_, pinnumber => $n, bus => $bus);
		if ($style eq '!') {
			$pin->{style} = 'invert';
		} elsif ($style eq '>') {
			$pin->{style} = 'clock';
		}
	}

	push @pins, $pin;
}

#map { $_->print} @pins;

my $layout = Layout::Bjj->new(\@pins);
$layout->layout;
#$layout->print;

gaf::version;
$layout->render;
