#! /usr/bin/python
# -*- coding: utf-8 -*-

import re, os, sys
from cStringIO import StringIO
from subprocess import Popen, PIPE
from optparse import OptionParser


num_re = r"(-?\d*\.?\d*e?[+-]?\d*f?)"

def wrap_N_(s):
    if s:
        return 'N_("%s")' % s
    else:
        return '""'

class UIDefs(object):

    class UID(dict):
        def write(self, fp, v, prefix):
            d = {}
            d.update(self)
            d["tail"] = "".join([", "+x for x in d["value"]])
            d["prefix"] = prefix
            d["N_name"] = wrap_N_("%(name)s" % self)
            d["N_tooltip"] = wrap_N_("%(tooltip)s" % self)
            if "alias" in d:
                assert "enum" not in d
                fp.write('\t%(variable)s_ = %(prefix)sregisterVar("%(id)s",%(N_name)s,"%(type)sA",%(N_tooltip)s,&%(variable)s%(tail)s);\n' % d)
            elif "enum" in d:
                def value_pair(s):
                    m = re.match(r"(.*)\[(.*)\]$", s)
                    if m:
                        return '{"%s",%s}' % (m.group(1), wrap_N_(m.group(2)))
                    else:
                        return '{"%s"}' % s
                enumvals = ",".join([value_pair(x) for x in d["enum"].split("|")]+["{0}"])
                d["ename"] = ename = d["variable"] + "_values"
                fp.write('\tstatic const value_pair %s[] = {%s};\n' % (ename, enumvals))
                fp.write('\t%(prefix)sregisterEnumVar("%(id)s",%(N_name)s,"%(type)s",%(N_tooltip)s,%(ename)s,&%(variable)s%(tail)s);\n' % d)
            else:
                fp.write('\t%(prefix)sregisterVar("%(id)s",%(N_name)s,"%(type)s",%(N_tooltip)s,&%(variable)s%(tail)s);\n' % d)

        def __getitem__(self, n):
            try:
                return dict.__getitem__(self, n)
            except KeyError:
                return ""

    def __init__(self):
        self.ui = {}

    def add(self, element, key, value):
        try:
            uid = self.ui[element]
        except KeyError:
            self.ui[element] = uid = self.UID(variable=element)
        uid[key] = value

    def get(self, element, key):
        return self.ui[element][key]

    def has(self, element, key):
        return self.ui[element].has_key(key)

    def var_filter(self, const=2):
        s = "|".join([r"%s\s*=\s*%s" % (v, num_re) for v in self.ui.keys()])
        def filt(t):
            if re.compile(r"\s*("+s+");").match(t):
                return True
            if const == 1 and t.startswith("for "):
                return True
            if const == 0 and not t.startswith("for "):
                return True
            return False
        return filt

    def write(self, fp, prefix=""):
        for v, r in self.ui.items():
            r.write(fp, v, prefix)

    def check_parameter(self, fname, uiname, l):
        s1 = set(l)
        s2 = set([v["id"] for v in self.ui.values()])
        d = s2 - s1
        errlevel = 0
        if d:
            print ("%s:warning: parameters in faust dsp not used in %s: %s"
                   % (fname, uiname, ", ".join(d)))
            errlevel = 1
        d = s1 - s2
        if d:
            print ("%s:error: parameters in %s not found in faust dsp: %s"
                   % (fname, uiname, ", ".join(d)))
            errlevel = 2
        return errlevel


