#!/usr/bin/perl

# build-weed-plugin-C - Copyright G. Finch (salsaman) 2021
# Released under the LGPL 3 or later - see file COPYING.LGPL or www.gnu.org for details

#usage : build-weed-plugin-C <script_file> <output_directory>
# if <output_directory> is omitted, plugins will be generated in /tmp

# if <script_file> == "-get" then the commandline parameters are interpreted as:
# build-lives-rfx-plugin -get <section> <file>
# and we then print the contents of section on stdout

########################################################
# this will auto-generate a Weed C plugin from the script info in $file

my $USE_STRICT = 1;
if ($USE_STRICT) {
    use strict;
}

my $USE_WARNINGS = 1;
if ($USE_WARNINGS) {
    use warnings;
}

my $builder_version = "3.2.1-pre";
my $rfx_version = "1.8.3";

################################
## TODO - allow license, version, (c) author(s) and (c) yeasr to be overriden in script

my $license_type = "GNU GPL";
my $license_version = "3 or higher";

my $copyright_authors = "";
my $copyright_years = "";

my $CURRENT_YEAR = "2021"; ## TODO
##################################

my ($section, $file);
my @pieces;

if ($ARGV[0] eq "-get") {
    $section = $ARGV[1];
    $file = $ARGV[2];
    my @result = &rc_read($section, $file);
    foreach (@result) {
	print STDOUT "$_\n";
    }
    exit 0;
}

$file = $ARGV[0];

my $define = (&rc_read("define", $file))[0];
if ($define eq "") {
    print STDERR "Error  - <define> section missing from script file.\n";
    exit 5;
}

my $plversion = substr($define, 1);
my $plhversion = &make_ver_hash($plversion);

unless ($plhversion <= &make_ver_hash($rfx_version)) {
    print STDERR "\n\nError:  - Invalid script RFX version $version, this builder supports up to $rfx_version\n";
    exit 4;
}

my $delim = substr($define, 0, 1);
my $delimit = $delim;
if ($delim =~ /^[\^\$\.\*\+\?\|\(\)\[\]\{\}\\]+/) {
    $delim = "\\" . $delim;
}

my $pf_name = (&rc_read("name", $file))[0];
@pieces = split(/$delim/, $pf_name);
my $plugin_name = @pieces[0];
my $filter_name = @pieces[1];
if (!$filter_name) {
    $filter_name = $plugin_name;
}

my @authors = &rc_read("author", $file);
@pieces = split(/$delim/, @authors[0]);

my $author = @pieces[0];
my $extra_authors = @pieces[1];

if (@authors > 1) {
    $extra_authors = @authors[1];
}

my $lang_code = (&rc_read("language_code", $file))[0];

my $uid = (&rc_read("unique_id", $file))[0];

my $versions = (&rc_read("version", $file))[0];
@pieces = split(/$delim/, $versions);
my $pver = @pieces[0];
my $fver = @pieces[1];

my @description = (&rc_read("description", $file));
my @requires=&rc_read("requires", $file);

my @typedefs = &rc_read("typedefs", $file);

my @sdata = &rc_read("sdata", $file);

my @filter_flags = &rc_read("filter_flags", $file);
my $flagstr;

my @static = &rc_read("static", $file);

my @pal_list = &rc_read("palette_list", $file);

my @in_chans = &rc_read("in_chans", $file);
my @out_chans = &rc_read("out_chans", $file);

my @params = &rc_read("params", $file);
my @oparams = &rc_read("out_params", $file);

my @param_window = &rc_read("param_window", $file);
my $nwlines = @param_window;

my @init = &rc_read("init", $file);
my @proc= &rc_read("process", $file);
my @deinit = &rc_read("deinit", $file);

my @setup= &rc_read("setup", $file);

my @disp = &rc_read("display_vals", $file);

unless ($lang_code eq "192" || hex($lang_code) == 192) {
    print STDERR "Error:  - Invalid language code for this builder !\n";
    exit 3;
}

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

# generate into /tmp
# LiVES will call this to generate in plugins/effects/rendered/test

if (!defined($ARGV[1])) {
    if ($^O eq "MSWin32") {
	$prefix_dir = "C:\\";
    } else {
	$prefix_dir = "/tmp";
    }
}
else {
    $prefix_dir = $ARGV[1];
}

if ($^O eq "MSWin32") {
    $plugin_file = "$prefix_dir\\$plugin_name.c";
}
else {
    $plugin_file = "$prefix_dir/$plugin_name.c";
}

if (defined($DEBUG)) {
    print STDERR "Creating plugin file $plugin_file\n";
}

my $num_inc = @in_chans;
my $num_outc = @out_chans;
my $num_params = @params;
my $num_oparams = @oparams;
my $incs = "NULL";
if ($num_inc) {
    $incs = "in_chantmpls";
}
my $outcs = "NULL";
if ($num_outc) {
    $outcs = "out_chantmpls";
}
my $inps = "NULL";
if ($num_params) {
    $inps = "in_paramtmpls";
}
my $outps = "NULL";
if ($num_oparams) {
    $outps = "out_paramtmpls";
}

my $use_sdata = 0;
my $has_init = 0;
my $has_deinit = 0;

my $can_inplace = 0;
my $init_func = "NULL";
my $deinit_func = "NULL";

my $proc_func = "$plugin_name" . "_process";

if (@sdata) {
    $use_sdata = 1;
}

if ($use_sdata || @init) {
    $has_init = 1;
    $init_func = "$plugin_name" . "_init";
}

if ($use_sdata || @deinit) {
    $has_deinit = 1;
    $deinit_func = "$plugin_name" . "_deinit";
}

my $pvary = 0;
my $svary = 0;
my $may_thread = 0;

