#!/usr/bin/perl -w
use strict;
# $Id$
# Thu, 13 Nov 2008 21:06:23 +0100
# Maciej 'agaran' Pijanka <agaran@pld-linux.org>
# for OpenARM SBC Project
# license: gpl v3

use Getopt::Long qw//;
use File::Basename qw/basename/;
use IO::Dir;
use IO::File;
use Data::Dumper qw/Dumper/;

use lib File::Basename::dirname($0).'/lib';
use Text::Table;


my %Config;

# ===[ SETUP DEFAULTS ]=============================
$Config{docdir} = '.';
$Config{bomdir} = '.';
$Config{verbose} = 1;

# 0 mean not show, -1 show all, positive value limits depth of shown
$Config{dbg_showdirs} = 0;


# modes
my $show_conf = 0;
my $show_help = 0;

# ==================================================

my %Inv_By_PartNo;
my @Inv;
my %BomData;
my %Files;
my ($file_name, $file_line);

sub err_printf($@) {
	my ($format, @args) = @_;

	if (defined $file_name && defined $file_line) {
		$format = '(%s:%d) '.$format;
		unshift @args, shortdir($file_name), $file_line;
	}

	printf STDERR "-E- ".$format."\n", @args;
	# exit? or fail-exit here
}

sub wrn_printf($@) {
	my ($format, @args) = @_;
	return if ($Config{verbose} <= 1) ;

	if (defined $file_name && defined $file_line) {
		$format = '(%s:%d) '.$format;
		unshift @args, shortdir($file_name), $file_line;
	}

	printf STDERR "-W- ".$format."\n", @args;
}

sub inf_printf($@) {
	my ($format, @args) = @_;
	return if ($Config{verbose} <= 2) ;

	if (defined $file_name && defined $file_line) {
		$format = '(%s:%d) '.$format;
		unshift @args, shortdir($file_name), $file_line;
	}

	printf STDERR "-I- ".$format."\n", @args;
}

sub nfo_printf($@) {
	my ($format, @args) = @_;

	return if ($Config{verbose} <= 0) ;

	if (defined $file_name && defined $file_line) {
		$format = '(%s:%d) '.$format;
		unshift @args, shortdir($file_name), $file_line;
	}

	printf STDERR "-N- ".$format."\n", @args;
	# exit? or fail-exit here
}



sub Config_Show {
	printf "Config for %s\n----------------------------------------\n", basename($0);
	foreach my $name (sort keys %Config) {
		printf "%-20s %s\n", $name, $Config{$name};
	}
}

sub Help_Show {
	printf "Help for %s\n----------------------------------------\n", basename($0);
	unless (defined($_[1]) && length($_[1]) != 0) {
		print "Basic help\n\t--showrc|showconf shows current configuration\n".
			"\t--docdir|-d <dir> tells script where information.txt files should be searched\n".
			"\t--define <something>=<somethingelseornot> defines some configration value\n".
			"\t--verbose|-v [level] increases or sets verbosity level\nOrder of options DOES matter\n".
			"\t--bomdir|-b <dir> tells script where boms should be searched\n".
			"\t--outfile|-o <file> tells where script shall save output data\n".
			"\t--force|-f forces script to save data even if file exist already\n".
			"\t-l <num> repeat title for output tables every <num> rows\n";

		print "\t\e[0;31mThis Help Is created especially for \e[0;33mJelle\e[0;29m\n".
			"\tThis script search all directories below specified ones to find interesting files\n";
		return;
	} 
	if ($_[1] =~ /^foo$/) {
		print "Noo, there is no foo's here\n";
	} else {
		printf "Sorry, help for `%s' don't exist (eventually) yet\n", $_[1];
	}
	exit;
}

sub fix_dir ($) {
	my $dir = shift @_;

	$dir =~ s/\/$//;

	if (! -d $dir) {
		err_printf("Sorry `%s' is not valid directory, exiting", $dir);
	}

	return $dir;
}