class Parser(object):

    def skip_until(self, exp):
        r = re.compile(exp)
        for line in self.lines:
            m = r.match(line)
            if m:
                return m
        return None

    def skip_while(self, exp):
        m = re.compile(exp).match
        for line in self.lines:
            if not m(line):
                return line
        return ""

    def copy(self, exp, line=None):
        cp = []
        if line:
            cp.append(line)
        m = re.compile(exp).match
        for line in self.lines:
            if m(line):
                break
            cp.append(line)
        # remove indentation
        m = re.compile(r"\t*").match
        n = 10
        for l in cp:
            if l != "\n":
                n = min(n, len(m(l).group(0)))
        return [l[n:] for l in cp]

    def get_section_list(self):
        return "includes", "var-decl", "alias-defines", "alias-undefines", "var-init", "var-free", "ui", "compute"

    def getIO(self, s):
        e = r"\s*virtual int getNum%sputs\(\)\s*{\s*return\s*(\d+);\s*}" % s
        for line in self.lines:
            m = re.match(e, line)
            if m:
                return int(m.group(1))
        raise ValueError("getNum%sputs not found in source" % s)

    def parse_name(self, s):
        def findBrackets(aString):
            l = []
            n = []
            while aString:
                v = aString.split('[',1)
                if len(v) == 2:
                    s, match = v
                else:
                    n.append(aString)
                    break
                s.strip()
                if s:
                    n.append(s)
                match.lstrip()
                open = 1
                for index in range(len(match)):
                    if match[index] in '[]':
                        open = (open + 1) if match[index] == '[' else (open - 1)
                    if not open:
                        l.append(match[:index])
                        break
                aString = match[index+1:].lstrip()
            return " ".join(n), l
        s, l = findBrackets(s)
        for v in l:
            if ":" in v:
                key, value = v.split(":",1)
            else:
                key = v
                value = ""
            if key == "name" and value:
                self.groups[s] = value
        return s

    def readUI(self, exp):
        stop = re.compile(exp).match
        pre = r"\s*interface->"
        nm = '"([^"]*)"'
        vr = "([a-zA-Z_][0-9a-zA-Z_]*)"
        sarg = (r"%s,\s*&%s,\s*%s,\s*%s,\s*%s,\s*%s"
                % (nm, vr, num_re, num_re, num_re, num_re))
        openbox = re.compile(pre+r"open(Horizontal|Vertical)Box\(%s\);" % nm).match
        closebox = re.compile(pre+r"closeBox\(\);").match
        vslider = re.compile(pre+r"addVerticalSlider\(%s\);" % sarg).match
        hslider = re.compile(pre+r"addHorizontalSlider\(%s\);" % sarg).match
        numentry = re.compile(pre+r"addNumEntry\(%s\);" % sarg).match
        checkbutton = re.compile(pre+r"addCheckButton\(%s,\s*&%s\);" % (nm, vr)).match
        declare = re.compile(pre+r"declare\((?:&%s|0),\s*%s,\s*%s\);" % (vr, nm, nm)).match
        stack = []
        def make_name_int(mystack):
            st = mystack[1:]
            if not st:
                return mystack[0]
            s = make_name_int(st)
            if s.startswith("."):
                return s
            return mystack[0] + "." + s
            
        def make_name(nm):
            stack.append(nm)
            nm = make_name_int(stack)
            stack.pop()
            if nm.startswith("."):
                return nm[1:]
            return nm

        nextbox = {}
        for line in self.lines:
            if stop(line):
                return
            m = openbox(line)
            if m:
                grp = m.group(2)
                if "[" in grp:
                    # 0.9.30 didn't parse [..] in [hv]group names
                    grp = self.parse_name(grp)
                if nextbox:
                    if "name" in nextbox:
                        self.groups[grp] = nextbox["name"]
                    # ignore all other attributes for now
                    nextbox = {}
                if not stack:
                    if self.toplevel and grp == self.modname:
                        grp = self.toplevel
                    if not self.topname:
                        self.topname = grp
                stack.append(grp)
                continue
            if closebox(line):
                stack.pop()
                continue
            m = vslider(line) or hslider(line) or numentry(line)
            if m:
                vn = m.group(2)
                self.ui.add(vn, "type", "S")
                self.ui.add(vn, "id", make_name(m.group(1)))
                self.ui.add(vn, "value", m.groups()[2:])
                continue
            m = checkbutton(line)
            if m:
                vn = m.group(2)
                self.ui.add(vn, "type", "B")
                self.ui.add(vn, "id", make_name(m.group(1)))
                self.ui.add(vn, "value", ("0.0","0.0","1.0","1.0"))
                continue
            m = declare(line)
            if m:
                if m.group(1) is None: # 0.9.43: attributes for next openbox
                    nextbox[m.group(2)] = m.group(3)
                else:
                    self.ui.add(m.group(1), m.group(2), m.group(3))
                continue
            assert False, line

    def readMeta(self):
        "only needed for faust 9.4; not used at the moment"
        self.meta = {}
        stop = re.compile(r'// Code generated with Faust').match
        declare = re.compile(r'// ([^:]+):\s*"([^"]*)"\s*$').match
        for line in self.lines:
            if stop(line):
                return
            m = declare(line)
            if m:
                key = m.group(1)
                value = m.group(2)
                self.meta[key] = value
                if key == "name":
                    self.toplevel = value

    def splitgroups(self, value):
        d = {}
        for l in re.split(r"\]\s*,\s*", value):
            a = re.split(r"\s*\[\s*",l,1)
            g = a[0].strip()
            if len(a) == 1:
                v = "?"
            else:
                v = a[1].rstrip(" ]")
            d[g] = v
        return d
            

    def readMeta2(self, stop_expr):
        self.meta = {}
        stop = re.compile(stop_expr).match
        declare = re.compile(r'\s*m->declare\s*\("([^"]+)"\s*,\s*"([^"]*)"\);').match
        for line in self.lines:
            if stop(line):
                return
            m = declare(line)
            if m:
                key = m.group(1)
                value = m.group(2)
                self.meta[key] = value
                if key == "id":
                    self.toplevel = value
                elif key == "name":
                    self.name = value
                elif key == "groups":
                    self.groups.update(self.splitgroups(value));

    def readIncludes(self, stop_expr):
        stop = re.compile(stop_expr).match
        cp = []
        for line in self.lines:
            if stop(line):
                return cp
            if line.startswith('#include "'):
                cp.append(line)
        raise RuntimeError("EOF while looking for #include")

    def change_var_decl(self, lines):
        param_matcher = re.compile(r"FAUSTFLOAT\s+([a-zA-Z_0-9]+);\n$").match
        array_matcher = re.compile(r"(int|float|double)\s+([a-zA-Z_0-9]+)\s*\[\s*(\d+)\s*\]\s*;\n$").match
        static_matcher = re.compile(r"static (int|float|double)\s+([a-zA-Z_0-9]+)\s*\[\s*(\d+)\s*\]\s*;\n$").match
        out = []
        out_defines = []
        out_undefines = []
        for l in lines:
            m = param_matcher(l);
            if m:
                var = m.group(1)
                alias = self.ui.has(var,"alias")
                if alias:
                    #l = ('FAUSTFLOAT&\t%s = get_alias("%s");\n'
                    #     % (var, self.ui.get(var, "id")))
                    out.append(l)
                    out.append('FAUSTFLOAT\t*%s_;\n' % var)
                    out_defines.append('#define %s (*%s_)\n' % (var, var))
                    out_undefines.append('#undef %s\n' % var)
                    continue
            m = static_matcher(l);
            if m:
                self.staticlist.append((m.group(2), m.group(1), m.group(3)))
            if self.memory_threshold:
                m = array_matcher(l)
                if m:
                    sz = {"int": 4, "float": 4, "double": 8}[m.group(1)]
                    alen = int(m.group(3))
                    if alen * sz > self.memory_threshold:
                        l = "%s *%s;\n" % (m.group(1), m.group(2))
                        self.memlist.append((m.group(2), m.group(1), alen))
            if l.startswith(("int","float","double","FAUSTFLOAT")):
                l = "%(static)s" + l
            out.append(l)
        return out, out_defines, out_undefines

    def add_var_alloc(self):
        l = []
        for v, t, s in self.memlist:
            l.append("if (!%s) %s = new %s[%d];\n" % (v, v, t, s))
        return l

    def add_var_free(self):
        l = []
        for v, t, s in self.memlist:
            l.append("if (%s) { delete %s; %s = 0; }\n" % (v, v, v))
        return l

    def __init__(self, lines, modname, memory_threshold):
        self.lines = lines
        self.modname = modname
        self.memory_threshold = memory_threshold;
        self.toplevel = None
        self.topname = None
        self.name = None
        self.groups = {}
        self.memlist = []
        self.staticlist = []
        s = {}
        self.ui = UIDefs()
        #self.readMeta()  # (needed only for faust 9.4
        self.headvers = self.skip_until(r"\s*//\s*(Code generated with Faust.*)").group(1)
        s["includes"] = self.readIncludes(r"  private:")
        #self.skip_until(r"  private:")
        var_decl = self.copy(r"  public:")
        self.skip_until(r"^\s*static\s+void\s+metadata\s*\(\s*Meta\s*\*\s*m\s*\)\s*{")
        self.readMeta2(r"\s*}\s*\n$")
        self.numInputs = self.getIO("In")
        self.numOutputs = self.getIO("Out")
        self.skip_until(r"\s*static void classInit")
        s["var-init"] = self.copy(r"\s*}$")
        self.skip_until(r"\s*virtual void instanceInit")
        s["var-init"] += self.copy(r"\s*}$")
        self.skip_until(r"\s*virtual void buildUserInterface")
        s["ui"] = self.readUI(r"\s*}$")
        s["var-decl"], s["alias-defines"], s["alias-undefines"] = self.change_var_decl(var_decl)
        s["var-init"] = s["var-init"]
        s["var-alloc"] = self.add_var_alloc()
        s["var-free"] = self.add_var_free()
        self.skip_until(r"\s*virtual void compute")
        iodef = r"\s*(float|FAUSTFLOAT)\s*\*\s*(in|out)put(\d+)\s*=\s*\2put\[\3\];"
        s["compute"] = self.copy(iodef)
        line = self.skip_while(iodef)
        s["compute"] += self.copy("\s*}$", line)
        self.sections = s
        if self.topname is None:
            self.topname = self.modname
        # ignore any following definitions of static class members
        #self.checkfor(r".*\bexp\b", "compute")

    def checkfor(self, re_exp, sect):
        loop = re.compile(r"\s*for\s*\(int\s+i=0;\s*i<count;\s*i\+\+\)\s*{").match
        re_m = re.compile(re_exp).match
        in_loop = False
        for l in self.sections[sect]:
            if not in_loop:
                if loop(l):
                    in_loop = True
                continue
            if re_m(l):
                print self.modname, l

    def getNumInputs(self):
        return self.numInputs

    def getNumOutputs(self):
        return self.numOutputs

    def __getitem__(self, n):
        return self.sections[n]

    def formatted_groups(self, plugin_id):
        l = []
        for k, v in self.groups.items():
            if k == plugin_id:
                continue
            k = '"'+k+'"'
            l.append("\t%s, %s,\n" % (k, wrap_N_(v)))
        return "".join(l) + "\t0\n"
  
    def write(self, fp, sect, indent=0, filt=lambda l: False, dct=None):
        pre = "\t" * indent
        for l in self.sections[sect]:
            if filt(l):
                continue
            fp.write(pre)
            if dct:
                l = l % dct
            fp.write(l)