my $fflagstr = "";
foreach (@filter_flags) {
    if ($_ eq "may thread" || $_ eq "may_thread") {
	$fflagstr .= " | WEED_FILTER_HINT_MAY_THREAD";
	$may_thread = 1;
    }
    elsif ($_ eq "converter") {
	$fflagstr .= " | WEED_FILTER_IS_CONVERTER";
    }
    elsif ($_ eq "sizes_vary") {
	$fflagstr .= " | WEED_FILTER_CHANNEL_SIZES_MAY_VARY";
	$svary = 1;
    }
    elsif ($_ eq "palettes_vary") {
	$fflagstr .= " | WEED_FILTER_CHANNEL_PALETTES_MAY_VARY";
	$pvary = 1;
    }
    elsif ($_ eq "stateful") {
	$fflagstr .= " | WEED_FILTER_HINT_STATEFUL";
    }
    elsif ($_ eq "process_last" || $_ eq "process last") {
	$fflagstr .= " | WEED_FILTER_HINT_PROCESS_LAST";
    }
    elsif ($_ eq "linear_gamma" || $_ eq "linear gamma") {
	$fflagstr .= " | WEED_FILTER_PREF_LINEAR_GAMMA";
    }
    elsif ($_ eq "premult_alpha" || $_ eq "premult alpha") {
	$fflagstr .= " | WEED_FILTER_PREF_PREMULTIPLIED_ALPHA";
    }
    elsif ($_ eq "unstable") {
	$fflagstr .= " | WEED_FILTER_HINT_MAYBE_UNSTABLE";
    }
}

my $cnum = 0;
foreach (@in_chans) {
    @pieces = split(/$delim/);
    if (@pieces > 1) {
	if (@pieces[1] =~ /planar/) {
	    push (@in_planar, $cnum);
	    last;
	}
    }
    $cnum++;
}

$cnum = 0;
foreach (@out_chans) {
    @pieces = split(/$delim/);
    if (@pieces > 1) {
	if (@pieces[1] =~ /inplace/) {
	    $can_inplace = 1;
	}
    }
    if (@pieces[1] =~ /planar/) {
	push (@out_planar, $cnum);
    }
    $cnum++;
}

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

open OUT, ">", $plugin_file;

chmod 0666, $plugin_file;

print OUT "/////////////////////////////////////////////////////////////////////////////\n";
print OUT "// Weed $plugin_name plugin, version $pver\n";
print OUT "// Compiled with Builder version $builder_version\n";
print OUT "// autogenerated from script @ARGV[0]\n";

print OUT "\n// released under the $license_type version $license_version\n";

if ($license_type eq "GNU GPL") {
    print OUT "// see file COPYING or www.gnu.org for details\n";
}

if (!$copyright_authors) {
    if (!$extra_authors) {
	$copyright_authors = $author;
    }
    else {
	$copyright_authors =  "$extra_authors and $author";
    }
}

if (!$copyright_years) {
    $copyright_years = $CURRENT_YEAR;
}

print OUT "\n// (c) $copyright_authors $copyright_years\n";

print OUT "/////////////////////////////////////////////////////////////////////////////\n\n";
print OUT "static int package_version = $pver; // version of this package\n\n";
print OUT "/////////////////////////////////////////////////////////////////////////////\n";

if ($uid) {
    print OUT "\n#define _UNIQUE_ID_ \"$uid\"\n\n";
}

#add requirements
my $need_putils = 0;
my $need_pconv = 0;
my $need_random = 0;
my $need_fontutils = 0;
my $need_pango_compat = 0;
my $need_audio = 0;
my $need_alpha_sort = 0;
my $need_fourcc = 0;
my $need_avstream = 0;
my $av_chanfmts = 0;
my $adv_palettes = 0;

if ($pal_lists[0] =~ /^ALL/) {
    $need_putils = 1;
}

foreach (@requires) {
    if ($_ eq "putils" || $_ eq "palette_utils") {
	$need_putils = 1;
	next;
    }
    if ($_ eq "pconv" || $_ eq "palette_conversions") {
	$need_pconv = 1;
	next;
    }
    if ($_ eq "random") {
	$need_random = 1;
	next;
    }
    if ($_ eq "fontutils") {
	$need_font_utils = 1;
	next;
    }
    if ($_ eq "pango_compat") {
	$need_pango_compat = 1;
	next;
    }
    if ($_ eq "audio") {
	$need_audio = 1;
	next;
    }
    if ($_ eq "alpha_sort") {
	$need_alpha_sort = 1;
	next;
    }
    if ($_ eq "fourcc_compat") {
	$need_fourcc_compat = 1;
	next;
    }
    if ($_ eq "avstream_types") {
	$need_avstrem_types = 1;
	next;
    }
    if ($_ eq "av_chanfmts") {
	$av_chanfmts = 1;
	next;
    }
    if ($_ eq "adv_palettes") {
	$av_s = 1;
	next;
    }
}

if ($need_putils) {
    print OUT "#define NEED_PALETTE_UTILS\n";
}

if ($need_random) {
    print OUT "#define NEED_RANDOM\n";
}

if ($need_pconv) {
    print OUT "#define NEED_PALETTE_CONVERSIONS\n";
}

if ($need_fontutils) {
    print OUT "#define NEED_FONT_UTILS\n";
}

if ($need_pango_compat) {
    print OUT "#include <pango/pango-font.h>\n";
    print OUT "#define NEED_PANGO_COMPAT\n";
}

if ($need_audio) {
    print OUT "#define NEED_AUDIO\n";
}

if ($need_alpha_sort) {
    print OUT "#define NEED_ALPHA_SORT\n";
}

if ($need_fourcc) {
    print OUT "#define NEED_FOURCC_COMPAT\n";
}

if ($need_avstream) {
    print OUT "#define NEED_AV_STREAM_TYPES\n";
}

if ($av_chanfmts) {
    print OUT "#define USE_AVUTIL_CHANNEL_LAYOUTS\n";
}

if ($adv_palettes) {
    print OUT "#define WEED_ADVANCED_PALETTES\n";
}

