#!/usr/bin/perl -w

use strict;
package Pcb;
use Data::Dumper;
use Common;

use Exporter 5.57 'import';
our @EXPORT_OK =

 qw( %obj_type @obj_flag %obj_flag %obj_rflag %pcb_flag %layer_type %thermal_style %item %item_new %item_old %item_rev );

our %obj_type;
our %obj_rtype;
our @obj_flag;
our %obj_flag;
our %obj_rflag;
our %pcb_flag;
our %layer_type;
our %thermal_style;

INIT {
    flagInit();
    itemInit();
}
##############################
my $Lowres = 0; # globel value used by dim2nm()

%obj_type = (
    NO_TYPE           => 0x00000,
    VIA_TYPE          => 0x00001,
    ELEMENT_TYPE      => 0x00002,
    LINE_TYPE         => 0x00004,
    POLYGON_TYPE      => 0x00008,
    TEXT_TYPE         => 0x00010,
    RATLINE_TYPE      => 0x00020,

    PIN_TYPE          => 0x00100,
    PAD_TYPE          => 0x00200,
    ELEMENTNAME_TYPE  => 0x00400,
    POLYGONPOINT_TYPE => 0x00800,
    LINEPOINT_TYPE    => 0x01000,
    ELEMENTLINE_TYPE  => 0x02000,
    ARC_TYPE          => 0x04000,
    ELEMENTARC_TYPE   => 0x08000,

    LOCKED_TYPE       => 0x10000,
    NET_TYPE          => 0x20000,
    ARCPOINT_TYPE     => 0x40000,

);
$obj_type{PIN_TYPES}  = $obj_type{VIA_TYPE} | $obj_type{PIN_TYPE};
$obj_type{LOCK_TYPES} = $obj_type{VIA_TYPE} | $obj_type{LINE_TYPE} | $obj_type{ARC_TYPE} |
    $obj_type{POLYGON_TYPE} | $obj_type{ELEMENT_TYPE} | $obj_type{TEXT_TYPE} |
    $obj_type{ELEMENTNAME_TYPE} | $obj_type{LOCKED_TYPE};
$obj_type{ALL_TYPES}  = 0xfffff;

@obj_flag = (
    # for a given numeric value, use the most specific flag, in this case the first
    # with a matching type
#    noflag      => [ 0x0000, ],
    [ "pin"      ,  0x0001, $obj_type{ALL_TYPES} ],
    [ "via"      ,  0x0002, $obj_type{ALL_TYPES} ],
    [ "found"    ,  0x0004, $obj_type{ALL_TYPES} ],
    [ "hole"     ,  0x0008, $obj_type{PIN_TYPES} ],
    [ "nopaste"  ,  0x0008, $obj_type{PAD_TYPE} ],
    [ "rat"      ,  0x0010, $obj_type{RATLINE_TYPE} ],
    [ "pininpoly",  0x0010, $obj_type{PIN_TYPES} | $obj_type{PAD_TYPE} ],
    [ "clearpoly",  0x0010, $obj_type{POLYGON_TYPE} ],
    [ "hidename" ,  0x0010, $obj_type{ELEMENT_TYPE} ],
    [ "showname" ,  0x0020, $obj_type{ELEMENT_TYPE} ],
    [ "clearline",  0x0020, $obj_type{LINE_TYPE} | $obj_type{ARC_TYPE} | $obj_type{TEXT_TYPE} ],
    [ "fullpoly" ,  0x0020, $obj_type{POLYGON_TYPE}],
    [ "selected" ,  0x0040, $obj_type{ALL_TYPES} ],
    [ "onsolder" ,  0x0080, $obj_type{ELEMENT_TYPE} | $obj_type{PAD_TYPE} | $obj_type{TEXT_TYPE} |
		     $obj_type{ELEMENTNAME_TYPE} ],
    [ "auto"     ,  0x0080, $obj_type{ALL_TYPES} ],
    [ "square"   ,  0x0100, $obj_type{PIN_TYPES} | $obj_type{PAD_TYPE} ],
    [ "rubberend",  0x0200, $obj_type{LINE_TYPE} | $obj_type{ARC_TYPE} ],
    [ "warn"     ,  0x0200, $obj_type{PIN_TYPES} | $obj_type{PAD_TYPE} ],
    [ "usetherm" ,  0x0400, $obj_type{PIN_TYPES} | $obj_type{LINE_TYPE} | $obj_type{ARC_TYPE} ],
#   [ "onsilk"   ,  0x0400, ?? ]
    [ "octagon"  ,  0x0800, $obj_type{PIN_TYPES} | $obj_type{PAD_TYPE} ],
    [ "drc"      ,  0x1000, $obj_type{ALL_TYPES} ],
    [ "lock"     ,  0x2000, $obj_type{ALL_TYPES} ],
    [ "edge2"    ,  0x4000, $obj_type{ALL_TYPES} ],
#   [ "visit"    ,  0x8000, ?? ],
    [ "connected", 0x10000, $obj_type{ALL_TYPES} ],
);

%pcb_flag = (
    "shownumber"     => 0x00000001,
    "localref"       => 0x00000002,
    "checkplanes"    => 0x00000004,
    "showdrc"        => 0x00000008,
    "rubberband"     => 0x00000010,
    "description"    => 0x00000020,
    "nameonpcb"      => 0x00000040,
    "autodrc"        => 0x00000080,
    "alldirection"   => 0x00000100,
    "swapstartdir"   => 0x00000200,
    "uniquename"     => 0x00000400,
    "clearnew"       => 0x00000800,
    "snappin"        => 0x00001000,
    "showmask"       => 0x00002000,
    "thindraw"       => 0x00004000,
    "orthomove"      => 0x00008000,
    "liveroute"      => 0x00010000,
    "thindrawpoly"   => 0x00020000,
    "locknames"      => 0x00040000,
    "onlynames"      => 0x00080000,
    "newfullpoly"    => 0x00100000,
    "hidenames"      => 0x00200000,
    "autoburiedvias" => 0x00400000,
);

%layer_type = (
  copper  => 0,
  silk    => 1,
  mask    => 2,
  paste   => 3,
  outline => 4,
  route   => 5,
  keepout => 6,
  fab     => 7,
  assy    => 8,
  notes   => 9,
);

%thermal_style = (
    ""  => 1,
    "+" => 2,
    "S" => 3,
    "X" => 4,
    "t" => 5,
);