activate = """
static int activate(bool start, PluginDef* = 0)
{
    if (start) {
        if (!mem_allocated) {
            mem_alloc();
            clear_state_f();
        }
    } else if (!mem_allocated) {
        mem_free();
    }
    return 0;
}

"""

plugin = r"""
PluginDef plugin = {
    PLUGINDEF_VERSION,
    0,   // flags
    "%s",  // id
    %s,  // name
    %s,  // groups
    %s,  // mono_audio
    %s,  // stereo_audio
    init,  // set_samplerate
    %s,  // activate plugin
    register_params,
    %s,   // load_ui
    %s,  // clear_state
};
"""

plugin_standalone_header = """
#include "gx_faust_support.h"
#include "gx_plugin.h"

"""

plugin_standalone_footer = """
extern "C" __attribute__ ((visibility ("default"))) int
get_gx_plugins(int *count, PluginDef **pplugin)
{
    *count = 1;
    *pplugin = &plugin;
    return 0;
}
"""

loadui_glade = r"""
static const char *glade_def = "\
%s";

static int load_ui(const UiBuilder& b) {
    b.load_glade(glade_def);
    return 0;
}
"""

template_plugin = """\
// generated from file '%(filepath)s' by dsp2cc:
// %(headline)s
#if %(has_standalone_header)s


#include "gx_faust_support.h"
#include "gx_plugin.h"
#endif

%(includes)s\
%(start_extra_namespace)s\
namespace %(namespace)s {
%(var_decl)s\
#if %(has_activate)s
static bool mem_allocated = false;
#endif
static int	fSamplingFreq;

#if %(has_state)s
static void clear_state_f(PluginDef* = 0)
{
%(state_init)s\
}

#endif
static void init(unsigned int samplingFreq, PluginDef* = 0)
{
%(init_body)s\
#if %(has_state_no_activate)s
	clear_state_f();
#endif
}

#if %(has_activate)s
static void mem_alloc()
{
%(var_alloc)s\
	mem_allocated = true;
}

static void mem_free()
{
	mem_allocated = false;
%(var_free)s\
}


static int activate(bool start, PluginDef* = 0)
{
    if (start) {
        if (!mem_allocated) {
            mem_alloc();
            clear_state_f();
        }
    } else if (!mem_allocated) {
        mem_free();
    }
    return 0;
}

#endif
static void compute(int count%(compute_args)s, PluginDef *)
{
%(compute_body)s\
	}
}

static int register_params(const ParamReg& reg)
{
%(register_body)s\
	return 0;
}
#if %(has_cc_ui)s
int load_ui(const UiBuilder& b) {
%(cc_ui)s
}

#endif
#if %(has_glade_ui)s

static const char *glade_def = "\\
%(glade_ui)s";

static int load_ui(const UiBuilder& b) {
    b.load_glade(glade_def);
    return 0;
}
#endif
%(groups_define)s\

PluginDef plugindef = {
    PLUGINDEF_VERSION,
    0,   // flags
    "%(plugin_id)s",  // id
    %(plugin_name)s,  // name
    %(groups_p)s,  // groups
    %(mono_compute_p)s,  // mono_audio
    %(stereo_compute_p)s,  // stereo_audio
    init,  // set_samplerate
    %(activate_p)s,  // activate plugin
    register_params,
    %(load_ui_p)s,   // load_ui
    %(clear_state_p)s,  // clear_state
    0, // delete_instance
};

PluginDef *plugin() {
    return &plugindef;
}

} // end namespace %(namespace)s
%(end_extra_namespace)s\
"""