print OUT "\n#ifndef NEED_LOCAL_WEED_PLUGIN\n";
print OUT "#include <weed/weed-plugin.h>\n";
print OUT "#ifndef NEED_LOCAL_WEED_UTILS\n";
print OUT "#include <weed/weed-utils.h> // optional\n";
print OUT "#else\n";
print OUT "#include \"../../libweed/weed-utils.h\" // optional\n";
print OUT "#endif\n";
print OUT "#include <weed/weed-plugin-utils.h>\n";
print OUT "#else\n";
print OUT "#include \"../../libweed/weed-plugin.h\"\n";
print OUT "#include \"../../libweed/weed-utils.h\" // optional\n";
print OUT "#include \"../../libweed/weed-plugin-utils.h\" // optional\n";
print OUT "#endif\n\n";

print OUT "#include \"weed-plugin-utils.c\"\n\n";

print OUT "//////////////////////////////////////////////////////////////////\n\n";

## add includes
my $inc_stdio = 0;

foreach (@requires) {
    if ($_ =~ /^\</ || $_ =~ /^"/) {
	print OUT "#include $_\n";
	if ($_ eq "<stdio.h>") {
	    $inc_stdio = 1;
	}
    }
}

if ($uid && !$inc_stdio) {
    print OUT "#include <stdio.h>\n";
}

print OUT "\nstatic int verbosity = WEED_VERBOSITY_ERROR;\n";

if (@typedefs) {
    &add_typedefs;
}

if ($use_sdata) {
    &add_sdata;
}

if ($num_params) {
    print OUT "enum {\n";
    
    foreach (@params) {
	@pieces = split(/$delim/);
	my $pname = @pieces[0];
	print OUT "  P_$pname,\n";
    }

    print OUT "};\n";
}

if (@static) {
    # add static funcs
    print OUT "\n";
    &gen_array(0, 0, @static);
    print OUT "\n/////////////////////////////////////////////////////////////////////////////\n";
}

# add init

if ($has_init) {
    print OUT "\nstatic weed_error_t $init_func(weed_plant_t *inst) {\n";
    ## TODO - variable palettes, variable sizes
    ## TODO - rows for planar palettes
    ## TODO - singleton check

    &add_start(0);

    if ($num_params) {
	&get_paramvals(1, 0);
	&get_paramvals(0, 0);
    }

    ## gui only reinit
    print OUT "  if (!(weed_instance_get_flags(inst) & WEED_INSTANCE_UPDATE_GUI_ONLY)) {\n";
    
    if ($use_sdata) {
	print OUT "    sdata = (sdata_t *)weed_calloc(1, sizeof(sdata_t));\n";
	print OUT "    if (!sdata) return WEED_ERROR_MEMORY_ALLOCATION;\n";
    }

    if ($need_random) {
	print OUT "\n    weed_set_int64_value(inst, WEED_LEAF_PLUGIN_RANDOM_SEED,\n" .
	    "        weed_get_int64_value(inst, WEED_LEAF_RANDOM_SEED, NULL));\n";
    }

    if (@init) {
	## add main init code
	print OUT "\n    if (1) {\n";
	&gen_array(1, 6, @init);
	print OUT "    }\n";
    }

    print OUT "    weed_set_instance_data(inst, sdata);\n";
    print OUT "  }\n";

    if ($use_sdata) {
	print OUT "  else sdata = weed_get_instance_data(inst, sdata);\n";
    }

    ## add gui init code

    if ($num_params) {
	&clean_params;
    }

    &add_end(0);

    print OUT "\n  return WEED_SUCCESS;\n";
    print OUT "}\n\n";
}

if ($has_deinit) {
    print OUT "\nstatic weed_error_t $deinit_func(weed_plant_t *inst) {\n";

    if ($use_sdata) {
	print OUT "  sdata_t *sdata = weed_get_instance_data(inst, sdata);\n";
    }

    &gen_array(2, 2, @deinit);
    
    if ($use_sdata) {
	my $has_destr = 0;
	print OUT "\n  if (sdata) ";
	foreach (@sdata) {
	    @pieces = split(/$delim/);
	    my $destr = @pieces[2];
	    if ($destr) {
		my $field = @pieces[1];
		if (!$has_destr) {
		    print OUT "{\n    ";
		    $has_destr = 1;
		}
		print OUT "if (sdata->$field) $destr(sdata->$field);\n    ";
	    }
	}
	print OUT "weed_free(sdata);\n";
	if ($has_destr) {
	    print OUT "  }\n";
	}
	print OUT "  weed_set_instance_data(inst, NULL);\n";
    }

    print OUT "\n  return WEED_SUCCESS;\n";
    print OUT "}\n\n";
}

print OUT "\nstatic weed_error_t $proc_func(weed_plant_t *inst, weed_timecode_t tc) {\n";

&add_start(1);

if ($num_params) {
    &get_paramvals(1, 1);
    &get_paramvals(0, 1);
}

my $xspcs = "";
if ($use_sdata) {
    $xspcs = "  ";
}

## add threading
if ($may_thread && $num_outc == 1) {
    print OUT "\n$xspcs  if (is_threading) {\n";
    print OUT "$xspcs    offset = weed_channel_get_offset(out_chan);\n";
    if ($num_inc == 1) {
	print OUT "$xspcs    src += offset * irow;\n";
    }
    else {
	print OUT "$xspcs    src0 += offset * irow0;\n";
	print OUT "$xspcs    src1 += offset * irow1;\n";
    }
    print OUT "$xspcs    dst += offset * orow;\n";
    print OUT "$xspcs  }\n";
}

print OUT "\n";

print OUT "$xspcs  if (1) {\n";
my $indent = 4;
if ($use_sdata) {
    $indent = 6;
}
&gen_array(3, $indent, @proc);
print OUT "$xspcs  }\n";

if ($num_params) {
    &clean_params;
}

&add_end(1);
if ($use_sdata) {
    print OUT "  }\n";
}
print OUT "\n  return WEED_SUCCESS;\n";
print OUT "}\n\n";


## add disp_func
if (@disp) {
    &add_disp_func;
}

print OUT "\nWEED_SETUP_START(200, 200) {\n";
print OUT "  weed_plant_t *host_info = weed_get_host_info(plugin_info);\n";
print OUT "  weed_plant_t *filter_class;\n";

if ($uid) {
    print OUT "  uint64_t unique_id;\n";
}

