/************************************************************************
 ************************************************************************
    FAUST compiler
	Copyright (C) 2003-2004 GRAME, Centre National de Creation Musicale
    ---------------------------------------------------------------------
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 ************************************************************************
 ************************************************************************/



/*****************************************************************************
******************************************************************************
							FAUST SIGNAL COMPILER
						Y. Orlarey, (c) Grame 2002
------------------------------------------------------------------------------
Compile a list of FAUST signals into a C++ class .

 History :
 ---------
 	2002-02-08 : First version

******************************************************************************
*****************************************************************************/



#include "timing.hh"
#include "compile.hh"
#include "floats.hh"
#include "sigtype.hh"

#include <stdio.h>
//#include <iostream>

#include "sigprint.hh"
#include "ppsig.hh"

#include "sigtyperules.hh"
#include "simplify.hh"
#include "privatise.hh"
//#include "factorize.hh"

//#include "grouper.hh"
//#include "sigvisitor.hh"




/*****************************************************************************
******************************************************************************

							    	API

******************************************************************************
*****************************************************************************/

extern int 		gDetailsSwitch;
extern string 	gMasterName;




/*****************************************************************************
******************************************************************************

						  GENERAL COMPILER METHODS

******************************************************************************
*****************************************************************************/







/*****************************************************************************
							   constructor
*****************************************************************************/

Compiler::Compiler(const string& name, const string& super, int numInputs, int numOutputs, bool vec)
		: fClass(new Klass(name, super, numInputs, numOutputs, vec)),
		  fNeedToDeleteClass(true), 
		  fUIRoot(uiFolder(cons(tree(0), tree(subst("$0", gMasterName))))),
		  fDescription(0)
{}

Compiler::Compiler(Klass* k)
		: fClass(k),
		  fNeedToDeleteClass(false), 
		  fUIRoot(uiFolder(cons(tree(0), tree(subst("$0", gMasterName))))),
		  fDescription(0)
{}


Compiler::~Compiler()
{ 
	if (fNeedToDeleteClass) delete fClass;
}



/*****************************************************************************
							user interface elements
*****************************************************************************/

/**
 * Add a widget with a certain path to the user interface tree
 */
void Compiler::addUIWidget(Tree path, Tree widget)
{
	fUIRoot = putSubFolder(fUIRoot, path, widget);
}


/**
 * Remove fake root folder if not needed (that is if the UI
 * is completely enclosed in one folder
 */
Tree Compiler::prepareUserInterfaceTree(Tree t)
{
	Tree root, elems;
	if (isUiFolder(t, root, elems) && isList(elems) && isNil(tl(elems)) ) {
		Tree folder = right(hd(elems));
		return (isUiFolder(folder)) ? folder : t;
	}
	return t;
}

//================================= some string processing utilities =================================

/**
 * Removes enclosing whitespaces : '  toto  ' -> 'toto'
 */
static string wdel(const string& s)
{
    size_t i = 0;
    size_t j = s.size();
    while (i<j && s[i]==' ') i++;
    while (j>i && s[j-1] == ' ') j--;
    return s.substr(i,j-i);
}


//================================= BUILD USER INTERFACE METHOD =================================

/**
 * Generate buildUserInterface C++ lines of code corresponding 
 * to user interface element t
 */