sub shortdir ($) {
	my $path = shift @_;

	return substr($path, 2) if ($path =~ /^\.\//) ;
	return $path;
}


sub trim($) {
	my ($value) = @_;

	$value =~ s/^ +//;
	$value =~ s/ +$//;
	return $value;
}

sub etrim($) {
	my ($value) = @_;

	$value =~ s/^[ 	]+//;
	$value =~ s/[ 	]+$//;
	return $value;
}

# break string at $lim character (or bit before) 
# but only on whitespaces
sub strbreak($$) {
	my ($str,$lim) = @_;
	my @p = split /\ /,$str;
	$str = '';
	my $l = 0;
	while (@p) {
		my $e = shift @p;
		if ($l + length ($e) +1 > $lim) {
			$str .= "\n".$e;
			$l = length $e;
		} else {
			$str .= " ".$e;
			$l += length($e) + 1;
		}
	}
	return $str;
}

# this function return price of single item
# quantity is needed to cope with quantity-based prices
sub get_price($$) {
	my ($id, $quantity) = @_;

	if (!defined $Inv[$id]{Price}) {
		wrn_printf("%s has no price, setting to 0.0", $Inv[$id]{Manufacturer_Partno});
		return 0.0;
	} 

	if (ref $Inv[$id]{Price} eq 'ARRAY') { # handle structured prices
		my ($ret,$li) = (0,0);
		foreach my $pd (@{$Inv[$id]{Price}}) {
			if (ref $pd eq 'ARRAY') {
				my ($rmin,$rmax) = split '-', $pd->[0];
				if ($rmin > $li && $quantity >= $rmin) {
					$ret = $pd->[1];
					$li = $rmin;
				}
				#inf_printf("Debuging %s, %s %d",$pd->[0], $ret, $quantity);
			}
		}
		return $ret;
	} 

	return $Inv[$id]{Price};
}



# this subroutine is used as callback function
# executed by file_lookup
sub parse_ifile($) {
	my ($filepath) = @_;

	open(IN, $filepath) or return 1;

	my %data;

	$file_name = $filepath;
	$file_line = 0;

	my $_ok;

	while (not eof IN) {
		my $line = <IN>;
		$file_line++;

		chomp $line;

		next if ($line =~ /^[ 	]*$/);
		next if ($line =~ /^;/);

		last if ($line =~ /^--/);

		if ($line =~ /^([A-Za-z ]+):(.*)$/) {
			my ($name,$value) = (lc etrim($1),etrim($2));

			if ($name =~ /^price$/) {
				$value =~ s/[^0-9\.\,]//g; # remove any characters beside DIGITS
				if ($value =~ s/^([0-9]+)[,.]([0-9]+)$/$1.$2/) {
					# printf STDERR "Price %.3f\n", $value;
					if (!defined($data{price})) {
						$data{price} = $value;
						$_ok->{$name} = 1;
					} else {
						wrn_printf("Duplicated price field.");
					}
				} else {
					err_printf("Bad data in price field.");
				}
			} elsif ($name =~ /^manufacturer$/i) {
				# printf STDERR "Manufacturer %s\n", $value;
				if (!defined($data{manufacturer})) {
					$data{manufacturer} = $value;
					$_ok->{$name} = 1;
				} else {
					wrn_printf("Duplicated manufacturer field.");
				}
			} elsif ($name =~ /^manufacturer part no$/ ) {
				# printf STDERR "ManPartNo %s\n", $value;
				if (!defined($data{manufact_partno})) {
					$data{manufact_partno} = trim($value);
					$_ok->{$name} = 1;
				} else {
					wrn_printf("Duplicated manufacturer part no field."); 
				}
			} elsif ($name =~ /^description$/i) {
				if (!defined($data{desc})) {
					$data{desc} = $value;
				} else {
					wrn_printf("Duplicated description no field.");
				}
			} elsif ($name =~ /^datasheet$/i) {
				$data{datasheet} = [] unless defined $data{datasheet};
				push @{$data{datasheet}}, $value;
				# printf STDERR "Datasheet %s\n", $value;
			} elsif ($name =~ /^supplier$/i) {
				# printf STDERR "Supplier %s\n", $value;
				if (!defined($data{supplier})) {
					$data{supplier} = $value;
					$_ok->{$name} = 1;
				} else {
					wrn_printf("Duplicated supplier field.");
				}
			} elsif ($name =~ /^order code$/) {
				if (!defined($data{ordercode})) {
					$data{ordercode} = $value;
					$_ok->{$name} = 1;
				} else {
					wrn_printf("Duplicated order code field.");
				}
				#push @DATA, { $name => $value };
			} elsif ($name =~ /^url .*$/) {
				# printf STDERR "URL %s\n", $value;
			} elsif ($name =~ /^catalog(ue|) page$/) {
				# printf STDERR "Catalogue Page %s\n", $value;
			} else {
				err_printf("Unhandled field type `%s'.",$name);
			}
		} elsif ($line =~ /^([A-Za-z ]+)[ \t]*\(([A-Za-z 0-9\(\)\-]+)\)[ \t]*:(.*)$/) {
			my ($name, $extdata, $value) = (lc etrim($1),etrim($2),etrim($3));
			if ($name =~ /^price$/) {
				$extdata =~ s/ //g;
				#wrn_printf("Extended-Price <%s> <%s> at %s:%d", $extdata, $value, shortdir($filepath), $file_line);
				$data{price} = [] if (!defined $data{price});

				if(ref $data{price} eq 'ARRAY') {
					$value =~ s/[^0-9\.\,]//g; # remove any characters beside DIGITS
					if ($value =~ s/^([0-9]+)[,.]([0-9]+)$/$1.$2/) {
						push @{$data{price}}, [ $extdata, $value ];
						$_ok->{$name} = 1;
					} else {
						err_printf("Bad extended price field.");
					}
				}
			} else {
				wrn_printf("Unrecognized extended data `%s'.", $name);
			}
		} else {
			wrn_printf("Unparseable line, is it an comment?");
		}
	}
	close(IN);

	if (scalar keys %data == 0) {
		inf_printf("No data for me, skipping.");
		return;
	}

	foreach my $field (split /\!/, q/price!manufacturer part no!order code/) {
		wrn_printf("Missing required field `".(ucfirst $field)."'.") unless ($_ok->{$field});
	}
	

	my $id = scalar @Inv;

	$Inv[$id] = {};

	$Inv[$id]{Datasheet} = delete $data{'datasheet'} if defined $data{'datasheet'};
	$Inv[$id]{Manufacturer} = etrim(trim(delete $data{'manufacturer'})) if defined $data{'manufacturer'};
	$Inv[$id]{Description} = etrim(trim(delete $data{'desc'})) if defined $data{'desc'};
	$Inv[$id]{Price} = delete $data{'price'} if defined $data{'price'};
	$Inv[$id]{Ordercode} = etrim(trim(delete $data{'ordercode'})) if defined $data{'ordercode'};
	$Inv[$id]{Manufacturer_Partno} = etrim(trim(delete $data{'manufact_partno'})) if defined $data{'manufact_partno'};
	$Inv[$id]{Supplier} = delete $data{'supplier'} if defined $data{'supplier'};

	unless (defined ($Inv_By_PartNo{$Inv[$id]{Manufacturer_Partno}})) {
		$Inv_By_PartNo{$Inv[$id]{Manufacturer_Partno}} = $id;
	} else {
		wrn_printf("PartNumber %s happened more than once, using first occurence (id:%d)", 
			$Inv[$id]{Manufacturer_Partno}, $id);
	}

	#inf_printf("Part %s defined in file %s", $Inv[$id]{Manufacturer_Partno}, shortdir($filepath));
	
	wrn_printf("Bug in parser, please report: %s", Dumper(\%data)) if (scalar keys %data > 0);
}


sub parse_bom ($) {
	my ($filepath) = @_;

	open(IN, $filepath) or return 1;
	my $fkey = substr(basename($filepath),0,length(basename($filepath))-4);

	$file_name = $filepath;
	$file_line = 0;

	#wrn_printf("GotARg: %s", shortdir($filepath));

	my @Fields;

	my %data;
	my $v = '';
	while (not eof IN) {
		my $line = <IN>;
		$file_line++;

		chomp $line;

		if ($line =~ /^\.START$/) {
			$v = 'boms';
			next;
		}

		if ($line =~ /^\.END$/) {
			$v = '';
			next;
		}

		if ($v eq 'boms') {
			@Fields = split(/\t/, substr($line,2));
			$v = 'bom';
			# some funny way to generate field-map
			# that if someone reorder bom file columns we are still on place
			next;
		}

		if ($v eq 'bom') {
			my ($refdes, $device, $value, $footprint, $quantity) = split (/\t/, $line);

			$device = trim($device);

#			wrn_printf("Device: %s (value: %s) at refdes %s, %d pcs", $device, $value, $refdes, $quantity);

			if (!defined $Inv_By_PartNo{$device}) {
				wrn_printf("Device %s not found in inventory.", $device);
				next;
			}
			my $id = $Inv_By_PartNo{$device};
			next if ($Inv[$id]{Manufacturer} =~ /none/i); # skip parts whose manufacturer is none
			#inf_printf("Found in Inventory at %d %s", $id, $Inv[$id]{Manufacturer_Partno});
			push @{$data{$id}{RefDes}}, etrim(trim($refdes));
			if (!defined $data{$id}{Footprint}) {
				$data{$id}{Footprint} = $footprint;
			} else {
				if ($data{$id}{Footprint} ne $footprint) {
					err_printf("Different footprints for same device in within single bom file, script ".
						"cannot work around this, will use first one, but expect that output file ".
						"might have errors.");
				}
			}
		} 
	}
	close(IN);

	if (scalar keys %data == 0) {
		inf_printf("No data for me, skipping.");
		return;
	}

	foreach my $id (keys %data) {
		my %tmp;
		map { $tmp{$_} = 1 } @{$data{$id}{RefDes}};
		@{$data{$id}{RefDes}} = keys %tmp;
		my $cnt = scalar @{$data{$id}{RefDes}};
		push @{$BomData{$id}{RefDes}}, @{$data{$id}{RefDes}};
		if (!defined $BomData{$id}{Footprint}) {
			$BomData{$id}{Footprint} = $data{$id}{Footprint};
		} else {
			if ($data{$id}{Footprint} ne $data{$id}{Footprint}) {
				err_printf("Different footprints for same device between sheets, script cannot ".
					"work around this, will use first one, but expect that output file might ".
					"have errors.");
			}
		}
		push @{$BomData{$id}{Files}}, $fkey;
		push @{$Files{$fkey}{$id}{RefDes}}, @{$data{$id}{RefDes}};
		
#		printf "%-20s %.4f %s\n", $Inv[$id]{Manufacturer_Partno}, $cnt, $icost, join (', ', @{$data{$id}{RefDes}});
#		%BomData{ById}{$id}{RefDes}
	}
}

sub file_lookup ($$$$) ;
sub file_lookup ($$$$) { # {{{1
	my ($dir, $depth, $regexp, $callback) = @_;

	err_printf("BUG: Sorry, callback must be CODE ref") unless (ref $callback eq 'CODE');

	if ( -d $dir) {
		my $d = IO::Dir->new($dir);
		return 1 if (!defined $d);


		while (defined(my $e = $d->read)) { 
			my $fe = $dir .'/'. $e;
			if ( -f $fe) {
				if ($fe =~ $regexp) {
					&$callback($fe);
					$file_name = undef;
				}
			} elsif (-d $fe) { # now its dir...
				if ($e eq '.svn') { # if entry name is equal to svn
					next; # go to next entry in foreach loop
				}
				next if ($e eq '.' or $e eq '..'); # skip to next if dir entry is . or ..

				if ($Config{dbg_showdirs} == -1 or $Config{dbg_showdirs} > $depth) {
					$file_name = undef;
					dbg_printf("Entering directory %s", shortdir($fe));
				}
				return 1 if (file_lookup($fe, $depth+1, $regexp, $callback) == 1);
			} else {
				# symlink or other mysterius beast
			}
		}
	}
	return 0;
} # }}}

sub gen_bomfile ($) { # {{{1
	my $outfile = shift @_;

	if (-e $outfile) {
		wrn_printf("File already exist, will overwrite");
	}

	my $out = new IO::File $outfile, 'w';

	my $bomtable = Text::Table->new(
		{ title => '| ', is_sep => 1 },
		{ title => 'id', align => 'right', align_title => 'left' },
		{ title => ' | ', is_sep => 1 },
		{ title => 'description', align => 'left', align_title => 'left' },
		{ title => ' | ', is_sep => 1 },
		{ title => 'manufacturer partid', align => 'left', align_title => 'left' },
		{ title => ' | ', is_sep => 1 },
		{ title => 'manufacturer', align => 'left', align_title => 'left' },
		{ title => ' | ', is_sep => 1 },
		{ title => 'order code', align => 'left', align_title => 'left' },
		{ title => ' | ', is_sep => 1 },
		{ title => 'quantity', align => 'right', align_title => 'left' },
		{ title => ' | ', is_sep => 1 },
		{ title => "price", align => 'right', align_title => 'left' },
		{ title => ' | ', is_sep => 1 },
		{ title => "cost", align => 'right', align_title => 'left' },
		{ title => ' |', is_sep => 1 },
	);

	# counters
	my ($bn,$totalcost,$partcount) = (1,0,0);

	foreach my $id ( sort {my $p = $Inv[$a]{Manufacturer} cmp $Inv[$b]{Manufacturer}; if ($p == 0) {
#		return $Inv[$a]{Manufacturer_Partno} cmp $Inv[$b]{Manufacturer_Partno}}; return $p; }  keys %BomData) {
			return $Inv[$a]{Description} cmp $Inv[$b]{Description}}; return $p; }  keys %BomData) {
		my %tmp;

		map { $tmp{$_} = 1 } @{$BomData{$id}{RefDes}}; # duplicated refdes removal
		@{$BomData{$id}{RefDes}} = keys %tmp;

		my $quant = scalar @{$BomData{$id}{RefDes}}; # quantity based on number of refdes..

		my $price = get_price($id,$quant);
		if ($price == 0) {
			wrn_printf("%s has zero price", $Inv[$id]{Manufacturer_Partno});
		}
		my $icost = $quant * $price;
		
		$bomtable->add($bn++, $Inv[$id]{Description}, $Inv[$id]{Manufacturer_Partno}, $Inv[$id]{Manufacturer},
			$Inv[$id]{Ordercode},$quant, sprintf("%.3f",$price), sprintf("%.3f", $icost));

		$totalcost += $icost;
		$partcount += $quant;
	}

	printf $out "file generated at %s\n\n", scalar localtime(time());

	print $out $bomtable->rule('-','+');
	print $out $bomtable->title();
	print $out $bomtable->rule('-','+');

	# repeat header every NTH lines or not
	unless (defined $Config{'lines'}) { 
		print $out $bomtable->body();
	} else {
		my @p = split(/\n/,$bomtable->body());
		while (@p) {
			my @sub = splice @p,0,$Config{'lines'};
			print $out join("\n",@sub)."\n";
			if (scalar @p > 0) {
				print $out $bomtable->rule('-','+');
				print $out $bomtable->title();
				print $out $bomtable->rule('-','+');
			}
		}
	}
	print $out $bomtable->rule('-','+');

	printf $out "\nTotal cost: %.3f\nTotal part count: %d\n\n", $totalcost, $partcount;

	$out->close;
} # }}}

sub gen_mapfile ($) { # {{{1
	my $outfile = shift @_;

	if (-e $outfile) {
		wrn_printf("File already exist, will overwrite");
	}

	my $out = new IO::File $outfile, 'w';

	my $parttable = Text::Table->new(
		{ title => '| ', is_sep => 1 },
		{ title => 'id', align => 'right', align_title => 'center' },
		{ title => ' | ', is_sep => 1 },
		{ title => 'description', align => 'left', align_title => 'left' },
		{ title => ' | ', is_sep => 1 },
		{ title => 'manufacturer partid', align => 'left', align_title => 'left' },
		{ title => ' | ', is_sep => 1 },
		{ title => 'footprint', align => 'left', align_title => 'left' },
		{ title => ' | ', is_sep => 1 },
		{ title => 'refdes', align => 'left', align_title => 'left' },
		{ title => ' | ', is_sep => 1 },
		{ title => 'sheet', align => 'left', align_title => 'left' },
		{ title => ' |', is_sep => 1 },
	);
	
	my ($pn) = (1);
	
	foreach my $fkey (sort keys %Files) {
		my $rowblock = 0;
		foreach my $id ( sort {my $p = $Inv[$a]{Manufacturer} cmp $Inv[$b]{Manufacturer}; if ($p == 0) {
			return $Inv[$a]{Description} cmp $Inv[$b]{Description}}; return $p; }  keys %{$Files{$fkey}}) {
	
			if ($Config{'no-repeat-columns'}) {
				my @p = split(/\n/, strbreak(join (', ', sort @{$Files{$fkey}{$id}{RefDes}}),43));
	
				$parttable->add($pn, $Inv[$id]{Description}, $Inv[$id]{Manufacturer_Partno}, $BomData{$id}{Footprint}, 
					etrim(trim(shift @p)), $fkey);
	
				while (@p) {
					$parttable->add('++', '', '', '', etrim(trim(shift @p)), '');
				}
			} else {
				my @p = split(/\n/, strbreak(join (', ', sort @{$Files{$fkey}{$id}{RefDes}}),43));
				while (@p) {
					$parttable->add($pn, $Inv[$id]{Description}, $Inv[$id]{Manufacturer_Partno}, $BomData{$id}{Footprint}, 
						etrim(trim(shift @p)), $fkey);
				}
			}
	
	
			$pn++;
		}
		$parttable->add('--','=========','==========','==========','==========','==========');
	}
	
	print $out $parttable->rule('-','+');
	print $out $parttable->title();
	print $out $parttable->rule('-','+');
	unless (defined $Config{'lines'}) { 
		print $out $parttable->body();
	} else {
		my @p = split(/\n/,$parttable->body());
		while (@p) {
			my @sub = splice @p,0,$Config{'lines'};
			print $out join("\n",@sub)."\n";
			if (scalar @p > 0) {
				print $out $parttable->rule('-','+');
				print $out $parttable->title();
				print $out $parttable->rule('-','+');
			}
		}
	}
	print $out $parttable->rule('-','+');

	$out->close();	
} #}}}1

Getopt::Long::Configure("bundling");

if (scalar @ARGV == 0) {
	Help_Show();
	exit;
}

my $result = Getopt::Long::GetOptions (
	"showrc|showconf" => sub { $show_conf = 1 },
	"docdir|d=s" => sub { $Config{docdir} = $_[1]; },
	# not sure if bomdir or SCH dir
	"bomdir|b=s" => sub { $Config{$_[0]} = $_[1]; },  # $_[0] contain basename of option, so in few cases could be (ab)used
	"define|D=s" => sub { my ($p,$q) = split(/=/,$_[1],2); $Config{$p} = ($q||1); },
	"verbose|v:+" => \$Config{'verbose'}, 
	"help|h|?:s" => sub { $show_help = 1; }, 
	"outfile|o=s" => sub { $Config{$_[0]} = $_[1]; },
	"force|f" => sub { $Config{$_[0]} = 1 },
	"lines|l=i" => sub { $Config{$_[0]} = abs(int($_[1]));},

	# options 
);
if (!$result) {
	printf "Usage: %s [-d directory] [-v]\n",basename($0);
	exit;
}

# ==================================================
# processing of options/config values, checking ranges etc
# 

$Config{docdir} = fix_dir ($Config{docdir});
$Config{bomdir} = fix_dir ($Config{bomdir});

if ( $show_help == 1) {
	Help_Show();
	exit;
}

if ( $show_conf == 1) {
	Config_Show();
	exit;
}

# make Inventory
nfo_printf("Fetching data from information.txt (under %s)",shortdir($Config{docdir}));
file_lookup($Config{docdir}, 0, qr/\/information.txt$/, \&parse_ifile);
nfo_printf("Finished, %d entries loaded.", scalar(@Inv)+1);

# process BOM files
nfo_printf("Fetching BOM data from %s", shortdir($Config{bomdir}));
file_lookup($Config{bomdir}, 0, qr/\.bom$/, \&parse_bom);
nfo_printf("Finished, %d entries loaded.", scalar keys %BomData);

if (!defined $Config{outfile}) {
	$Config{outfile} = './output';
	wrn_printf("Output file not specified, saving out in ".$Config{outfile});
}

if ($Config{outfile} =~ /^(.*)\.txt$/) {
	$Config{outfile} = $1;
	inf_printf("Please specify output name without extension, -bom.txt and -map.txt will be added automatically");
}

gen_bomfile($Config{'outfile'}.'-bom.txt');

gen_mapfile($Config{'outfile'}.'-map.txt');

nfo_printf("Script finished, output saved in %s-{map,bom}.txt", $Config{outfile});

__END__

	if ( -e $Config{outfile}) {
		unless (defined $Config{force} && $Config{force} == 1) {
			inf_printf("Unlinking output.txt before owrewriting");
			unlink($Config{outfile});
		} else {
			wrn_printf("Output file already exist, add --force if i shall overwrite it");
			exit;
		}
	}