if (@pal_list) {
    ## setup palette_list
    print OUT "  int palette_list[] = ";
    if ($pal_list[0] =~ /^ALL/) {
	print OUT "$pal_list[0];\n";
    }
    else {
	print OUT "{";
	foreach (@pal_list) {
	    @pieces = split(/$delim/);
	    foreach (@pieces) {
		if ($_ =~ /^WEED_PALETTE_/) {
		    print OUT $_ . ", ";
		}
		else {
		    print OUT "WEED_PALETTE_" . $_ . ", ";
		}
	    }
	}
	print OUT "WEED_PALETTE_END};\n";
    }
}

#setup in_chantmpls
if ($num_inc) {
    my $cnum = 0;
    print OUT "  weed_plant_t *$incs" . "[] = {\n      ";
    foreach (@in_chans) {
	$flagstr = "";
	@pieces = split(/$delim/);
	my $chan_name = @pieces[0];
	if (@pieces > 1) {
	    if (@pieces[1] =~ /reinit_on_size_change/) {
		$flagstr .= " | WEED_CHANNEL_REINIT_ON_SIZE_CHANGE";
	    }
	    if (@pieces[1] =~ /reinit_on_palette_change/) {
		$flagstr .= " | WEED_CHANNEL_REINIT_ON_PALETTE_CHANGE";
	    }
	    if (@pieces[1] =~ /reinit_on_rowstrides_change/) {
		$flagstr .= " | WEED_CHANNEL_REINIT_ON_ROWSTRIDES_CHANGE";
	    }
	    if (@pieces[1] =~ /optional/) {
		$flagstr .= " | WEED_CHANNEL_OPTIONAL";
	    }
	    if (@pieces[1] =~ /needs_natural_size/) {
		$flagstr .= " | WEED_CHANNEL_NEEDS_NATURAL_SIZE";
	    }
	    if (@pieces[1] =~ /planar/) {
		push (@in_planar, $cnum);
	    }
	    $cnum++;
	}
	print OUT "weed_channel_template_init(\"$chan_name\", ";
	if ($flagstr) {
	    print OUT substr($flagstr, 3);
	}
	else {
	    print OUT "0";
	}
	print OUT "),\n      ";
    }
    print OUT"NULL};\n";
}

#setup out_chantmpls
if ($num_outc) {
    my $cnum = 0;
    print OUT "  weed_plant_t *$outcs" . "[] = {\n      ";
    foreach (@out_chans) {
	$flagstr = "";
	@pieces = split(/$delim/);
	my $chan_name = @pieces[0];
	if (@pieces > 1) {
	    if (@pieces[1] =~ /inplace/) {
		$flagstr .= " | WEED_CHANNEL_CAN_DO_INPLACE";
	    }
	    if (@pieces[1] =~ /reinit_on_size_change/) {
		$flagstr .= " | WEED_CHANNEL_REINIT_ON_SIZE_CHANGE";
	    }
	    if (@pieces[1] =~ /reinit_on_palette_change/) {
		$flagstr .= " | WEED_CHANNEL_REINIT_ON_PALETTE_CHANGE";
	    }
	    if (@pieces[1] =~ /reinit_on_rowstrides_change/) {
		$flagstr .= " | WEED_CHANNEL_REINIT_ON_ROWSTRIDES_CHANGE";
	    }
	    if (@pieces[1] =~ /optional/) {
		$flagstr .= " | WEED_CHANNEL_OPTIONAL";
	    }
	    if (@pieces[1] =~ /planar/) {
		push (@out_planar, $cnum);
	    }
	}
	print OUT "weed_channel_template_init(\"$chan_name\", ";
	if ($flagstr) {
	    print OUT substr($flagstr, 3);
	}
	else {
	    print OUT "0";
	}
	print OUT "),\n      ";
	$cnum++;
    }
    print OUT"NULL};\n";
}

#setup in_paramtmpls
if ($num_params) {
    print OUT "  weed_plant_t *$inps" . "[] = {\n      ";
    foreach (@params) {
	@pieces = split(/$delim/);
	my $pname = @pieces[0];
	my $plabel = @pieces[1];
	my $type = @pieces[2];
	if ($type =~ /^num/) {
	    my $pdef = @pieces[3];
	    my $pmin = @pieces[4];
	    my $pmax = @pieces[5];
	    my $dp = 0 + substr($type, 3);
	    if (!$dp) {
		print OUT "weed_integer_init(\"$pname\", \"$plabel\", $pdef, $pmin, $pmax)";
	    }
	    else {
		print OUT "weed_float_init(\"$pname\", \"$plabel\", $pdef, $pmin, $pmax)";
	    }
	}
	elsif ($type eq "bool") {
	    if (@pieces > 4) {
		print OUT "weed_radio_init(\"$pname\", \"$plabel\", ";
	    }
	    else {
		print OUT "weed_switch_init(\"$pname\", \"$plabel\", ";
	    }

	    if (@pieces[3] eq "1" || @pieces[3] eq "WEED_TRUE" || @pieces[3] eq "TRUE" || @pieces[3] eq "true") {
		print OUT "WEED_TRUE";
	    }
	    else {
		print OUT "WEED_FALSE";
	    }
	    if (@pieces > 4) {
		print OUT ", @pieces[4]";
	    }
	    print OUT ")";
	}
	elsif ($type eq "string") {
	    my $pdef = @pieces[3];
	    print OUT "weed_text_init(\"$pname\", \"$plabel\", $pdef)";
	}
	elsif ($type eq "string_list") {
	    my $pdef = @pieces[3];
	    print OUT "weed_string_list_init(\"$pname\", \"$plabel\", $pde, $listnamef)";
	}
	elsif ($type eq "colRGB24") {
	    my $pred = @pieces[3];
	    my $pgreen = @pieces[4];
	    my $pblue = @pieces[5];
	    if ($pred =~ /\./) {
		print OUT "weed_colRGBd";
	    }
	    else {
		print OUT "weed_colRGBi";
	    }
	    print OUT "_init(\"$pname\", \"$plabel\", $pred, $pgreen, $pblue)";
	}
	print OUT ",\n      ";
    }
    print OUT"NULL};\n";
}