void Compiler::generateUserInterfaceTree(Tree t)
{
	Tree 	label, elements, varname, sig;

	if (isUiFolder(t, label, elements)) {
		const int		orient = tree2int(left(label));
		const char * 	str = tree2str(right(label));
        const char * 	model;

        // extract metadata from group label str resulting in a simplifiedLabel
		// and metadata declarations for fictive zone at address 0
        string  simplifiedLabel;
        map<string, set<string> >   metadata;
        extractMetadata(str, simplifiedLabel, metadata);

        // add metadata if any
        for (map<string, set<string> >::iterator i = metadata.begin(); i != metadata.end(); i++) {
            const string& key = i->first;
            const set<string>& values = i->second;
            for (set<string>::const_iterator j = values.begin(); j != values.end(); j++) {
                fClass->addUICode(subst("interface->declare($0, \"$1\", \"$2\");", "0", wdel(key) ,wdel(*j)));
            }
        }
        //-----------------


		switch (orient) {
			case 0 : model = "interface->openVerticalBox(\"$0\");"; break;
			case 1 : model = "interface->openHorizontalBox(\"$0\");"; break;
			case 2 : model = "interface->openTabBox(\"$0\");"; break;
			default :
					fprintf(stderr, "error in user interface generation 1\n");
				exit(1);
		}
        fClass->addUICode(subst(model, simplifiedLabel));
		generateUserInterfaceElements(elements);
		fClass->addUICode("interface->closeBox();");

	} else if (isUiWidget(t, label, varname, sig)) {

		generateWidgetCode(label, varname, sig);

	} else {

		fprintf(stderr, "error in user interface generation 2\n");
		exit(1);

	}
}

/**
 * Iterate generateUserInterfaceTree on a list of user interface elements
 */
void Compiler::generateUserInterfaceElements(Tree elements)
{
	while (!isNil(elements)) {
		generateUserInterfaceTree(right(hd(elements)));
		elements = tl(elements);
	}
}

/**
 * Generate buildUserInterface C++ lines of code corresponding 
 * to user interface widget t
 */
void Compiler::generateWidgetCode(Tree fulllabel, Tree varname, Tree sig)
{
	Tree path, c, x, y, z;
    string label;
    map<string, set<string> >   metadata;

    extractMetadata(tree2str(fulllabel), label, metadata);

    // add metadata if any
    for (map<string, set<string> >::iterator i = metadata.begin(); i != metadata.end(); i++) {
        const string& key = i->first;
        const set<string>& values = i->second;
        for (set<string>::const_iterator j = values.begin(); j != values.end(); j++) {
            fClass->addUICode(subst("interface->declare(&$0, \"$1\", \"$2\");", tree2str(varname), wdel(key) ,wdel(*j)));
        }
    }

	if ( isSigButton(sig, path) ) 					{
        fClass->incUIActiveCount();
		fClass->addUICode(subst("interface->addButton(\"$0\", &$1);", label, tree2str(varname)));

	} else if ( isSigCheckbox(sig, path) ) 			{
        fClass->incUIActiveCount();
		fClass->addUICode(subst("interface->addCheckButton(\"$0\", &$1);", label, tree2str(varname)));

	} else if ( isSigVSlider(sig, path,c,x,y,z) )	{
        fClass->incUIActiveCount();
		fClass->addUICode(subst("interface->addVerticalSlider(\"$0\", &$1, $2, $3, $4, $5);",
				label,
				tree2str(varname),
				T(tree2float(c)),
				T(tree2float(x)),
				T(tree2float(y)),
				T(tree2float(z))));

	} else if ( isSigHSlider(sig, path,c,x,y,z) )	{
        fClass->incUIActiveCount();
		fClass->addUICode(subst("interface->addHorizontalSlider(\"$0\", &$1, $2, $3, $4, $5);",
				label,
				tree2str(varname),
				T(tree2float(c)),
				T(tree2float(x)),
				T(tree2float(y)),
				T(tree2float(z))));

	} else if ( isSigNumEntry(sig, path,c,x,y,z) )	{
        fClass->incUIActiveCount();
		fClass->addUICode(subst("interface->addNumEntry(\"$0\", &$1, $2, $3, $4, $5);",
				label,
				tree2str(varname),
				T(tree2float(c)),
				T(tree2float(x)),
				T(tree2float(y)),
				T(tree2float(z))));

	} else if ( isSigVBargraph(sig, path,x,y,z) )	{
        fClass->incUIPassiveCount();
		fClass->addUICode(subst("interface->addVerticalBargraph(\"$0\", &$1, $2, $3);",
				label,
				tree2str(varname),
				T(tree2float(x)),
				T(tree2float(y))));

	} else if ( isSigHBargraph(sig, path,x,y,z) )	{
        fClass->incUIPassiveCount();
		fClass->addUICode(subst("interface->addHorizontalBargraph(\"$0\", &$1, $2, $3);",
                label,
				tree2str(varname),
				T(tree2float(x)),
				T(tree2float(y))));

	} else {
		fprintf(stderr, "Error in generating widget code\n");
		exit(1);
	}
}