template_plugin_instance = """\
// generated from file '%(filepath)s' by dsp2cc:
// %(headline)s

#if %(has_separate_header)s
#define FAUSTFLOAT float
#else
#if %(has_standalone_header)s
#include "gx_faust_support.h"
#include "gx_plugin.h"
#endif
%(includes)s\
#endif

%(start_extra_namespace)s\
namespace %(namespace)s {

#if %(has_plugindef)s
class Dsp: public PluginDef {
#else
class Dsp {
#endif
private:
	int fSamplingFreq;
%(var_decl)s\
#if %(has_activate)s
	bool mem_allocated;
	void mem_alloc();
	void mem_free();
#endif
#ifnot %(has_plugindef)s

public:
#endif
#if %(has_state)s
	void clear_state_f();
#endif
#if %(has_activate)s
	int activate(bool start);
#endif
#if %(has_cc_ui)s
	int load_ui_f(const UiBuilder& b);
#endif
#if %(has_glade_ui)s
	int load_ui_f(const UiBuilder& b);
	static const char *glade_def;
#endif
	void init(unsigned int samplingFreq);
	void compute(int count%(compute_args)s);
	int register_par(const ParamReg& reg);
#if %(has_plugindef)s

#if %(has_state)s
	static void clear_state_f_static(PluginDef*);
#endif
#if %(has_activate)s
	static int activate_static(bool start, PluginDef*);
#endif
#if %(has_cc_ui)s
	static int load_ui_f_static(const UiBuilder& b);
#endif
#if %(has_glade_ui)s
	static int load_ui_f_static(const UiBuilder& b);
#endif
	static void init_static(unsigned int samplingFreq, PluginDef*);
	static void compute_static(int count%(compute_args)s, PluginDef*);
	static int register_params_static(const ParamReg& reg);
	static void del_instance(PluginDef *p);

public:
#endif
	Dsp();
	~Dsp();
};

#if %(has_separate_header)s
} // end namespace %(namespace)s
%(end_extra_namespace)s\
#impl
// generated from file '%(filepath)s' by dsp2cc:
// %(headline)s

#if %(has_standalone_header)s
#include "gx_faust_support.h"
#include "gx_plugin.h"
#include "%(header_name)s"
#endif
%(includes)s\

%(start_extra_namespace)s\
namespace %(namespace)s {
#endif

%(static_decl)s\
%(groups_define)s\

Dsp::Dsp()%(dsp_initlist)s {
#if %(has_plugindef)s
	version = PLUGINDEF_VERSION;
	flags = 0;
	id = "%(plugin_id)s";
	name = %(plugin_name)s;
	groups = %(groups_p)s;
	mono_audio = %(mono_compute_p)s;
	stereo_audio = %(stereo_compute_p)s;
	set_samplerate = init_static;
	activate_plugin = %(activate_p)s;
	register_params = register_params_static;
	load_ui = %(load_ui_p)s;
	clear_state = %(clear_state_p)s;
	delete_instance = del_instance;
#endif
}

Dsp::~Dsp() {
}

#if %(has_state)s
inline void Dsp::clear_state_f()
{
%(state_init)s\
}

#if %(has_plugindef)s
void Dsp::clear_state_f_static(PluginDef *p)
{
	static_cast<Dsp*>(p)->clear_state_f();
}

#endif
#endif
inline void Dsp::init(unsigned int samplingFreq)
{
%(init_body)s\
#if %(has_state_no_activate)s
	clear_state_f();
#endif
}

#if %(has_plugindef)s
void Dsp::init_static(unsigned int samplingFreq, PluginDef *p)
{
	static_cast<Dsp*>(p)->init(samplingFreq);
}

#endif
#if %(has_activate)s
void Dsp::mem_alloc()
{
%(var_alloc)s\
	mem_allocated = true;
}

void Dsp::mem_free()
{
	mem_allocated = false;
%(var_free)s\
}

int Dsp::activate(bool start)
{
	if (start) {
		if (!mem_allocated) {
			mem_alloc();
			clear_state_f();
		}
	} else if (!mem_allocated) {
		mem_free();
	}
	return 0;
}

#if %(has_plugindef)s
int Dsp::activate_static(bool start, PluginDef *p)
{
	return static_cast<Dsp*>(p)->activate(start);
}

#endif
#endif
inline void Dsp::compute(int count%(compute_args)s)
{
%(defines)s\
%(compute_body)s\
	}
%(undefines)s\
}

#if %(has_plugindef)s
void Dsp::compute_static(int count%(compute_args)s, PluginDef *p)
{
	static_cast<Dsp*>(p)->compute(count%(compute_call_args)s);
}

#endif
int Dsp::register_par(const ParamReg& reg)
{
%(register_body)s\
	return 0;
}

#if %(has_plugindef)s
int Dsp::register_params_static(const ParamReg& reg)
{
	return static_cast<Dsp*>(reg.plugin)->register_par(reg);
}

#endif
#if %(has_cc_ui)s
inline int Dsp::load_ui_f(const UiBuilder& b)
{
%(cc_ui)s
}

#if %(has_plugindef)s
int Dsp::load_ui_f_static(const UiBuilder& b)
{
	return static_cast<Dsp*>(b.plugin)->load_ui_f(b);
}

#endif
#endif
#if %(has_glade_ui)s
const char *Dsp::glade_def = "\\
%(glade_ui)s";

int Dsp::load_ui_f(const UiBuilder& b)
{
	b.load_glade(glade_def);
	return 0;
}

#if %(has_plugindef)s
int Dsp::load_ui_f_static(const UiBuilder& b)
{
	return static_cast<Dsp*>(b.plugin)->load_ui_f(b);
}

#endif
#endif
#if %(has_plugindef)s
PluginDef *plugin() {
	return new Dsp();
}

void Dsp::del_instance(PluginDef *p)
{
	delete static_cast<Dsp*>(p);
}

#endif
} // end namespace %(namespace)s
%(end_extra_namespace)s\
"""