#setup out_paramtmpls
if ($num_oparams) {
    print OUT "  weed_plant_t *$outps" . "[] = {\n      ";
    foreach (@oparams) {
	@pieces = split(/$delim/);
	my $pname = @pieces[0];
	my $type = @pieces[1];
	if ($type =~ /^num/) {
	    my $pdef = @pieces[2];
	    my $dp = 0 + substr($type, 3);
	    if (!$dp) {
		if (@pieces > 3) {
		    my $pmin = @pieces[3];
		    my $pmax = @pieces[4];
		    print OUT "weed_out_param_integer_init(\"$pname\", $pdef, $pmin, $pmax)";
		} else {
		    print OUT "weed_out_param_integer_init_nominmax(\"$pname\", $pdef)";
		}
	    }
	    else {
		if (@pieces > 3) {
		    my $pmin = @pieces[3];
		    my $pmax = @pieces[4];
		    print OUT "weed_out_param_float_init(\"$pname\", $pdef, $pmin, $pmax)";
		} else {
		    print OUT "weed_out_param_float_init_nominmax(\"$pname\", $pdef)";
		}
	    }
	}
	elsif ($type eq "bool") {
	    print OUT "weed_out_param_switch_init(\"$pname\",  ";
	    if (!@pieces[3]) {
		print OUT "WEED_FALSE";
	    }
	    else {
		print OUT "WEED_TRUE";
	    }
	    print OUT ")";
	}
	elsif ($type eq "string") {
	    my $pdef = @pieces[2];
	    print OUT "weed_out_param_text_init(\"$pname\", $pdef)";
	}
	elsif ($type eq "colRGB24") {
	    my $pred = @pieces[2];
	    my $pgreen = @pieces[3];
	    my $pblue = @pieces[4];
	    if ($pred =~ /\./) {
		print OUT "weed_out_param_colRGBd";
	    }
	    else {
		print OUT "weed_out_param_colRGBi";
	    }
	    print OUT "_init(\"$pname\", $pred, $pgreen, $pblue)";
	}
	print OUT ",\n      ";
    }
    print OUT"NULL};\n";
}

if (@param_window) {
    print OUT "  weed_plant_t *gui;\n";
    print OUT "  char *rfx_strings[] = {\n      ";
    foreach (@param_window) {
	@pieces = split(/$delim/);
	if (@pieces[1] eq "display_val") {
	    push (@dispfuncs, $_);
	    $nwlines--;
	    next;
	}
	print OUT "\"" . $_ . "\",\n      ";
    }
    print OUT "NULL};\n";
}

if ($num_params) {
    &add_pgui(1, @params);
}

## setup flags
print OUT "  int filter_flags = ";

if ($fflagstr) {
    print OUT substr($fflagstr, 3);
}
else {
    print OUT "0";
}
print OUT ";\n";

print OUT "\n";

## set channel flags

## set param flags

print OUT "  verbosity = weed_get_host_verbosity(host_info);\n";

my$addnl = 1;
if ($num_params) {
    $addnl = &add_pgui(0, @params);
}

if ($addnl) {
    print OUT "\n";
}

my $pl = "NULL";
if (@pal_list) {
    $pl = "palette_list";
}

print OUT "  filter_class = weed_filter_class_init(\"$filter_name\", \"$author\", $fver, filter_flags, " .
    "$pl,\n    $init_func, $proc_func, $deinit_func, $incs, $outcs, $inps, $outps);\n\n";

if ($extra_authors) {
    print OUT "  weed_set_string_value(filter_class, WEED_LEAF_EXTRA_AUTHORS, \"@authors[1]\");\n";
}

## set filter gui
if (@param_window) {
    print OUT "  gui = weed_filter_get_gui(filter_class);\n";
    print OUT "  weed_set_string_value(gui, WEED_LEAF_LAYOUT_SCHEME, \"RFX\");\n";
    print OUT "  weed_set_string_value(gui, \"layout_rfx_delim\", \"$delimit\");\n";
    print OUT "  weed_set_string_array(gui, \"layout_rfx_strings\", " . $nwlines . ", rfx_strings);\n\n";
}

if (@description) {
    print OUT "  weed_filter_set_description(filter_class, \"@description[0]\"";
    shift @description;
    foreach (@description) {
	print OUT "\n\t\t\t\t\"$_\"";
    }
    print OUT ");\n\n";
}

## set layout

print OUT "  weed_plugin_info_add_filter_class(plugin_info, filter_class);\n";

if ($uid) {
    print OUT "\n  if (!sscanf(_UNIQUE_ID_, \"0X%lX\", &unique_id) || !sscanf(_UNIQUE_ID_, \"0x%lx\", &unique_id)) {\n";
    print OUT "    weed_set_int64_value(plugin_info, WEED_LEAF_UNIQUE_ID, unique_id);\n";
    print OUT "  }\n\n";
}

if (@setup) {
    print OUT "  if (1) {\n";
    &gen_array(5, 4, @setup);
    print OUT "  }\n\n";
}

print OUT "  weed_plugin_set_package_version(plugin_info, package_version);\n";
print OUT "}\n";
print OUT "WEED_SETUP_END;\n\n";

## add desetup


#######################################################3