//==================================== USER INTERFACE MACROS ==================================

/**
 * Generate user interface macros corresponding 
 * to user interface element t
 */
void Compiler::generateMacroInterfaceTree(const string& pathname, Tree t)
{
	Tree 	label, elements, varname, sig;

	if (isUiFolder(t, label, elements)) {
		string pathname2 = pathname;
		//string str = unquote(tree2str(right(label)));
		string str = tree2str(right(label));
		if (str.length()>0) pathname2 += str + "/";
		generateMacroInterfaceElements(pathname2, elements);

	} else if (isUiWidget(t, label, varname, sig)) {

		generateWidgetMacro(pathname, label, varname, sig);

	} else {

		fprintf(stderr, "error in user interface macro generation 2\n");
		exit(1);

	}
}


/**
 * Iterate generateMacroInterfaceTree on a list of user interface elements
 */
void Compiler::generateMacroInterfaceElements(const string& pathname, Tree elements)
{
	while (!isNil(elements)) {
		generateMacroInterfaceTree(pathname, right(hd(elements)));
		elements = tl(elements);
	}
}


/**
 * Generate user interface macros corresponding 
 * to a user interface widget
 */
void Compiler::generateWidgetMacro(const string& pathname, Tree fulllabel, Tree varname, Tree sig)
{
	Tree path, c, x, y, z;
    string label;
    map<string, set<string> >   metadata;

    extractMetadata(tree2str(fulllabel), label, metadata);

    //string pathlabel = pathname+unquote(label);
	string pathlabel = pathname+label;


	if ( isSigButton(sig, path) ) 					{
		fClass->addUIMacro(subst("FAUST_ADDBUTTON(\"$0\", $1);", pathlabel, tree2str(varname)));

	} else if ( isSigCheckbox(sig, path) ) 			{
		fClass->addUIMacro(subst("FAUST_ADDCHECKBOX(\"$0\", $1);", pathlabel, tree2str(varname)));

	} else if ( isSigVSlider(sig, path,c,x,y,z) )	{
		fClass->addUIMacro(subst("FAUST_ADDVERTICALSLIDER(\"$0\", $1, $2, $3, $4, $5);",
				pathlabel,
				tree2str(varname),
				T(tree2float(c)),
				T(tree2float(x)),
				T(tree2float(y)),
				T(tree2float(z))));

	} else if ( isSigHSlider(sig, path,c,x,y,z) )	{
		fClass->addUIMacro(subst("FAUST_ADDHORIZONTALSLIDER(\"$0\", $1, $2, $3, $4, $5);",
				pathlabel,
				tree2str(varname),
				T(tree2float(c)),
				T(tree2float(x)),
				T(tree2float(y)),
				T(tree2float(z))));

	} else if ( isSigNumEntry(sig, path,c,x,y,z) )	{
		fClass->addUIMacro(subst("FAUST_ADDNUMENTRY(\"$0\", $1, $2, $3, $4, $5);",
				pathlabel,
				tree2str(varname),
				T(tree2float(c)),
				T(tree2float(x)),
				T(tree2float(y)),
				T(tree2float(z))));

	} else if ( isSigVBargraph(sig, path,x,y,z) )	{
		fClass->addUIMacro(subst("FAUST_ADDVERTICALBARGRAPH(\"$0\", $1, $2, $3);",
				pathlabel,
				tree2str(varname),
				T(tree2float(x)),
				T(tree2float(y))));

	} else if ( isSigHBargraph(sig, path,x,y,z) )	{
		fClass->addUIMacro(subst("FAUST_ADDHORIZONTALBARGRAPH(\"$0\", $1, $2, $3);",
				pathlabel,
				tree2str(varname),
				T(tree2float(x)),
				T(tree2float(y))));

	} else {
		fprintf(stderr, "Error in generating widget code\n");
		exit(1);
	}
}