class Output(object):

    def __init__(self, parser, fname, options):
        self.parser = parser
        self.fname = fname
        self.options = options
        self.has_activate = len(self.parser.memlist) > 0
        s = StringIO()
        self.parser.write(s, "var-init", 1, filt=self.parser.ui.var_filter(0))
        self.state_init = s.getvalue()


    def write_head(self, fp):
        "file header and anything that must not be in a namespace"
        fp.write("// generated from file '%s' by dsp2cc:\n" % self.fname)
        fp.write("// %s\n\n" % self.parser.headvers)
        if self.options.init_type == "plugin" and self.options.template_type in ("staticlib", "sharedlib"):
            fp.write(plugin_standalone_header)
        self.parser.write(fp, "includes")

    def write_load_ui(self, fp, wrap=True):
        load_ui = 0
        ui_name = self.fname.replace(".dsp", "_ui.glade")
        if os.path.exists(ui_name):
            xml = file(ui_name).read()
            if self.parser.ui.check_parameter(
                self.fname, ui_name,
                re.findall('<property name="var_id">([^<]*)</property>',xml)):
                raise SystemExit, 1
            s = xml.replace("\\",r"\\").replace("\n","\\n\\\n").replace('"',r'\"')
            if wrap:
                fp.write(loadui_glade % s)
            else:
                fp.write(s)
            load_ui = 2
        else:
            ui_name = self.fname.replace(".dsp", "_ui.cc")
            if os.path.exists(ui_name):
                ccdef = file(ui_name).read()
                if self.parser.ui.check_parameter(
                    self.fname, ui_name,
                    re.findall(r'\.create_[a-zA-Z0-9_]+\("([^"]*)',ccdef)):
                    raise SystemExit, 1
                fp.write("\n")
                fp.write(ccdef)
                load_ui = 1
        return load_ui

    def write_body_plugin(self, fp):
        fp.write("static int register_params(const ParamReg& reg)\n{\n")
        self.parser.ui.write(fp, prefix="reg.")
        fp.write("\treturn 0;\n}\n")
        if self.write_load_ui(fp):
            load_ui = "load_ui"
        else:
            load_ui = "0"
        plugin_id = self.parser.toplevel or self.parser.topname
        if self.parser.groups:
            fp.write("\nstatic const char* parm_groups[] = {\n%s\t};\n" % self.parser.formatted_groups(plugin_id))
        if self.parser.name:
            plugin_name = wrap_N_(self.parser.name)
        else:
            plugin_name = '"?%s"' % self.parser.modname
        fp.write(plugin % (
            plugin_id,    # id
            plugin_name,  # name
            "parm_groups" if self.parser.groups else "0",  # groups
            "compute" if self.parser.getNumOutputs() == 1 else "0",  # mono_audio
            "compute" if self.parser.getNumOutputs() == 2 else "0",  # stereo_audio
            "activate" if self.has_activate else "0",  # activate plugin
            load_ui, # load_ui
            "clear_state_f" if self.state_init else "0", # clear_state
            ))

    def parser_sect(self, sect, indent=0, filt=lambda l: False):
        s = StringIO()
        self.parser.write(s, sect, indent, filt)
        return s.getvalue()

    def parser_ui(self, prefix=""):
        s = StringIO()
        self.parser.ui.write(s, prefix)
        return s.getvalue()

    def gen_load_ui(self):
        s = StringIO()
        ret = self.write_load_ui(s, False)
        if ret == 0:
            return False, "", False, ""
        if ret == 1:
            return True, s.getvalue(), False, ""
        if ret == 2:
            return False, "", True, s.getvalue()
        assert False, str(ret)

    def dsp_initlist(self, has_plugindef):
        if has_plugindef:
            l = ["PluginDef()"]
        else:
            l = []
        if self.has_activate:
            for v, t, s in self.parser.memlist:
                l.append("%s(0)" % v)
            l.append("mem_allocated(false)")
        if not l:
            return ""
        return "\n\t: " + ",\n\t  ".join(l)

    def write_plugin(self, fp, fp_head, h_name):
        if self.options.init_type == "plugin":
            dd = dict(static = "static ", cls = "")
            ds = ""
            indent = 0
        else:
            dd = dict(static = "", cls = "Dsp::")
            ds = "_static"
            indent = 1
        plugin_standalone = self.options.template_type in ("staticlib", "sharedlib")
        has_plugindef = self.options.init_type in ("plugin", "plugin-instance")
        in_namespace = self.options.in_namespace
        compute_args = ("".join([", float *input%d" % i for i in range(self.parser.getNumInputs())])
                        + "".join([", float *output%d" % i for i in range(self.parser.getNumOutputs())]))
        compute_call_args = ("".join([", input%d" % i for i in range(self.parser.getNumInputs())])
                             + "".join([", output%d" % i for i in range(self.parser.getNumOutputs())]))
        has_cc_ui, cc_ui, has_glade_ui, glade_ui = self.gen_load_ui()
        plugin_id = self.parser.toplevel or self.parser.topname
        if self.parser.groups:
            groups_define = "\nstatic const char* parm_groups[] = {\n%s\t};\n" % self.parser.formatted_groups(plugin_id)
        else:
            groups_define = ""
        if self.parser.name:
            plugin_name = wrap_N_(self.parser.name)
        else:
            plugin_name = '"?%s"' % self.parser.modname
        static_decl = "".join(["%s Dsp::%s[%s];\n" % (tp, v, n) for v, tp, n in self.parser.staticlist])
        template = template_plugin if self.options.init_type == "plugin" else template_plugin_instance
        s = StringIO()
        s.write(template % dict(
            filepath = self.fname,
            headline = self.parser.headvers,
            start_extra_namespace = "namespace %s {\n" % in_namespace if in_namespace else "",
            end_extra_namespace = "} // end namespace %s\n" % in_namespace if in_namespace else "",
            has_standalone_header = plugin_standalone,
            includes = self.parser_sect("includes"),
            namespace = self.parser.modname,
            var_decl = self.parser_sect("var-decl", indent=indent) % dd,
            static_decl = static_decl,
            has_activate = self.has_activate,
            state_init = self.state_init,
            has_state = self.state_init != "",
            has_state_no_activate = self.state_init != "" and not self.has_activate,
            init_body = self.parser_sect("var-init", 1, filt=self.parser.ui.var_filter(1)),
            dsp_initlist = self.dsp_initlist(has_plugindef),
            var_alloc = self.parser_sect("var-alloc", 1),
            var_free = self.parser_sect("var-free", 1),
            compute_args = compute_args,
            compute_call_args = compute_call_args,
            defines = self.parser_sect("alias-defines", 0),
            compute_body = self.parser_sect("compute", 1),
            undefines = self.parser_sect("alias-undefines", 0),
            register_body = self.parser_ui("reg."),
            has_cc_ui = has_cc_ui,
            has_glade_ui = has_glade_ui,
            cc_ui = cc_ui,
            glade_ui = glade_ui,
            groups_define =  groups_define,
            plugin_id = plugin_id,
            plugin_name = plugin_name,
            groups_p = "parm_groups" if groups_define else "0",
            mono_compute_p = "compute"+ds if self.parser.getNumOutputs() == 1 else "0",
            stereo_compute_p = "compute"+ds if self.parser.getNumOutputs() == 2 else "0",
            activate_p = "activate"+ds if self.has_activate else "0",
            load_ui_p = "load_ui_f"+ds if has_cc_ui or has_glade_ui else "0",
            clear_state_p = "clear_state_f"+ds if self.state_init else "0",
            has_plugindef = has_plugindef,
            has_separate_header = fp_head is not None,
            header_name = h_name,
            ))
        s.seek(0)
        depth = 0  # conditional nesting depth
        truelevel = 0  # last depth where all nested conditions where true
        impl_seen = False
        for lno, l in enumerate(s):
            if l.startswith("#if"):
                if l.endswith("True\n"):
                    cond = True
                elif l.endswith("False\n"):
                    cond = False
                else:
                    assert False, "%d: %s" % (lno+1, l)
                if l.startswith("#ifnot ") != cond:
                    if truelevel == depth:
                        truelevel += 1
                depth += 1
                continue
            elif l == "#else\n":
                if depth == 0:
                    raise SystemExit("template line %d: wrong #else" % (lno+1))
                if truelevel == depth:
                    truelevel -= 1
                elif truelevel == depth-1:
                    truelevel = depth
                continue
            elif l == "#endif\n":
                if depth == 0:
                    raise SystemExit("template line %d: unbalanced #if / #endif" % (lno+1))
                if truelevel == depth:
                    truelevel -= 1
                depth -= 1
                continue
            if l == "#impl\n":
                impl_seen = True
                continue
            if truelevel == depth:
                if fp_head and not impl_seen:
                    fp_head.write(l)
                else:
                    fp.write(l)
        if depth:
            raise SystemExit("template EOF: unbalanced #if / #endif")

    def write(self, fp, fp_head, h_name):
        if self.options.init_type in ("plugin", "plugin-instance", "no-init-instance"):
            self.write_plugin(fp, fp_head, h_name)
            return
        #FIXME: cleanup when write_plugin can generate code for all types of plugins
        self.write_head(fp)
        if self.options.in_namespace:
            fp.write("namespace %s {\n" % self.options.in_namespace)
        fp.write("namespace %s {\n" % self.parser.modname)
        self.parser.write(fp, "var-decl", dct=dict(static="static "))
        if self.has_activate:
            fp.write("static bool mem_allocated = false;\n")
        fp.write("static int\tfSamplingFreq;\n\n")
        if self.state_init:
            fp.write("static void clear_state_f(PluginDef* = 0)\n{\n")
            fp.write(self.state_init);
            fp.write("}\n\n")
        if self.state_init and not self.has_activate:
            fp.write("static void init(unsigned int samplingFreq, PluginDef* = 0)\n{\n")
            self.parser.write(fp, "var-init", 1, filt=self.parser.ui.var_filter(1))
            fp.write('\tclear_state_f();\n')
            fp.write("}\n\n")
        else:
            fp.write("static void init(unsigned int samplingFreq, PluginDef* = 0)\n{\n")
            self.parser.write(fp, "var-init", 1, filt=self.parser.ui.var_filter(1))
            fp.write("}\n\n")
        if self.has_activate:
            fp.write("static void mem_alloc()\n{\n")
            self.parser.write(fp, "var-alloc", 1)
            fp.write("\tmem_allocated = true;\n}\n\n")
            fp.write("static void mem_free()\n{\n")
            fp.write("\tmem_allocated = false;\n")
            self.parser.write(fp, "var-free", 1)
            fp.write("}\n\n")
            fp.write(activate)
        fp.write("%s(int count%s%s" % (
            "static void compute" if self.options.init_type == "plugin" else "void compute",
            "".join([", float *input%d" % i for i in range(self.parser.getNumInputs())]),
            "".join([", float *output%d" % i for i in range(self.parser.getNumOutputs())])))
        if self.options.init_type == "plugin":
            fp.write(", PluginDef *)\n{\n")
        else:
            fp.write(")\n{\n")
        self.parser.write(fp, "alias-defines", 0)
        self.parser.write(fp, "compute", 1)
        self.parser.write(fp, "alias-undefines", 0)
        fp.write("\t}\n}\n\n")
        if self.options.init_type == "ctor":
            fp.write("static struct RegisterParams { RegisterParams(); } RegisterParams;\n")
            fp.write("RegisterParams::RegisterParams()\n{\n")
            self.parser.ui.write(fp)
            fp.write('\tregisterInit("%s", init);\n' % self.parser.topname)
            fp.write("}\n")
        elif self.options.init_type == "plugin":
            self.write_body_plugin(fp)
        else:
            fp.write("static int register_params(const ParamReg& reg)\n{\n")
            self.parser.ui.write(fp, prefix="reg.")
            fp.write("\treturn 0;\n}\n")
        if self.options.init_type == "plugin" and self.options.template_type == "sharedlib":
            fp.write(plugin_standalone_footer)
        fp.write("\n} // end namespace %s\n" % self.parser.modname)
        if self.options.in_namespace:
            fp.write("} // end namespace %s\n" % self.options.in_namespace)