sub rc_read {
    # return an array value from script file
    my ($key, $scriptfile) = @_;
    my $string = "";
    my (@result, $part);

    unless (defined(open IN, "$scriptfile")) {
	print STDERR "Error:  - Unable to read values from script file,  $scriptfile\n";
	exit 2;
    }
    $part = 0;
    while (<IN>) {
	next if ($_ =~ /#/);
	if ($_ =~ /(.*)(<\/$key>)/) {
	    return @result;
	}
	if ($part == 1 || $_ =~ /(<$key>)(.*)/) {
	    if ($part == 1) {
		chomp($_);
		$string = $_;
		@result = (@result, $string);
	    }
	    else {
		$part = 1;
	    }}}
    return @result;
}

sub make_ver_hash {
    # turn a version like
    # a.b.c into an integer
    # a * 1,000,000 plus b * 1,000 plus c
    # eg. 1.4.6 becomes 10004006

    my $string = shift;
    if ($string eq "") {
	return 0;
    }
    my ($ver_major, $ver_minor, $ver_micro) = split (/\./,  $string, 3);
    my $version_hash = ($ver_major * 1000 + $ver_minor) * 1000;
    $version_hash;
}

sub subst_macro {
    my ($type, $nspaces, $line) = @_;
    if ($type == 3) {
	## proc
	if ($line eq "START_STATEFUL_UPDATE") {
	    print OUT " " x $nspaces . "if (is_threading) {\n";
	    print OUT " " x $nspaces . "  if (!weed_plant_has_leaf(inst, WEED_LEAF_STATE_UPDATED)) {\n";
	    print OUT " " x $nspaces . "    return WEED_ERROR_FILTER_INVALID;\n"; 
	    print OUT " " x $nspaces . "  }\n";
	    print OUT " " x $nspaces . "  /* host should set this to WEED_FALSE for the first thread, then wait for WEED_TRUE */\n";
	    print OUT " " x $nspaces . "  if (weed_get_boolean_value(inst, WEED_LEAF_STATE_UPDATED, NULL) == WEED_FALSE) {\n";
	    return 4;
	}
	if ($line eq "END_STATEFUL_UPDATE") {
	    $nspaces -= 4;
	    print OUT " " x $nspaces . "    weed_set_boolean_value(inst, WEED_LEAF_STATE_UPDATED, WEED_TRUE);\n";
	    print OUT " " x $nspaces . "  }\n";
	    print OUT " " x $nspaces . "}\n";
	    return -4;
	}
    }
    return "none";
}

sub gen_array {
    my ($type, $nspaces, @array) = @_;
    ## type: 0 = static, 1 = init, 2 = deinit, 3 = proc
    foreach (@array) {
	if ($_ eq "") {
	    print OUT "\n";
	    next;
	}
	my $xspaces = &subst_macro($type, $nspaces, $_);
	if ($xspaces eq "none") {
	    print OUT " " x $nspaces . $_ . "\n";
	}
	else {
	    $nspaces += $xspaces;
	}
    }
}

sub add_start {
    my $is_procfunc = shift;
    
    if ($num_inc > 0) {
	if ($num_inc == 1) {
	    print OUT "  weed_plant_t *in_chan = weed_get_in_channel(inst, 0);\n";
	}
	else {
	    print OUT "  weed_plant_t **in_chans = weed_get_in_channels(inst, NULL);\n";
	}
    }

    if ($num_outc > 0) {
	if ($num_outc == 1) {
	    print OUT "  weed_plant_t *out_chan = weed_get_out_channel(inst, 0);\n";
	}
	else {
	    print OUT "  weed_plant_t **out_chans = weed_get_out_channels(inst, NULL);\n";
	}
    }

    if ($num_params) {
	print OUT "  weed_plant_t **in_params = weed_get_in_params(inst, NULL);\n";
    }
    if ($num_oparams) {
	print OUT "  weed_plant_t **out_params = weed_get_out_params(inst, NULL);\n";
    }

    if ($is_procfunc) {
	if ($num_inc > 0) {
	    if ($num_inc == 1) {
		if (@in_planar) {
		    print OUT "  unsigned char **srcp = " .
			"(unsigned char **)weed_channel_get_pixel_data_planar" .
			"(in_chan, NULL);\n";
		}
		else {
		    print OUT "  unsigned char *src = weed_channel_get_pixel_data(in_chan);\n";
		}
	    }
	    else {
		for ($i = 0; $i < $num_inc; $i++) {
		    my $got_planar = 0;
		    foreach (@in_planar) {
			if ($_ == $i) {
			    $got_planar = 1;
			    last;
			}
		    }
		    if (!$got_planar) {
			print OUT "  unsigned char *src$i = weed_channel_get_pixel_data(" .
			    "in_chans[$i]);\n";
		    }
		    else {
			print OUT "  unsigned char **srcp$i = " .
			    "(unsinged_char **)weed_channel_get_pixel_data_planar(" .
			    "in_chans[$i]);\n";
		    }
		}
	    }
	}

	if ($num_outc > 0) {
	    if ($num_outc == 1) {
		if (@out_planar) {
		    print OUT "  unsigned char **dstp " .
			"= (unsigned char **)weed_channel_get_pixel_data_planar" .
			"(out_chan, NULL);\n";
		}
		else {
		    print OUT "  unsigned char *dst = weed_channel_get_pixel_data(out_chan);\n";
		}
	    }
	    else {
		for ($i = 0; $i < $num_outc; $i++) {
		    my $got_planar = 0;
		    foreach (@out_planar) {
			if ($_ == $i) {
			    $got_planar = 1;
			    last;
			}
		    }
		    if (!$got_planar) {
			print OUT "  unsigned char *dst$i = weed_channel_get_pixel_data(" .
			    "out_chans[$i]);\n";
		    }
		    else {
			print OUT "  unsigned char **dstp$i = " .
			    "(unsigned char **)weed_channel_get_pixel_data_planar(" .
			    "out_chans[$i], NULL);\n";
		    }
		}
	    }
	}

	if ($can_inplace && $num_outc == 1) {
	    if ($num_inc == 1) {
		print OUT "  int inplace = (src == dst);\n";
	    }
	    elsif ($num_inc > 1) {
		print OUT "  int inplace = (src0 == dst);\n";
	    }
	}
    }

    if ($num_inc > 0) {
	if ($num_inc == 1) {
	    if (!$num_outc) {
		print OUT "  int width = weed_channel_get_width(in_chan);\n";
		print OUT "  int height = weed_channel_get_height(in_chan);\n";
	    }
	    print OUT "  int pal = weed_channel_get_palette(in_chan);\n";
	    if (!@in_planar) {
		print OUT "  int irow = weed_channel_get_stride(in_chan);\n";
	    }
	    else {
		print OUT "  int *irows = weed_channel_get_rowstrides(in_chan, NULL);\n";
	    }
	}
	else {
	    if (!$num_outc) {
		print OUT "  int width = weed_channel_get_width(in_chans[0]);\n";
		print OUT "  int height = weed_channel_get_height(in_chans[0]);\n";
	    }
	    print OUT "  int pal = weed_channel_get_palette(in_chans[0]);\n";
	    for ($i = 0; $i < $num_inc; $i++) {
		my $got_planar = 0;
		foreach (@out_planar) {
		    if ($_ == $i) {
			$got_planar = 1;
			last;
		    }
		}
		if (!$got_planar) {
		    print OUT "  int irow$i = weed_channel_get_stride(in_chans[$i]);\n";
		}
		else {
		    print OUT "  int *irows$i = weed_channel_get_rowstrides(in_chans[$i], NULL);\n";
		}
	    }
	}
    }

    if ($is_procfunc) {
	if ($may_thread) {
	    if ($num_outc == 1) {
		if ($num_inc == 1) {
		    print OUT "  int iheight = weed_channel_get_height(in_chan);\n";
		}
		else {
		    print OUT "  int iheight = weed_channel_get_height(in_chans[0]);\n";
		}
		print OUT "  int is_threading = weed_is_threading(inst);\n";
	    }
	    print OUT "  int offset = 0;\n";
        }
    }
    if ($num_outc > 0) {
	if ($num_outc == 1) {
	    print OUT "  int width = weed_channel_get_width(out_chan);\n";
	    print OUT "  int height = weed_channel_get_height(out_chan);\n";
	    if (@out_planar) {
		print OUT "  int *orows = weed_channel_get_rowstrides(out_chan, NULL);\n";
	    }
	    else {
		print OUT "  int orow = weed_channel_get_stride(out_chan);\n";
	    }
	    if (!$num_inc) {
		print OUT "  int pal = weed_channel_get_palette(out_chan);\n";
	    }
	}
	else {
	    print OUT "  int width = weed_channel_get_width(out_chans[0]);\n";
	    print OUT "  int height = weed_channel_get_height(out_chans[0]);\n";
	    for (my $i = 0; $i < $num_inc; $i++) {
		my $got_planar = 0;
		foreach (@out_planar) {
		    if ($_ == $i) {
			$got_planar = 1;
			last;
		    }
		}
		if (!$got_planar) {
		    print OUT "  int orow$i = weed_channel_get_stride(out_chans[$i]);\n";
		}
		else {
		    print OUT "  int orows$i = weed_channel_get_rowstrides(out_chans[$i], NULL);\n";
		}
	    }
	    if (!$num_inc) {
		print OUT "  int pal = weed_channel_get_palette(out_chans[0]);\n";
	    }
	}
    }
    if ($need_putils && ($num_inc || $num_out)) {
	print OUT "  int psize = pixel_size(pal);\n";
    }
    if ($use_sdata) {
	print OUT "  sdata_t *sdata";
	if ($is_procfunc) {
	    print OUT " = weed_get_instance_data(inst, sdata);\n";
	    print OUT "  if (!sdata) return WEED_ERROR_REINIT_NEEDED;\n";
	    print OUT "  else {\n";
	}
	else {
	    print OUT ";\n";
	}
    }
}

sub add_end {
    my $is_procfunc = shift;
    if ($is_procfunc) {
	if (@in_planar) {
	    if (@in_planar == 1) {
		print OUT "  weed_free(srcp);\n";
	    }
	    else {
		foreach (@in_planar) {
		    print OUT "  weed_free(srcp$_);\n";
		}
	    }
	}
	if (@out_planar) {
	    if (@out_planar == 1) {
		print OUT "  weed_free(dstp);\n";
	    }
	    else {
		foreach (@in_planar) {
		    print OUT "  weed_free(dstp$_);\n";
		}
	    }
	}
    }
    if ($num_oparams) {
	print OUT "  weed_free(out_params);\n";
    }
    if ($num_inc > 1) {
	print OUT "  weed_free(in_chans);\n";
    }
    if ($num_outc > 1) {
	print OUT "  weed_free(out_chans);\n";
    }
}

sub add_typedefs {
    foreach (@typedefs) {
	@pieces = split(/$delim/, $_);
	my $p0 = @pieces[0];
	my $p1 = @pieces[1];
	print OUT "typedef $p0 $p1;\n";
    }
}

sub add_sdata {
    print OUT "\ntypedef struct {\n";
    foreach (@sdata) {
	@pieces = split(/$delim/);
	my $spacer = " ";
	if (@pieces[0] =~ /\*$/) {
	    $spacer = "";
	}
	print OUT "  @pieces[0]$spacer@pieces[1];\n";
    }
    print OUT "} sdata_t;\n\n";
}

sub get_paramvals {
    ## TODO - handle multi valued params
    my ($checkonly, $isproc) = @_;
    if (!$checkonly && !$isproc) {
	print OUT "\n";
    }
    my $spcs = "";
    if ($isproc && $use_sdata) {
	$spcs = "  ";
    }
    foreach (@params) {
	@pieces = split(/$delim/);
	my $pname = @pieces[0];
	my $type = @pieces[2];
	if ($type eq "colRGB24") {
	    my $pred = @pieces[3];
	    if ($pred =~ /\./) {
		if (!$has_darray) {
		    $has_darray = 1;
		    if ($checkonly) {
			print OUT "$spcs  double *darray;\n";
		    }
		}
		if ($checkonly) {
		    print OUT "$spcs  double $pname" . "_red, $pname" . "_green, $pname" . "_blue;\n";
		}
		else {
		    print OUT "$spcs  darray = weed_param_get_array_double(in_params[P_$pname], NULL);\n";
		    print OUT "$spcs  $pname" . "_red = darray[0];\n";
		    print OUT "$spcs  $pname" . "_green = darray[1];\n";
		    print OUT "$spcs  $pname" . "_blue = darray[2];\n";
		}
	    }
	    else {
		if (!$has_iarray) {
		    $has_iarray = 1;
		    if ($checkonly) {
			print OUT "$spcs  int *iarray;\n";
		    }
		}
		if ($checkonly) {
		    print OUT "$spcs  int $pname" . "_red, $pname" . "_green, $pname" . "_blue;\n";
		}
		else {
		    print OUT "$spcs  iarray = weed_param_get_array_int(in_params[P_$pname], NULL);\n";
		    print OUT "$spcs  $pname" . "_red = iarray[0];\n";
		    print OUT "$spcs  $pname" . "_green = iarray[1];\n";
		    print OUT "$spcs  $pname" . "_blue = iarray[2];\n";
		}
	    }
	}
	next if $checkonly;
	if ($type =~ /^num/) {
	    my $dp = 0 + substr($type, 3);
	    if (!$dp) {
		print OUT "$spcs  int $pname = weed_param_get_value_int(in_params[P_$pname]);\n";
	    }
	    else {
		print OUT "$spcs  double $pname = weed_param_get_value_double(in_params[P_$pname]);\n";
	    }
	}
	elsif ($type eq "string_list") {
	    print OUT "$spcs  int $pname = weed_param_get_value_int(in_params[P_$pname]);\n";
	}
	elsif ($type eq "bool") {
	    print OUT "$spcs  int $pname = weed_param_get_value_boolean(in_params[P_$pname]);\n";
	}
	elsif ($type eq "string") {
	    print OUT "$spcs  int $pname = weed_param_get_value_string(in_params[P_$pname]);\n";
	}
    }
    if (!$checkonly) {
	if ($has_iarray) {
	    print OUT "$spcs  weed_free(iarray);\n";
	}
	if ($has_darray) {
	    print OUT "$spcs  weed_free(darray);\n";
	}
	print OUT "$spcs  weed_free(in_params);\n";
	if (!$isproc) {
	    print OUT "\n";
	}
    }
}


sub clean_params {
    ## TODO - handle multi valued params
    foreach (@params) {
	@pieces = split(/$delim/);
	my $pname = @pieces[0];
	my $type = @pieces[2];
	if ($type eq "string") {
	    print OUT "  weed_free($pname);\n";
	}
    }
}


sub add_pgui {
    my $checkonly = shift;
    my $pnum = -1;
    my $dispfunc;
    my $fnname;
    my $pstep;
    my $dp;
    my $retval = 1;
    foreach (@params) {
	my $mydispfunc = "";
	$pnum++;
	foreach (@dispfuncs) {
	    @pieces = split(/$delim/);
	    $fnname = @pieces[2];
	    for (my $i = 3; @pieces[$i] ne ""; $i++) {
		if (0 + @pieces[$i] == $pnum) {
		    $mydispfunc = $fnname;
		    last;
		}
	    }
	}
	@pieces = split(/$delim/);
	my $pname = @pieces[0];
	my $type = @pieces[2];
	if ($type =~ /^num/) {
	    $pstep = 0;
	    if (@pieces > 6) {
		$pstep = @pieces[6];
	    }
	    $dp = 0 + substr($type, 3);
	    if ($dp || $pstep || $mydispfunc) {
		if ($checkonly) {
		    print OUT "  weed_plant_t *pgui;\n";
		    return;
		}
		print OUT "\n  pgui = weed_paramtmpl_get_gui(in_paramtmpls[P_$pname]);\n";
	    }
	    if ($dp) {
		print OUT "  weed_set_int_value(pgui, WEED_LEAF_DECIMALS, $dp);\n";
		if ($pstep) {
		    print OUT "  weed_set_double_value(pgui, WEED_LEAF_STEP_SIZE, $pstep);\n";
		}
	    }
	    elsif ($pstep) {
		print OUT "  weed_set_double_value(pgui, WEED_LEAF_STEP_SIZE, $pstep);\n";
	    }
	}
	if ($mydispfunc) {
	    print OUT "  weed_set_funcptr_value(pgui, WEED_LEAF_DISPLAY_VALUE_FUNC, " .
		"(weed_funcptr_t)$plugin_name" . "_disp_$mydispfunc);\n";
	}
	if ($dp || $pstep || $mydispfunc) {
	    #$retval = 0;
	}
    }
    return $retval;
}


sub disp_fn_end {
    my $wtype = shift;
    print OUT "    }\n";
    print OUT "    return weed_set_$wtype" . "_value(weed_param_get_gui(param), " .
	"WEED_LEAF_DISPLAY_VALUE, val);\n";
    print OUT "  }\n";
}


sub add_disp_func {
    my $stage = 0;
    my $inv_rev = "";
    my $wtype;

    foreach (@disp) {
	if (!$stage) {
	    next if (!$_);
	    @pieces = split(/$delim/);
	    my $fname = @pieces[0];
	    my $vtype = @pieces[1];
	    my $inv_fwd = @pieces[2];
	    $inv_rev = @pieces[3];
	    $wtype = $vtype;
	    print OUT "\nstatic weed_error_t $plugin_name" .
		"_disp_$fname(weed_plant_t *inst, weed_plant_t *param, " .
		"int inverse) {\n";
	    print OUT "  if (!weed_plant_has_leaf(param, WEED_LEAF_VALUE)) " .
		"return WEED_ERROR_NOSUCH_LEAF;\n";
	    print OUT "  else {\n";
	    ## get val
	    print OUT "    $vtype val = weed_param_get_value_$wtype(param);\n";
	    print OUT "    if (inverse == WEED_FALSE) {\n";
	    if ($inv_fwd) {
		print OUT "      if ($inv_fwd) return WEED_ERROR_NOT_READY;\n";
	    }
	    $stage = 1;
	    next;
	}
	if ($stage == 1) {
	    if ($_ eq "~") {
		$stage = 2;
		print OUT "    }\n";
		print OUT "    else {\n";
		if ($inv_rev) {
		    print OUT "      if ($inv_rev) return WEED_ERROR_NOT_READY;\n";
		}
		next;
	    }
	    print OUT "      $_\n";
	    next;
	}
	if ($_ eq "~") {
	    &disp_fn_end($wtype);
	    $stage = 0;
	    next;
	}
	print OUT "      $_\n";
    }
    &disp_fn_end($wtype);
    print OUT "}\n\n";
}