sub flagSplitSymbolic($) {
    my $str = shift;
    my @flag = ();
    my @thermal = ();

    $str =~ m/^"(.*)"$/;
    $str = $1;
    if (!defined($str) || $str eq "" || flagIsNumeric($str)) {
    } elsif ($str =~ m/thermal\(([^)]+)\)$/) {
	my $pp = $`;
	my $th = $1;
	@flag = split(/,/,$pp);
	if (defined($th)) {
	    @thermal = split(/,/,$th);
	}
    } else {
	@flag = split(/,/,$str);
    }
    (\@flag, \@thermal);
}
sub flagSplitSymbolic_old($) {
    my $str = shift;
    # split SFlag at , but keep thermal(xx,xx,xx) as one

    my @flag = ();
    if (!defined($str) || $str eq "" || flagIsNumeric($str)) {
    } elsif ($str !~ m/thermal/) {
	@flag = split(/,/, $str);
    } else {
	while (length($str)) {
	    if ($str !~ m/,/) {
		push @flag, $str;
		$str = "";
	    } elsif ( $str =~ m/^(thermal\([^)]+\))(,(.+))?/ ) {
		my $a = $1;
		my $b = $3;

		push @flag, $a;
		if (defined($b)) { $str = $b; }
		else { $str = ""; }
	    } elsif ( $str =~ m/^([a-z2]+)(,(.+))?/ ) {
		my $a = $1;
		my $b = $3;

		push @flag, $a;
		if (defined($b)) { $str = $b; }
		else { $str = ""; }
	    } else {
		# error ?
	    }
	}
    }
    @flag;
}

sub flagInit() {
    for (my $ix = 0; $ix < @obj_flag; $ix++) {
	$obj_flag{$obj_flag[$ix][0]} = $ix;

	my $id = $obj_flag[$ix][1];
	if (!defined($obj_rflag{$id})) {
	    $obj_rflag{$id} = [];
	}
	push @{$obj_rflag{$id}}, $ix;
    }
    for my $k (keys %obj_type) {
	my $v = $obj_type{$k};
	my $str = sprintf("%0x", $v);
	$obj_rtype{$v} = $k;
    }
}

sub flagShowType() {
    my @list = sort { $obj_type{$a} <=> $obj_type{$b} } keys %obj_type;
    print "\%Pcb::obj_type = {\n";
    for my $k (@list) {
	my $v = $obj_type{$k};
	printf "  %-17s => 0x%05x\n", $k, $v;
    }
    print "};\n";
}
sub flagShowFlagArr() {
    print "\@Pcb::obj_flag = {\n";
    for (my $ix = 0; $ix < @obj_flag; $ix++) {
	my @arr = @{$obj_flag[$ix]};
	$arr[0] = '"' . $arr[0] . '"';
	printf "  [ %-11s, 0x%05x, 0x%05x ],\n", @arr;
    }
    print "};\n";
}
sub flagShowFlagHash() {
    my @list = sort { $obj_flag{$a} <=> $obj_flag{$b} } keys %obj_flag;
    print "\%Pcb::obj_flag = {\n";
    for my $k (@list) {
	my $v = $obj_flag{$k};
	$k = '"' . $k . '"';
	printf "  %-11s => %2d,\n", $k, $v;
    }
    print "};\n";
}

sub flagType2Num($) {
    my $type = shift;

    $type = uc($type);
    my $num = Common::str2int($type);
    if (defined($num)) {
	my $chk = $obj_rtype{$num};
	if (defined($chk)) { return $num; }
	return undef;
    }
    if ($type =~ m/^\"?([A-Z]+)(_TYPES?)?\"?$/) {
	if ($2) {
	} else {
	    $type = $1 . "_TYPE";
	}
	$num = $obj_type{$type};
	return $num;
    }

    return undef;
}
sub flagType2Sym($) {
    my $type = shift;

    $type = uc($type);
    my $num = Common::str2int($type);
    if (defined($num)) {
	my $symb = $obj_rtype{$num};
	if (defined($symb)) { return $symb; }
	return undef;
    }

    if ($type =~ m/^\"?([A-Z]+)(_TYPES?)?\"?$/) {
	if ($2) {
	} else {
	    $type = $1 . "_TYPE";
	}
	my $symb = lc($type);
	$num = $obj_type{$type};
	if (defined($num)) { return $symb; }
    }

    return undef;
}

sub flagIsNumeric($) {
    my $flag = shift;
    my $num = Common::str2int($flag);
    if (!defined($num)) { return undef; }
    return $num;
}
sub flagIsSymbolic($) {
    my $flag = shift;

    $flag = lc($flag);
    if ($flag !~ m/^\"?([a-z,0-9+SX()]*)\"?$/) { return undef; }
    $flag = $1;
}

sub flagToNum($) {
    my $flag = shift;
    my $num = flagIsNumeric($flag);
    return $num if (defined($num));

    my $symb = flagIsSymbolic($flag);
    if (!defined($symb)) { $symb = ""; }
    # TODO
    my @fld = split(/,/, $symb);
    $num = 0;
    for (@fld) {
	my $ix = $obj_flag{$_};
	next if (!defined($ix));
	$num |= $obj_flag[$ix][1];
    }
    $num;
}
sub flagToSymb($$) {
    my $flag = shift;
    my $type = shift;

    my $Ntype = flagType2Num($type);
    if (!defined($Ntype)) { return ""; }

    my $symb = undef;
    my $num = flagIsNumeric($flag);
    if (defined($num)) {
	my @bit = Common::num2bits($num);
	my @ix = ();
	for my $bit (@bit) {
	    my $rr = $obj_rflag{$bit};
	    if (!defined($rr)) { next; }
	    my @arr = @{$rr};
	    for my $ix (@arr) {
		my $ty = $obj_flag[$ix][2];
		if ($ty & $Ntype) {
		    push @ix, $ix;
		    last;
		}
	    }
	}
	my @symb = map { $Pcb::obj_flag[$_][0]; } @ix;
	$symb = join(",", @symb);
    } else {
	$flag = flagIsSymbolic($flag);
	if (defined($flag)) {
	    my @fld = split(/,/, $flag);
	    my @nxt = ();
	    for (@fld) {
		my $ix = $obj_flag{$_};
		if (defined($ix)) {
		    push @nxt, $_;
		}
	    }
	    $symb = join(",", @nxt);
	}
    }

    return $symb;
}

sub flagIsSet($$) {
    my $flag = shift;
    my $test = shift;

    my $nflag = flagToNum($flag);
    my $ntest = flagToNum($test);

    if (!defined($nflag) || !defined($ntest)) { return undef; }
    return $nflag & $ntest;
}
sub flagToggle($$$) {
    my $flag   = shift;
    my $toggle = shift;
    my $type   = shift;

    my $nflag   = flagToNum($flag);
    my $ntoggle = flagToNum($toggle);
    my $ntype   = flagType2Num($type);

    if (!defined($nflag) || !defined($ntoggle) || !defined($ntype)) { return undef; }

    my $res = $nflag ^= $ntoggle;

    my $num = Common::str2int($flag);
    if (defined($num)) {
	# ok, keep the resulting flags numeric
	return $nflag;
    } else {
	# keep them symbolic
	my $symb = flagToSymb($nflag, $type);
	return $symb;
    }
}

sub Settings(;$) {
    my $file = shift // $ENV{HOME} . "/.pcb/settings";

    my @line;
    if (open(FH, $file)) {
	@line = <FH>;
	close(FH);
    } else {
	warn("cannot open \"$file\"");
	return ();
    }
    chomp $line[$#line];
    $line[$#line] .= "\n";

    @line;
}
sub settings(;$) {
    my $file = shift;
    my @line = Settings($file);
    my %settings;

    for my $line (@line) {
	next if ($line =~ m/^(#|$)/);
	if ($line =~ m/^\s*([a-zA-Z0-9-]+) ?= ?(.*)$/) {
	    my $k = $1;
	    my $v = $2;
	    $settings{$k} = $v;
	} else {
	    chomp $line;
	    print STDERR "strange line <$line>\n";
	}
    }

    %settings;
}
sub newlib_dir(%) {
    my %settings = @_;

    my $path = $settings{"lib-newlib"};
    my @dir = ();
    if ($path) {
	@dir = grep { -d $_  } split(/:/, $path);
	my @fail = grep { ! -d $_  } split(/:/, $path);
	if (@fail) {
	    warn("missing dirs from pcb settings: " . join(" ", @fail));
	}
    }

    @dir;
}
sub find_fp(@) {
    my @dir = @_;

    my @file = ();
    for my $dir (@dir) {
	push @file, Common::find($dir, '\*.fp');
    }
    @file;
}

sub find_pcb(@) {
    my @pcb;
    for my $dir (@_) {
	push @pcb, Common::find($dir  , '\*.pcb');
    }
    @pcb;
}

my $CurrentNumber = 0;
sub CurrentNumber(;$) {
    my $num = shift;

    if (defined($num) && $num =~ m/^\d+$/) {
	$CurrentNumber = $num;
    } else {
	$CurrentNumber++;
    }

    $CurrentNumber;
}

# Note: pcb files are not line oriened
our %item = (
    # items seems to be case dependent
    # 1, approximate order in a pcb file and what an item can contain
    # 2. number of args with [] symtax
    # 3. number of args with () symtax
    # 4, possible subitems
    # 5, space between item and [/(

    FileVersion => [  0, [ 1     ], [          ], [ "top",                    ], 0, ],
    PCB         => [  1, [ 3     ], [ 1,3      ], [ "top",                    ], 0, ],
    Grid        => [  2, [ 4     ], [ 3,4      ], [ "top",                    ], 0, ],
    PolyArea    => [  3, [ 1     ], [          ], [ "top",                    ], 0, ],
    Thermal     => [  4, [ 1     ], [          ], [ "top",                    ], 0, ],
    DRC         => [  5, [ 3,4,6 ], [          ], [ "top",                    ], 0, ],
    Flags       => [  6, [       ], [ 1        ], [ "top",                    ], 0, ],
    Groups      => [  7, [       ], [ 1        ], [ "top",                    ], 0, ],
    Styles      => [  8, [ 1     ], [          ], [ "top",                    ], 0, ],
    Symbol      => [  9, [ 2     ], [ 2        ], [ "top",                    ], 0, ],
    SymbolLine  => [ 10, [ 5     ], [ 5        ], [ undef, "Symbol"           ], 0, ],
    Attribute   => [ 11, [       ], [ 2        ], [ "top", "Element"          ], 0, ],
    Via         => [ 12, [ 8,10  ], [ 5,6,7,8  ], [ "top",                    ], 0, ],
    Element     => [ 13, [ 11    ], [ 7,8,9,11 ], [ "top",                    ], 0, ],
    Pad         => [ 14, [ 9,10  ], [ 7,8,9,10 ], [ undef, "Element"          ], 0, ],
    Pin         => [ 15, [ 9     ], [ 5,6,7,9  ], [ undef, "Element"          ], 0, ],
    ElementLine => [ 16, [ 5     ], [ 5        ], [ undef, "Element"          ], 1, ],
    ElementArc  => [ 17, [ 7     ], [ 7        ], [ undef, "Element"          ], 1, ],
    Layer       => [ 18, [       ], [ 2,3      ], [ "top",                    ], 0, ],
    Line        => [ 19, [ 7     ], [ 6,7      ], [ undef, "Layer"            ], 0, ],
    Arc         => [ 20, [ 9     ], [ 8,9      ], [ undef, "Layer"            ], 0, ],
    Text        => [ 21, [ 6     ], [ 5,6      ], [ undef, "Layer"            ], 0, ],
    Polygon     => [ 22, [       ], [ 1        ], [ undef, "Layer"            ], 0, ],
    Hole        => [ 23, [       ], [          ], [ undef, "Polygon", "Layer" ], 0, ],
    NetList     => [ 24, [       ], [ 0        ], [ "top",                    ], 0, ],
    Net         => [ 25, [       ], [ 2        ], [ undef, "NetList"          ], 0, ],
    Connect     => [ 26, [       ], [ 1        ], [ undef, "Net", "NetList"   ], 0, ],

    Cursor      => [ 40, [ 3     ], [ 3        ], [ "top",                    ], 0, ],
    Mark        => [ 40, [ 2     ], [ 2        ], [ undef, "Element"          ], 0, ],
    Rat         => [ 40, [ 7     ], [ 7        ], [ "top",                    ], 0, ],
    # I use "xy" as an item internally altough it isn't one
);
our %item_new;
our %item_old;
our %item_rev;
sub itemInit() {
    for my $item (keys %item) {
	my $new = $item{$item}[1];
	my $old = $item{$item}[2];
	my @sub = @{$item{$item}[3]};

	for my $k (@$new) {
	    $item_new{$item}{$k} = 1;
	}
	for my $k (@$old) {
	    $item_old{$item}{$k} = 1;
	}
	for (my $ix = 1; $ix < @sub; $ix++) {
	    if (!defined($item_rev{$sub[$ix]})) {
		$item_rev{$sub[$ix]} = [];
	    }
	    push @{$item_rev{$sub[$ix]}}, $item;
	}
    }
}
sub chkItem($$$@) {
    my $line = shift;
    my $item = shift;
    my $sep = shift;
    my @arg = @_;

    my $ref = $item{$item};
    if (!defined($ref)) {
	die "unknown item <$item> in <$line>";
    }

    my $cnt = @arg;
    my $chk;
    if ($sep eq "[") {
	if (!defined($item_new{$item}{$cnt})) {
	    die("invalid number of arguments for $item <$line>");
	}
    } elsif ($sep eq "(") {
	if (!defined($item_old{$item}{$cnt})) {
	    die("invalid number of arguments for $item <$line>");
	}
    } else {
	die("invalid call of chkItem(<$line., <$item>, <$sep>, ", join(", ", @arg) ,")");
    }

}

our %UNIT = (
    # pcb internal unit is integers of nm
    km => 1e3   * 1e9,
    m  => 1     * 1e9,
    cm => 1e-2  * 1e9,
    mm => 1e-3  * 1e9,
    um => 1e-6  * 1e9,
    nm => 1e-9  * 1e9,
    inch => 25.4        * 1e6, # 1 inch = 25.4 mm = 25.4 * 1e-3  * 1e9 = 25.4 * 1e6
    in   => 25.4        * 1e6,
    mil  => 25.4/1000   * 1e6,
    dmil => 25.4/10000  * 1e6,
    cmil => 25.4/100000 * 1e6,
);

sub dim2nm(@) {
    # din is a value with possible unit
    my @dim = @_;
    my @nm = ();

    for my $dim (@dim) {
	my $val = undef;
	if ($dim =~ m/^([-+]?(\d+(\.\d*)?|\.\d+))([cdhiklmnu]+)?$/) {
	    $val  = $1;
	    my $unit = $4;
	    my $k;

	    if (defined($unit)) {
		$k = $UNIT{$unit};
		if (!defined($k)) {
		    warn("undefined unit <$unit> in <$val>, using mm");
		    $k = $UNIT{mm};
		}
	    } elsif ($Lowres) {
		# old unitless values, for lines with XXX( ... ) syntax
		$k = $UNIT{mil};
	    } else {
		# newer unitless values, for lines with XXX[ ... ] syntax
		$k = $UNIT{cmil};
	    }
	    $val *= $k;
	}
	push @nm, $val;
    }

    if (@dim == 1) {
	return $nm[0];
    } else {
	@nm;
    }
}
my $unitlessZero = 1;
sub nm2dim(@) {
    my @dim = @_;
    my @str = ();
    for my $dim (@dim) {
	my $val;
	if ($dim == 0 && $unitlessZero == 1) {
	    $val = "0.0000";
	} else {
	    $val = sprintf("%.4fmm", $dim/1e6);
	}
	push @str, $val;
    }
    if (@dim == 1) {
	return $str[0];
    } else {
	@str;
    }
}
sub argDecode(@) {
    my @arg = @_;
    my @arr = ();

    for my $arg (@arg) {
	if ($arg =~ m/^['"](.*)['"]$/) {
	    push @arr, $1;
	} else {
	    push @arr, dim2nm($arg);
	}
    }
    @arr;
}

# read lines and return
# hash{item} [ {arg=>args, subitems=>[ ... ]}, ... ]
sub parse_pcb(@) {
    my @line = @_;

    my $data = {};
    my @data = ($data);
    my @stack = ();
    my $item = "";		# last seen item

    for (my $ix = 0; $ix < @line; $ix++) {
	my $line = $line[$ix];
	chomp $line;

	$line =~ s/^\s*//;
	$line =~ s/\s$//;
	next if ($line =~ m/^#/);
	next if ($line =~ m/^$/);

	my $top    = $stack[$#stack] // "";
	my $arg    = "";
	$Lowres = 0;
	my @arg    = ();

	# lvl up an down
	#print "\n<$line>\n";
	#print Dumper $data;
	if ($line =~ m/^\($/) {
	    my $dcur = $$data{$item};
	    my $dtop = ${$dcur}[$#{$dcur}];
	    push @data, $data;
	    push @stack, $item;
	    $data = $dtop;
	    $top = $item;	# i.e. the thing on top of the stack
	    $item = "(";
	    next;
	} elsif ($line =~ m/^\)$/) {
	    $item = ")";
	    pop @stack;
	    $data = pop @data;
	    next;
	}

	# hopefully a real item line
	$item = "";
	my $chk = undef;
	if ($line =~ m/^([a-z]+)\s*\[\s*(.*)\]$/i) {
	    $item  = $1;
	    $arg  = $2;
	    @arg = Common::simpleSplitArg($arg);
	    chkItem($line, $item, "[", @arg);
	} elsif ($line =~ m/^([a-z]+)\s*\((.*)\)$/i) {
	    $item  = $1;
	    $arg  = $2;
	    $Lowres = 1;
	    @arg = Common::simpleSplitArg($arg);
	    chkItem($line, $item, "(", @arg);
	} elsif ($line =~ m/^(\s*\[[-0-9.]+[a-u]*\s+[-0-9.]+[a-u]*\])+$/) {
	    $item = "xy";
	    $line =~ s/^\s*\[//;
	    $line =~ s/\]\s*$//;
	    $line =~ tr/ \t\n\r/ /s;
	    $line =~ tr/\[\]//d;
	    @arg = split( / /, $line );
	    #print STDERR "<", join("> <", @arg), ">\n";
	    #exit
	} else {
	    die("unknown syntax on line $ix <$line>");
	}

	if ($item eq "xy") {
	} else {
	    my $lvl_arr = $item{$item}[3];
	    if (!defined($lvl_arr)) {
		warn("unknown item <$item> on line $ix: <$line>\n");
	    }
	    my @lvl_arr = @$lvl_arr;
	    my $lvl = @stack;
	    if (@stack > @lvl_arr) {
		warn("to deep level <$lvl> at line $ix <$line>");
	    } elsif ($lvl == 0) {
		if (!defined($lvl_arr[0]) || "top" ne $lvl_arr[0]) {
		    warn("$item is not allowed at top level <$line>");
		}
	    } else {
		my $pre = $stack[$#stack];
		if (!defined($lvl_arr[1]) || $pre ne $lvl_arr[1]) {
		    warn("item <$item> is not allowed at level $lvl <$line>");
		}
	    }
	}


	# in pcb.pdf section 9.8 order
	if ($item eq "Arc") {
	    if (@arg == 3) {
		@arg = (@arg[0..4], "0.5mm", @arg[5..7]);
	    }
	    @arg = ( argDecode(@arg[0..5]), @arg[6..7], argDecode($arg[8]) );
	} elsif ($item eq "Attribute") {
	    @arg = argDecode(@arg);
	} elsif ($item eq "Connect") {
	    @arg = argDecode(@arg);
	} elsif ($item eq "Cursor") {
	    @arg = ( argDecode(@arg[0..1]), $arg[3] );
	} elsif ($item eq "DRC") {
	    if (@arg == 3) {
		push @arg, "0.2000mm";
	    }
	    if (@arg == 4) {
		push @arg, "0.4000mm", "0.2000mm";
	    }
	    @arg = argDecode(@arg);
	} elsif ($item eq "Element") {
	    # default values is unknown since pcb don't load theese old formats
	    CurrentNumber(0);
	    if (@arg == 7) {
		unshift @arg, 0;
	    }
	    if (@arg == 8) {
		@arg = ($arg[0..2], "?", $arg[3..7]);
	    }
	    if (@arg == 9) {
		@arg = ($arg[0..3], 0, 0, $arg[4..8]);
	    }
	    @arg = ( argDecode(@arg[0..7]), @arg[8..9], argDecode($arg[10]) );
	} elsif ($item eq "ElementArc") {
	    @arg = ( argDecode(@arg[0..3]), @arg[4..5], argDecode($arg[6]) );
	} elsif ($item eq "ElementLine") {
	    @arg = argDecode(@arg);
	} elsif ($item eq "FileVersion") {
	    # just a sinle unquoted value
	} elsif ($item eq "Flags") {
	    @arg = argDecode(@arg);
	    @arg = split(/,/,$arg[0]);
	    for my $arg (@arg) {
		if (!defined($pcb_flag{$arg})) {
		    warn("invalid pcb flag <$arg> in <$line>");
		}
	    }
	} elsif ($item eq "Grid") {
	    if (@arg == 3) {
		$arg[3] = 0;
	    }
	    @arg = ( argDecode(@arg[0..2]), $arg[3] );
	} elsif ($item eq "Groups") {
	    @arg = argDecode(@arg);
	    @arg = split(/:/,$arg[0]);
	} elsif ($item eq "Layer") {
	    if (@arg == 2) {
		$arg[2] = '""';
	    }
	    @arg = ($arg[0], argDecode(@arg[1..2]));
	} elsif ($item eq "Line") {
	    if (@arg == 6) {
		@arg = ( @arg[0..4], "0.5mm", $arg[5] );
	    }
	    @arg = argDecode(@arg);
	} elsif ($item eq "Mark") {
	    @arg = argDecode(@arg);
	} elsif ($item eq "Net") {
	    @arg = argDecode(@arg);
	} elsif ($item eq "NetList") {
	    # ok
	} elsif ($item eq "Pad") {
	    # TODO abs.-> relative values
	    if (@arg == 7) {
		@arg = ( @arg[0..5], CurrentNumber(), $arg[6] );
	    }
	    if (@arg == 8) {
		@arg = ( @arg[0..4], "0.20mm", "0.20mm", $arg[5..7] );
	    }
	    @arg = argDecode(@arg);
	} elsif ($item eq "PCB") {
	    @arg = argDecode(@arg);
	} elsif ($item eq "Pin") {
	    # TODO abs.-> relative values
	    if (@arg == 5) {
		@arg = ( @arg[0..2], "1.00mm", @arg[3..4] );
	    }
	    if (@arg == 6) {
		@arg = ( @arg[0..4], CurrentNumber(), $arg[5] );
	    }
	    if (@arg == 7) {
		@arg = ( @arg[0..2], "0.20mm", "0.20mm", $arg[3..6] );
	    }
	    @arg = argDecode(@arg);
	} elsif ($item eq "PolyArea") {
	    # ok
	} elsif ($item eq "Polygon") {
	    # TODO
	} elsif ($item eq "Rat") {
	    @arg = ( dim2nm(@arg[0..1]), $arg[2], dim2nm(@arg[3..4]), $arg[5] );
	} elsif ($item eq "Styles") {
	    @arg = argDecode(@arg);
	    @arg = split(/:/,$arg[0]);
	    # not fully interpreted
	} elsif ($item eq "Symbol") {
	    @arg = argDecode(@arg);
	} elsif ($item eq "SymbolLine") {
	    @arg = argDecode(@arg);
	} elsif ($item eq "Text") {
	    if (@arg == 5) {
		@arg = (@arg[0..2], 100, @arg[3..4] );
	    }
	    @arg = ( dim2nm(@arg[0..1]), @arg[2..3], argDecode(@arg[4..5]) );
	} elsif ($item eq "Thermal") {
	    # ok
	} elsif ($item eq "Via") {
	    if (@arg == 5) {
		@arg = ( @arg[0..2], "1.00mm", @arg[3..4] );
	    }
	    if (@arg == 6) {
		@arg = ( @arg[0..2], "0.20mm", @arg[3..5] );
	    }
	    if (@arg == 7) {
		@arg = ( @arg[0..3], "0.20mm", @arg[4..6] );
	    }
	    @arg = argDecode(@arg);
	} elsif ($item eq "xy") {
	    @arg = argDecode(@arg);
	} else {
	    die("unhandled item <$item> in <$line>");
	}

	if (!defined($$data{$item})) {
	    $$data{$item} = [];
	}
	push @{$$data{$item}}, { arg => [ @arg ] };
    }
    my %data = %$data;
}
sub sortItems() {
    my $A = $item{$a}[0] // -1;
    my $B = $item{$b}[0] // -1;
    $A <=> $B;
}
sub item2Str($@) {
    my $item = shift;
    my @arg = @_;

    my $str = $item;

    my $ref = $item{$item};
    return if (!defined($ref));

    my $sta = "[";
    my $end = "]";
    my $new = $$ref[1][0];
    my $old = $$ref[2][0];
    #print "$item: $new, $old\n";
    if (defined($new)) {
    } elsif (defined($old)) {
	$sta = "(";
	$end = ")";
    } else {
	$sta = $end = "";
    }
    if ($item{$item}[4]) {
	$str .= " ";
    }
    $str .= $sta;

    if ($item eq "Arc") {
	@arg = ( nm2dim(@arg[0..5]), @arg[6..7], Common::delimitStr('"', '"', $arg[8]) );
    } elsif ($item eq "Attribute") {
	@arg = ( Common::delimitStr('"', '"', @arg) );
    } elsif ($item eq "Connect") {
	@arg = ( Common::delimitStr('"', '"', @arg) );
    } elsif ($item eq "Cursor") {
	@arg = ( nm2dim(@arg[0..1]), $arg[2] );
    } elsif ($item eq "DRC") {
	@arg = nm2dim(@arg);
    } elsif ($item eq "Element") {
	@arg = ( Common::delimitStr('"', '"', @arg[0..3]),
		 nm2dim(@arg[4..7]), @arg[8..9], Common::delimitStr('"', '"', $arg[10]) );
    } elsif ($item eq "ElementArc") {
	@arg = ( nm2dim(@arg[0..3]), @arg[4..5], nm2dim($arg[6]) );
    } elsif ($item eq "ElementLine") {
	@arg = nm2dim(@arg);
    } elsif ($item eq "FileVersion") {
	# ok
    } elsif ($item eq "Flags") {
	@arg = (join(",", @arg));
	@arg = ( Common::delimitStr('"', '"', @arg) );
    } elsif ($item eq "Grid") {
	$arg[0] = nm2dim($arg[0]);
	$arg[1] = sprintf("%.4f", $arg[1]);
	$arg[2] = sprintf("%.4f", $arg[2]);
    } elsif ($item eq "Groups") {
	@arg = ( Common::delimitStr('"', '"', join(":", @arg)) );
    } elsif ($item eq "Layer") {
	@arg = ( $arg[0], Common::delimitStr('"', '"', @arg[1..2]));
    } elsif ($item eq "Line") {
	@arg = ( nm2dim(@arg[0..5]), Common::delimitStr('"', '"', $arg[6]) );
    } elsif ($item eq "Mark") {
	@arg = nm2dim(@arg);
    } elsif ($item eq "Net") {
	@arg = Common::delimitStr('"', '"', @arg);
    } elsif ($item eq "NetList") {
	# ok
    } elsif ($item eq "Pad") {
	@arg = ( nm2dim(@arg[0..6]), Common::delimitStr('"', '"', @arg[7..9]) );
    } elsif ($item eq "PCB") {
	@arg = ( Common::delimitStr('"', '"', $arg[0]), nm2dim(@arg[1..2]) );
    } elsif ($item eq "Pin") {
	@arg = ( nm2dim(@arg[0..5]), Common::delimitStr('"', '"', @arg[6..8]) );
    } elsif ($item eq "PolyArea") {
	# ok
    } elsif ($item eq "Polygon") {
	# TODO
    } elsif ($item eq "Rat") {
	@arg = ( nm2dim(@arg[0..1]), $arg[2], @arg[3..4], $arg[5], Common::delimitStr('"', '"', $arg[6]) );
    } elsif ($item eq "Styles") {
	@arg = ( Common::delimitStr('"', '"', join(":", @arg)) );
    } elsif ($item eq "Symbol") {
	@arg = (Common::delimitStr("'", "'", $arg[0]), nm2dim($arg[1]) );
    } elsif ($item eq "SymbolLine") {
	@arg = nm2dim(@arg);
    } elsif ($item eq "Text") {
	@arg = ( nm2dim(@arg[0..1]), @arg[2..3], Common::delimitStr('"', '"', @arg[4..5]) );
    } elsif ($item eq "Thermal") {
	# ok
    } elsif ($item eq "Via") {
	@arg = ( nm2dim(@arg[0..5]), Common::delimitStr('"', '"', @arg[6..7]) );
    } elsif ($item eq "xy") {
	$str = $sta;
	@arg = nm2dim(@arg);
	my @arr;
	for (my $ix = 0; $ix < @arg; $ix+= 2) {
	    push @arr, "[$arg[$ix] $arg[$ix+1]]";
	}
	@arg = @arr;
	push @arg, "";
    } else {
    }
    $str .= join(" ", @arg);
    $str .= $end;

    $str;
}
sub printData($@);
sub printData($@) {
    my $pre = shift;
    my %data = @_;
    #print Dumper \%data;
    #for my $k (keys %data) { print "$k\n"; }
    for my $item (sort sortItems keys %data) {
	next if ($item eq "arg");
	for (my $ix = 0; $ix < @{$data{$item}}; $ix++) {
	    my @arg = @{$data{$item}[$ix]{arg}};
	    if ($item eq "Element") { print "\n"; }
	    print $pre, item2Str($item, @arg), "\n";
	    if ($item eq "FileVersion" || $item eq "PCB") {
		print "\n";
	    }
	    #print "$pre$k: ", join(" ", @arg), "\n";
	    if (defined($item_rev{$item})) { print "$pre(\n"; }
	    printData( $pre . "\t", %{$data{$item}[$ix]});
	    if (defined($item_rev{$item})) {
		if ($item eq "Element") {
		    print "\n\t)\n";
		} else {
		    print "$pre)\n";
		}
	    }
	}
    }
}

########################################

sub polygon(@) {
    my %data = @_;
    #my @layer = grep { $$_{arg}[2] eq "copper" && $$_{arg}[1] ne "outline"  } @{$data{Layer}};
    my @layer = @{$data{Layer}};
    for (my $ix = 0; $ix < @layer; $ix++) {
	my @arg = @{$layer[$ix]{arg}};
	print "Layer $arg[0], name $arg[1]:\n";
	next unless(defined($layer[$ix]{Polygon}));
	my @polygon = @{$layer[$ix]{Polygon}};
	#print Dumper \@polygon;
	my @XY = ();
	for my $poly (@polygon) {
	    my @xy = @{$$poly{xy}};
	    #print "   Polygon:\n";
	    for (my $ip = 0; $ip < @xy; $ip++) {
		my @arr = @{$xy[$ip]{arg}};
		for (my $ax = 0; $ax < @arr; $ax += 2) {
		    push @XY, [ $arr[$ax], $arr[$ax+1] ];
		}
	    }
	}
	my $end = @XY - 1;
	my @X = sort { $$a[0] <=> $$b[0] } @XY;
	my @Y = sort { $$a[1] <=> $$b[1] } @XY;
	my ($min,$max) = ( [ $X[0][0], $Y[0][1] ], [ $X[$end][0], $Y[$end][1] ] );
	#($min,$max) = Common::bb($min,$max, $arr[$ax], $arr[$ax+1]);
	print "\t", join(" ", nm2dim( @$min, @$max) ), "\n";
    }
}

sub get_Board_Size($@) {
    my $line = shift;
    my @fld = @_;
    my $x = dim2nm($fld[1]);
    my $y = dim2nm($fld[2]);
    if (!defined($x) || !defined($y)) {
	chomp;
	print "Illegal units in <$line>\n";
	exit 1;
    }
    my @PCB = ($fld[0], $x, $y);
    @PCB
}
sub get_board_size(@) {
    my @PCB = ("", 0,0);
    for (@_) {
	if (m/^PCB\[(.*)\]/) {
	    @PCB = get_Board_Size($1);
	    last;
	}
    }
    @PCB; # name, x-dim, y-dim
}
sub refdes_prefix($@) {
    my $pfx = shift;
    my @line = @_;

    for (my $ix = 0; $ix < @line; $ix++) {
	if ($line[$ix] =~ m/^Element/) {
	    my @fld = split('"', $line[$ix]);
	    $fld[5] = $pfx . $fld[5];
	    $line[$ix] = join('"', @fld);
	}
    }
    @line;
}
sub get_BB(@) {
    my @line = @_;
    my @min = (0,0);
    #my @max = get_board_size(@line);

    my ($viamin, $viamax);
    my ($linmin, $linmax);
    for (@line) {
	s/^\s+//;
	if (m/^Via\[(.*)\]/i) {
	    my ($x, $y, $rest) = split(/\s+/, $1);
	    $x = dim2nm($x);
	    $y = dim2nm($y);
	    ($viamin, $viamax) = Common::bb($viamin, $viamax, $x, $y);
	} elsif (m/^Line\[(.*)\]/i) {
	    my ($x1, $y1, $x2, $y2, $rest) = split(/\s+/, $1);
	    $x1 = dim2nm($x1);
	    $y1 = dim2nm($y1);
	    $x2 = dim2nm($x2);
	    $y2 = dim2nm($y2);
	    ($linmin, $linmax) = Common::bb($linmin, $linmax, $x1, $y1);
	    ($linmin, $linmax) = Common::bb($linmin, $linmax, $x2, $y2);
	} elsif (m/^Layer/i) {
	} elsif (m/^Polygon\(\".*\"\)/) {
	}
    }

#    my @layer = grep { m/^Layer/ } @line;
#    @layer = grep { /"copper"/ } @layer;
#    @layer = grep { /"outline"/ } @layer;
    my @arr = nm2dim(@$viamin, @$viamax);
    print " via: ", join(" ", @arr), "\n";
    @arr = nm2dim(@$linmin, @$linmax);
    print " line: ", join(" ", @arr), "\n";
}

sub handle_layers($$$@) {
    # turn layer structure upside down
    # only handle copper and silk for the time beeing
    my $ix_group = shift;
    my $ix_layer = shift;
    my $line     = shift;
    my @layer    = @_;

    return if ($ix_layer < 0);
    my %lt;
    for (my $ix = 0; $ix < @layer; $ix++) {
	my $lt = $layer[$ix][2];
	if ($lt eq "copper" && $layer[$ix][1] eq "\"outline\"") {
	    # treat outline layer as a separate type from copper
	    $lt = "outline";
	}
	if (!defined($lt{$lt})) {
	    $lt{$lt} = [];
	}
	push @{$lt{$lt}}, $ix;
    }
    my @ix_arr;

    { # silk
	@ix_arr = @{$lt{silk}};
	my @layer_name = ( "bottom silk", "top silk");
	if (@ix_arr > 2) {
	    die "too many silk layers";
	}
	if (@ix_arr == 2) {
	    my $a = $ix_arr[0];
	    my $b = $ix_arr[1];
	    my $data = $layer[$a][3];
	    $layer[$a][3] = $layer[$b][3];
	    $layer[$b][3] = $data;
	} elsif (@ix_arr == 1) {
	    my $a = $ix_arr[0];
	    if ($layer[$a][1] eq $layer_name[0]) {
		$layer[$a][1] = $layer_name[1];
	    } else {
		$layer[$a][1] = $layer_name[0];
	    }
	}
    }

    { # copper
	@ix_arr = @{$lt{copper}};
	my @layer_name = ( "bottom", "top");

	if (@ix_arr > 2) {
	    die "cannot handle more than 2 copper layers for the time beeing";
	}
	if (@ix_arr == 2) {
	    my $a = $ix_arr[0];
	    my $b = $ix_arr[1];
	    my $data = $layer[$a][3];
	    $layer[$a][3] = $layer[$b][3];
	    $layer[$b][3] = $data;
	} elsif (@ix_arr == 1) {
	    my $a = $ix_arr[0];
	    if ($layer[$a][1] eq $layer_name[0]) {
		$layer[$a][1] = $layer_name[1];
	    } else {
		$layer[$a][1] = $layer_name[0];
	    }
	}
    }

    my $ix = $ix_layer;
    for my $lr (@layer) {
	my $str = "Layer($$lr[0] $$lr[1] \"$$lr[2]\")\n";
	$$line[$ix++] = $str;
	$$line[$ix++] = "(\n";
	my $data = $$lr[3];
	for my $Line (@$data) {
	    $$line[$ix++] = $Line;
	}
	$$line[$ix++] = "\t)\n";
    }
}
sub ChSideThermal($$) {
    my $flag = shift;
    my $map = shift;

    $flag;
}
sub ChSide(@) {
    my @line = @_;

    #print "\n";
    my %lmap = ();
    {
	# note: for Layer() layers are numbered 1 and uppwards
	# for thermal the numbers are starting with 0
	my @layer = grep { m/^Layer/ } @line;
	@layer = grep { /"copper"/ } @layer;
	my @arr = ();
	for my $line (@layer) {
	    $line =~ m/\(\s*(\d+)\s+"([^"]*)"\s+"([^"]*)"\s*\)/;
	    my $num = $1 - 1;
	    my $name = $2;
	    my $type = $3;
	    if ($name eq "outline") { next; }
	    push @arr, $num;
	    #print "$num $name $type\n";
	}
	if (@arr % 2) {
	    die("strange number of layers");
	}
	my $top = @arr - 1;
	#print " Layer order: ", join(" ", @arr), "\n";
	for my $nn (@arr) {
	    my $rv = $arr[$top - $nn];
	    $lmap{$nn} = $rv;
	    #print "$nn => $rv\n";
	}
    }

    my %Tdir = (
	0 => 2,
	1 => 3,
	2 => 0,
	3 => 1,
    );
    my ($name, $bx, $by) = ("", 0,0);
    #my @group = ();

    my $lvl = 0;
    my $ix_group = -1;
    my $ix_layer = -1; # starting $ix for layers
    #my @ix_layer = ();
    my @layer = ();
    my @stack = ();
    my $item = ""; # last seen item
    for (my $ix = 0; $ix < @line; $ix++) {
	next if ($line[$ix] =~ m/^\s*#/);
	next if ($line[$ix] =~ m/^\s*$/);
	my $line = $line[$ix];
	chomp $line;

	my $top    = $stack[$#stack] // "";
	my $pre    = "";
	my $arg    = "";
	my $post   = "";
	$Lowres = 0;
	my @arg    = ();

	if ($line =~ m/^(\s*)\(\s*$/) {
	    $pre = $1;
	    push @stack, $item;
	    $top = $item; # i.e. the thing on top of the stack
	    $item = "(";
	    $lvl++;
	} elsif ($line =~ m/^(\s*)\)\s*$/) {
	    $pre = $1;
	    $item = ")";
	    # $lvl--; pop @stack; done last in loop
	} elsif (defined($top) && ($top eq "Polygon" || $top eq "Hole")) {
	    # todo
	    $item = "xy";
	} elsif ($line =~ m/^(\s*([a-z]+)\s*\[\s*)(.*)(\]\s*)$/i) {
	    $pre  = $1;
	    $item  = $2;
	    $arg  = $3;
	    $post = $4;
	} elsif ($line =~ m/^(\s*([a-z]+)\s*\()(.*)(\)\s*)$/i) {
	    $pre  = $1;
	    $item  = $2;
	    $arg  = $3;
	    $post = $4;
	    $Lowres = 1;
	}
	@arg = Common::simpleSplitArg($arg) if (@arg == 0 && $arg);
	if (!defined($item{$item}) && $item ne '(' && $item ne ')' && $item ne 'xy') {
	    chomp $line[$ix];
	    die "unknown item <$item> in <$line[$ix]>";
	}

	if ($lvl == 0) {
	    if ($item eq "(" || $item eq ")") {
		# do nothing here
	    } elsif ($item eq "PCB") {
		($name, $bx, $by) = get_Board_Size($line,@arg);
	    } elsif ($item eq "Groups") {
		$ix_group = $ix; # save for use when we reach layer lines
	    } elsif ($item eq "Via") {
		if ($Lowres) {
		    if (@arg == 5) {
			# X Y Thickness "Name" NFlags
		    } elsif (@arg == 6) {
			# X Y Thickness Drill "Name" NFlags
		    } elsif (@arg == 7) {
			# X Y Thickness Clearance Drill "Name" NFlags
		    } elsif (@arg == 8) {
			# X Y Thickness Clearance Mask Drill "Name" NFlags
		    } else {
			die "wrong number of arguments in <$line>";
		    }
		    die "TODO: handle lowres <$item> in <$line>";
		} else {
		    if (@arg == 8) {
			# X Y Thickness Clearance Mask Drill "Name" SFlags
		    } elsif (@arg == 10) {
			# X Y Thickness Clearance Mask Drill BuriedFrom BuriedTo "Name" SFlags
			die "TODO: handle buried vias <$line>";
		    } else {
			die "wrong number of arguments in <$line>";
		    }
		    #print $arg[7], "\n";
		    my ($flag,$therm) = flagSplitSymbolic($arg[7]);
		    my @arr = ();
		    my %th;
		    for my $tx (@$therm) {
			$tx =~ m/^(\d+)(.+)$/;
			my $num = $1;
			my $how = $2;
			my $tobe = $lmap{$num};
			$th{$tobe} = $how;
			#print "$num $tobe $how\n";
		    }
		    for my $kk (sort { $a <=> $b } keys %th) {
			my $val = $th{$kk};
			push @arr, $kk . $val;
		    }
		    my $str = join(",", @$flag);
		    if (@arr) {
			if ($str) { $str .= ","; }
			$str .= "thermal(" . join(",", @arr) . ")";
		    }
		    #print "$str\n";
		    $arg[7] = '"' . $str . '"';
		    $arg[0] = nm2dim($bx - dim2nm($arg[0]));
		}
		#print $line[$ix];
		$line[$ix] = $pre . join(" ", @arg) . $post . "\n";
		#print $line[$ix], "\n";
	    } elsif ($item eq "Element") {
		my ($SFlags, $Desc, $Name, $Value, $MX, $MY, $TX, $TY, $TDir, $TScale, $TSFlags) =
		    (""     , ""   , ""   , ""    , 0  , 0  , 0  , 0  , 0    , 100    , "");
		my ($NFlags, $TNFlags) = (0,0);
		if ($Lowres) {
		    if (@arg == 7) {
			($Desc, $Name, $TX, $TY, $TDir, $TScale, $TNFlags) = @arg;
		    } elsif (@arg == 8) {
			($NFlags, $Desc, $Name, $TX, $TY, $TDir, $TScale, $TNFlags) = @arg;
		    } elsif (@arg == 9) {
			($NFlags, $Desc, $Name, $Value, $TX, $TY, $TDir, $TScale, $TNFlags) = @arg;
		    } elsif (@arg == 11) {
			($NFlags, $Desc, $Name, $Value, $MX, $MY, $TX, $TY, $TDir, $TScale, $TNFlags) = @arg;
		    } else {
			die "wrong number of arguments in <$line>";
		    }
		    $SFlags  = flagToSymbolic($NFlags , "element");
		    $TSFlags = flagToSymbolic($TNFlags, "element");
		} else {
		    if (@arg == 11) {
			($SFlags, $Desc, $Name, $Value, $MX, $MY, $TX, $TY, $TDir, $TScale, $TSFlags) = @arg;
		    } else {
			die "wrong number of arguments in <$line>";
		    }
		}
		$MX = nm2dim($bx - dim2nm($MX) );
		$TX = nm2dim( - dim2nm($TX) );
		$TDir = $Tdir{$TDir};
		$SFlags = '"' . flagToggle($SFlags,"onsolder","element") . '"';
		$TSFlags = '"' . flagToggle($TSFlags,"onsolder","element") . '"';
		$line[$ix] = $pre . 
		    join(" ", ($SFlags, $Desc, $Name, $Value, $MX, $MY, $TX, $TY, $TDir, $TScale, $TSFlags)) .
		    $post . "\n";

	    } elsif ($item eq "Layer") {
		$arg[2] =~ s/^"(.*)"$/$1/;
		if (!defined($layer_type{$arg[2]})) {
		    die "undefined layer type <$arg[2]> in <$line>";
		}
		push @layer, [ @arg, [ ] ]; # last [] is for layer content to be filled in below
		$ix_layer = $ix unless ($ix_layer >= 0);
	    }
	} elsif ($lvl == 1) {
	    my $ref = $item{$top}[1];
	    if (defined($ref) || $item eq "(" || $item eq ")") {
	    } else {
		die "strange line <$top> <$item> <$$ref{$item}> <$line>";
	    }
	    if ($item eq "(" || $item eq ")") {
	    } elsif ($top eq "Element") {
		if ($item eq "Mark") {
		    die "TODO: handle <$item> in <$line>";
		} elsif ($item eq "ElementArc") {
		    # X Y Width Height StartAngle DeltaAngle Thickness
		    if (@arg != 7) {
			die "wrong number of arguments in <$line>";
		    }
		    $arg[0] = nm2dim( - dim2nm($arg[0])); # x
		    $arg[4] = 180 - $arg[4]; # startangle
		    $arg[5] = - $arg[5]; # sweep
		} elsif ($item eq "ElementLine") {
		    # X1 Y1 X2 Y2 Thickness
		    if (@arg != 5) {
			die "wrong number of arguments in <$line>";
		    }
		    $arg[0] = nm2dim( - dim2nm($arg[0]));
		    $arg[2] = nm2dim( - dim2nm($arg[2]));
		} elsif ($item eq "Pad") {
		    #my ($rX1, $rY1, $rX2, $rY2, $Thickness, $Clearance, $Mask, $Name, $Number, $SFlags);
		    #my ($cX, $cY, $sX, $sY, $NFlags, $aX1, $aY1, $aX2, $aY2);
		    if ($Lowres) {
			die("TODO: lowres in <$line>");
		    } else {
			if (@arg == 10) {
			    #($rX1, $rY1, $rX2, $rY2, $Thickness, $Clearance, $Mask, $Name, $Number, $SFlags) = @arg;
			} elsif (@arg == 9) {
			    #($cX, $cY, $sX, $sY, $Clearance, $Mask, $Name, $Number, $SFlags) = @arg;
			    die "TODO: handle center-center syntax in <$line>";
			} else {
			    die "wrong number of arguments in <$line>";
			}
		    }
		    $arg[0] = nm2dim( - dim2nm($arg[0]));
		    $arg[2] = nm2dim( - dim2nm($arg[2]));
		    $arg[9] = '"' . flagToggle($arg[9],"onsolder","pad") . '"';
		} elsif ($item eq "Pin") {
		    if ($Lowres) {
			if (@arg == 5) {
			    # aX aY Thickness "Name" NFlags
			} elsif (@arg == 6) {
			    # aX aY Thickness Drill "Name" NFlags
			} elsif (@arg == 7) {
			    # aX aY Thickness Drill "Name" "Number" NFlags
			} elsif (@arg == 9) {
			    # rX rY Thickness Clearance Mask Drill "Name" "Number" NFlags
			} else {
			    die "wrong number of arguments in <$line>";
			}
			die("TODO: lowres in <$line>");
		    } else {
			if (@arg == 9) {
			    # rX rY Thickness Clearance Mask Drill "Name" "Number" SFlags
			} else {
			    die "wrong number of arguments in <$line>";
			}
			$arg[0] = nm2dim( - dim2nm($arg[0]));
		    }
		} else {
		    die "<$top> cannot have <$item>, <$line>";
		}
		$line[$ix] = $pre . join(" ", @arg) . $post . "\n";
	    } elsif ($top eq "Layer") {
		my $curr_layer = $layer[$#layer];
		my $layer_num = $$curr_layer[0];
		if ($Lowres && $item ne "Polygon") {
		    die("TODO: lowres in <$line>");
		}

		my $res = "";
		if      ($item eq "Line") {
		    if (@arg != 7) {
			die("TODO: wrong number or arg. in <$line>");
		    }
		    #my ($X1, $Y1, $X2, $Y2, $Thickness, $Clearance, $SFlags) = @arg;
		    $arg[0] = nm2dim($bx - dim2nm($arg[0]));
		    $arg[2] = nm2dim($bx - dim2nm($arg[2]) );
		    $res = $pre .
			join(" ", join(" ", @arg)) .
			$post . "\n";
		} elsif ($item eq "Text") {
		    $res = $line . "\n";
		} elsif ($item eq "Polygon") {
		    $res = $line . "\n";
		}
		push @{$$curr_layer[3]}, $res;
	    }
	} elsif ($lvl == 2) {
	    my $ref = $item{$top}[1];
	    if (defined($ref) || $item eq "(" || $item eq ")") {
	    } else {
		die "strange line <$top> <$item> <$$ref{$item}> <$line>";
	    }

	    if      ($top eq "Polygon") {
		my $curr_layer = $layer[$#layer];
		my $layer_num = $$curr_layer[0];
		if ($item eq "xy") {
		    # lowres not handled
		    if ($line =~ m/^(\s*)\[([\[\].0-9milc \t]*)\](\s*)$/) {
			$pre = $1;
			$arg = $2;
			$post = $3;
			@arg = split /\]\s*\[/, $arg;
		    } else {
			die "strange xv <$line>";
		    }
		    for (my $ax = 0; $ax < @arg; $ax++) {
			my ($x, $y) = split " ", $arg[$ax];
			$x = nm2dim($bx - dim2nm($x));
			$arg[$ax] = "[$x $y]";
		    }
		    $line[$ix] = $pre . join(" ", @arg) . $post;
		}
		push @{$$curr_layer[3]}, $line[$ix];
#		if ($item eq "Hole") {
#		}
	    } elsif ($top eq "net") {
		if ($item eq "connect") {
		}
	    }
	} elsif ($lvl == 3) {
	    my $ref = $item{$top}[1];
	    if (defined($ref) && defined($$ref{$item})) {
	    } else {
		die "strange line <$top> <$item> <$$ref{$item}> <$line>";
	    }

	} else {
	    die("too deep <$line>");
	}

	if ($item eq ")") {
	    $lvl--;
	    pop @stack;
	}

    }

     handle_layers($ix_group, $ix_layer, \@line, @layer);

    @line;
}

1;

__END__

pcb file items, case insensitive:

Arc [X Y RadiusX RadiusY Thickness Clearance StartAngle DeltaAngle SFlags]
Arc (X Y RadiusX RadiusY Thickness Clearance StartAngle DeltaAngle NFlags)
Arc (X Y RadiusX RadiusY Thickness StartAngle DeltaAngle NFlags)

Attribute ("Name" "Value")

Connect ("PinPad")

Cursor [X Y Zoom]
Cursor (X Y Zoom)

DRC [Bloat Shrink Line Silk Drill Ring]
DRC [Bloat Shrink Line Silk]
DRC [Bloat Shrink Line]

Element [SFlags "Desc" "Name" "Value" MX MY TX TY TDir TScale TSFlags]
Element (NFlags "Desc" "Name" "Value" MX MY TX TY TDir TScale TNFlags)
Element (NFlags "Desc" "Name" "Value" TX TY TDir TScale TNFlags)
Element (NFlags "Desc" "Name" TX TY TDir TScale TNFlags)
Element ("Desc" "Name" TX TY TDir TScale TNFlags)
(
)

ElementArc [X Y Width Height StartAngle DeltaAngle Thickness]
ElementArc (X Y Width Height StartAngle DeltaAngle Thickness)

ElementLine [X1 Y1 X2 Y2 Thickness]
ElementLine (X1 Y1 X2 Y2 Thickness)

FileVersion[Version]

Flags(Number)

Grid [Step OffsetX OffsetY Visible]
Grid (Step OffsetX OffsetY Visible)
Grid (Step OffsetX OffsetY)

Groups("String")

Layer (LayerNum "Name" "Flags")
(
)

Line [X1 Y1 X2 Y2 Thickness Clearance SFlags]
Line (X1 Y1 X2 Y2 Thickness Clearance NFlags)
Line (X1 Y1 X2 Y2 Thickness NFlags)

Mark [X Y]
Mark (X Y)

Net ("Name" "Style")
(
)

Netlist ( )
(
)

Pad [rX1 rY1 rX2 rY2 Thickness Clearance Mask "Name" "Number" SFlags]
Pad [cX cY sX sY Clearance Mask "Name" "Number" SFlags]
Pad (rX1 rY1 rX2 rY2 Thickness Clearance Mask "Name" "Number" NFlags)
Pad (cX cY sX sY Clearance Mask "Name" "Number" NFlags)
Pad (aX1 aY1 aX2 aY2 Thickness "Name" "Number" NFlags)
Pad (aX1 aY1 aX2 aY2 Thickness "Name" NFlags)

PCB ["Name" Width Height]
PCB ("Name" Width Height)
PCB ("Name")

Pin [rX rY Thickness Clearance Mask Drill "Name" "Number" SFlags]
Pin (rX rY Thickness Clearance Mask Drill "Name" "Number" NFlags)
Pin (aX aY Thickness Drill "Name" "Number" NFlags)
Pin (aX aY Thickness Drill "Name" NFlags)
Pin (aX aY Thickness "Name" NFlags)

PolyArea [Area]

Polygon (SFlags) (
   . . . [X Y] . . .
   Hole (
      . . . [X Y] . . .
   )
   ...

)
Polygon (SFlags) (
   . . . (X Y) . . .
   Hole (
      . . . (X Y) . . .
   )
   ...

)

Rat [X1 Y1 Group1 X2 Y2 Group2 SFlags]
Rat (X1 Y1 Group1 X2 Y2 Group2 NFlags)

Styles("String")

Symbol [Char Delta]
(
)

SymbolLine [X1 Y1 X2 Y2 Thickness]
SymbolLine (X1 Y1 X2 Y2 Thickness)

Text [X Y Direction Scale "String" SFlags]
Text (X Y Direction Scale "String" NFlags)
Text (X Y Direction "String" NFlags)

Thermal [Scale]

Via [X Y Thickness Clearance Mask Drill BuriedFrom BuriedTo "Name" SFlags]
Via [X Y Thickness Clearance Mask Drill "Name" SFlags]
Via (X Y Thickness Clearance Mask Drill "Name" NFlags)
Via (X Y Thickness Clearance Drill "Name" NFlags)
Via (X Y Thickness Drill "Name" NFlags)
Via (X Y Thickness "Name" NFlags)