def main():
    op = OptionParser(usage="usage: %prog [options] <faust-dsp-file>")
    op.add_option("-o", "--output", dest="oname",
                  help="write c++ code to FILE", metavar="FILE")
    op.add_option("-H", "--header-output", dest="hname",
                  help="write separate c++ header to FILE", metavar="FILE")
    op.add_option("-d", "--double", dest="faust", action="store_true", default=False,
                  help="additional faust options, build with double precision") 
    op.add_option("-f", "--float", dest="faustf", action="store_true", default=False,
                  help="additional faust options, build with single precision")
    op.add_option("-s", "--memory-threshold", dest="memory_threshold",
                  default=0, type="int",
                  help="change static memory allocations above threshold to dynamic ones")
    init_opts = ["ctor", "no-init", "no-init-instance", "plugin", "plugin-instance", "plugin-standalone"]
    op.add_option("-i", "--init-type", dest="init_type", action="store", default="ctor",
                  help="type of init code generation: %s" % ", ".join(init_opts))
    template_opts = ["embed", "staticlib", "sharedlib"]
    op.add_option("-t", "--template-type", dest="template_type", action="store",
                  default="embed",
                  help="template for code generation: %s" % ", ".join(template_opts))
    op.add_option("-N", "--in-namespace", action="store", metavar="NAMESPACE",
                  help="put definitions inside an extra namespace")
    options, args = op.parse_args()
    if options.init_type not in init_opts:
        op.error("unknown init-type")
    if options.template_type not in template_opts:
        op.error("unknown template-type")
    if len(args) != 1:
        op.error("exactly one input filename expected\n")
    fname = args[0]
    if not os.path.exists(fname):
        print "error: can't open '%s'" % fname
        raise SystemExit, 1
    if options.faust:
        precision = ' -double'
    elif options.faustf:
        precision = ' -single'
    else:
        precision = ''
    faust = Popen("faust %s%s" % (fname, precision), shell=True, stdout=PIPE)
    try:
        parser = Parser(faust.stdout,
                        os.path.splitext(os.path.basename(fname))[0],
                        options.memory_threshold)
    except ValueError, e:
        if faust.wait() == 0:
            print e
        raise SystemExit, 1
    if options.oname:
        outp = file(options.oname, "w")
    else:
        outp = sys.stdout
    if options.hname:
        h_outp = file(options.hname, "w")
        h_name = os.path.basename(options.hname)
    else:
        h_outp = h_name = None
    Output(parser, fname, options).write(outp, h_outp, h_name)
    outp.close()
    if h_outp:
        h_outp.close()

if __name__ == "__main__":
    main()
