/************************************************************************
 ************************************************************************
    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.
 ************************************************************************
 ************************************************************************/

 /**
 * @file drawschema.cpp
 * Implement block-diagram schema generation in svg or postscript format.
 * The result is a folder containing one or more schema files in svg or
 * ps format. Complex block-diagrams are automatically splitted.
 */


#include <stdio.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <string.h>

#include <ostream>
#include <sstream>
#include <set>
#include <utility>
#include <map>
#include <stack>
#include <string>

#include "boxes.hh"
#include "ppbox.hh"
#include "prim2.hh"

#include <vector>
#include "devLib.h"
#include "ppbox.hh"
#include "xtended.hh"
#include "occurrences.hh"
#include "boxcomplexity.h"

#include "schema.h"
#include "drawschema.hh"
#include "compatibility.hh"
#include "names.hh"
#include "description.hh"
#include "property.hh"



#if 0
#define linkcolor "#b3d1dc"
#define normalcolor "#ffeaa2"
#define uicolor "#F1CFA1"
#define slotcolor "#ffffd7"
#define numcolor "#ffffff"
#endif

#if 0
#define linkcolor "#F57900" 
#define normalcolor "#4B71A1"
#define uicolor "#47945E"
#define slotcolor "#EDD400"
#define numcolor "#4B71A1"
#endif

#if 0
#define linkcolor "#47945E" 
#define normalcolor "#4B71A1"
#define uicolor "#f44800"
#define slotcolor "#EDD400"
#define numcolor "#f44800"
#endif

#if 0
#define linkcolor "#47945E" 
#define normalcolor "#4B71A1"
#define uicolor "#816647"
#define slotcolor "#EDD400"
#define numcolor "#f44800"
#endif

#if 0
#define linkcolor "#003366" 
#define normalcolor "#4B71A1"
#define uicolor "#816647"
#define slotcolor "#EDD400"
#define numcolor "#f44800"
#endif

#if 0
#define linkcolor "#003366" 
#define normalcolor "#4B71A1"
#define uicolor "#477881"
#define slotcolor "#816647"
#define numcolor "#f44800"
#endif


#if 1
#define linkcolor "#003366" 
#define normalcolor "#4B71A1"
#define uicolor "#477881"
#define slotcolor "#47945E"
#define numcolor "#f44800"
#define invcolor "#ffffff"
#endif

using namespace std;

// external parameters
extern int gFoldThreshold;				// max diagram complexity before folding


// internal state during drawing
static Occurrences* 	gOccurrences;
static bool				sFoldingFlag;		// true with complex block-diagrams
static stack<Tree>		gPendingExp;		// Expressions that need to be drawn
static set<Tree>		gDrawnExp;			// Expressions drawn or scheduled so far
static const char* 		gDevSuffix;			// .svg or .ps used to choose output device
static char 			gCurrentDir[512];	// room to save current directory name
static string			gSchemaFileName;	// name of schema file beeing generated
static map<Tree,string>	gBackLink;			// link to enclosing file for sub schema

// prototypes of internal functions
static void 	writeSchemaFile(Tree bd);
static schema* 	generateDiagramSchema (Tree bd);
static schema* 	generateInsideSchema(Tree t);
static void 	scheduleDrawing(Tree t);
static bool 	pendingDrawing(Tree& t);
static schema* 	generateAbstractionSchema(schema* x, Tree t);
static schema* 	generateOutputSlotSchema(Tree a);
static schema* 	generateInputSlotSchema(Tree a);
static schema* 	generateBargraphSchema(Tree t);
static schema* 	generateUserInterfaceSchema(Tree t);
static char* 	legalFileName(Tree t, int n, char* dst);
static int 		cholddir ();
static int 		mkchdir(const char* dirname);
static schema*  addSchemaInputs(int ins, schema* x);
static schema*  addSchemaOutputs(int outs, schema* x);




/**
 *The entry point to generate from a block diagram as a set of
 *svg files stored in the directory "<projname>-svg/" or
 *"<projname>-ps/" depending of <dev>.
 */
void drawSchema(Tree bd, const char* projname, const char* dev)
{
	gDevSuffix 		= dev;
	sFoldingFlag 	= boxComplexity(bd) > gFoldThreshold;

	mkchdir(projname); 			// create a directory to store files

	scheduleDrawing(bd);		// schedule the initial drawing

	Tree t; while (pendingDrawing(t)) {
		writeSchemaFile(t);		// generate all the pending drawing
	}

	cholddir();					// return to current directory
}


/************************************************************************
 ************************************************************************
							IMPLEMENTATION
 ************************************************************************
 ************************************************************************/


//------------------- to schedule and retreive drawing ------------------

/**
 * Schedule a makeBlockSchema diagram to be drawn.
 */
static void scheduleDrawing(Tree t)
{
	if (gDrawnExp.find(t) == gDrawnExp.end()) {
		gDrawnExp.insert(t);
		gBackLink.insert(make_pair(t,gSchemaFileName));	// remember the enclosing filename
		gPendingExp.push(t);
	}
}

/**
 * Retrieve next block diagram that must be drawn
 */
static bool pendingDrawing(Tree& t)
{
	if (gPendingExp.empty()) return false;
	t = gPendingExp.top();
	gPendingExp.pop();
	return true;
}



//------------------------ dealing with files -------------------------

/**
 * Write a top level diagram. A top level diagram
 * is decorated with its definition name property
 * and is drawn in an individual file
 */
static void writeSchemaFile(Tree bd)
{
	Tree			id;
	schema* 		ts;
    int             ins, outs;

	char 			temp[1024];

	gOccurrences = new Occurrences(bd);
    getBoxType (bd, &ins, &outs);

	bool hasname = getDefNameProperty(bd, id); 

	//assert(hasname);
	if (!hasname) {
		// create an arbitrary name 
		id = tree(Node(unique("diagram_")));
	}

	// generate legal file name for the schema
	stringstream s1; s1 << legalFileName(bd, 1024, temp) << "." << gDevSuffix;
	gSchemaFileName = s1.str();

	// generate the label of the schema
	stringstream s2; s2 << tree2str(id);
	string link = gBackLink[bd];
    ts = makeTopSchema(addSchemaOutputs(outs, addSchemaInputs(ins, generateInsideSchema(bd))), 20, s2.str(), link);
	// draw to the device defined by gDevSuffix
	if (strcmp(gDevSuffix, "svg") == 0) {
		SVGDev dev(s1.str().c_str(), ts->width(), ts->height());
		ts->place(0,0, kLeftRight);
		ts->draw(dev);
        { collector c; ts->collectTraits(c); c.draw(dev); }
	} else {
		PSDev dev(s1.str().c_str(), ts->width(), ts->height());
		ts->place(0,0, kLeftRight);
		ts->draw(dev);
        {
            collector c;
            ts->collectTraits(c);
            c.draw(dev);
        }
	}
}



/**
 * Create a new directory in the current one to store the diagrams.
 * The current directory is saved to be later restaured.
 */
static int mkchdir(const char* dirname)
{
	//cerr << "mkchdir of " << dirname << endl;
	if (getcwd(gCurrentDir, 512) != 0) {
		int status = mkdir(dirname, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
		if (status == 0 || errno == EEXIST) {
			if (chdir(dirname) == 0) {
				return 0;
			}
		}
	}
	perror("mkchdir");
	exit(errno);
	//return errno;
}


/**
 *Switch back to the previously stored current directory
 */
static int cholddir ()
{
	if (chdir(gCurrentDir) == 0) {
		return 0;
	} else {
		perror("cholddir");
		exit(errno);
	}
}


/**
 * Transform the definition name property of tree <t> into a
 * legal file name.  The resulting file name is stored in
 * <dst> a table of at least <n> chars. Returns the <dst> pointer
 * for convenience.
 */
static char* legalFileName(Tree t, int n, char* dst)
{
	Tree	id;
	int 	i=0;
	if (getDefNameProperty(t, id)) {
		const char* 	src = tree2str(id);
		for (i=0; isalnum(src[i]) && i<16; i++) {
			dst[i] = src[i];
		}
	}
	dst[i] = 0;
	if (strcmp(dst, "process") != 0) { 
		// if it is not process add the hex address to make the name unique
		snprintf(&dst[i], n-i, "-%p", t);
	}
	return dst;
}



//------------------------ generating the schema -------------------------


/**
 * isInverter(t) returns true if t == '*(-1)'. This test is used
 * to simplify diagram by using a special symbol for inverters.
 */
Tree gInverter[6];

static bool isInverter(Tree t)
{
    // init gInverted table. For some reason doesn't work if done outside
    if (gInverter[0] == 0) {
        gInverter[0] = boxSeq(boxPar(boxWire(), boxInt(-1)),boxPrim2(sigMul));
        gInverter[1] = boxSeq(boxPar(boxInt(-1), boxWire()),boxPrim2(sigMul));
        gInverter[2] = boxSeq(boxPar(boxWire(), boxReal(-1.0)),boxPrim2(sigMul));
        gInverter[3] = boxSeq(boxPar(boxReal(-1.0), boxWire()),boxPrim2(sigMul));
        gInverter[4] = boxSeq(boxPar(boxInt(0), boxWire()),boxPrim2(sigSub));
        gInverter[5] = boxSeq(boxPar(boxReal(0.0), boxWire()),boxPrim2(sigSub));
    };

    //cerr << "isInverter " << t << '$' << boxpp(t) << endl;
    for (int i=0; i<6; i++) {
        if (t == gInverter[i]) return true;
    }
    return false;
}


/**
 * Compute the Pure Routing property, that is expressions
 * only made of cut, wires and slots. No labels will be
 * dispayed for pure routing expressions.
 */
property<bool> gPureRoutingProperty;

static bool isPureRouting(Tree t)
{
    bool    r;
    int     ID;
    Tree    x,y;

    if (gPureRoutingProperty.get(t,r)) {
        return r;
    } else if (    isBoxCut(t)
                || isBoxWire(t)
                || isInverter(t)
                || isBoxSlot(t, &ID)
                || (isBoxPar(t,x,y) && isPureRouting(x) && isPureRouting(y))
                || (isBoxSeq(t,x,y) && isPureRouting(x) && isPureRouting(y))
                || (isBoxSplit(t,x,y) && isPureRouting(x) && isPureRouting(y))
                || (isBoxMerge(t,x,y) && isPureRouting(x) && isPureRouting(y))
              ) {
        gPureRoutingProperty.set(t,true);
        return true;
    } else {
        gPureRoutingProperty.set(t,false);
        return false;
    }
}


/**
 * Generate an appropriate schema according to
 * the type of block diagram. When folding is requiered,
 * instead of going down block-diagrams with a name,
 * schedule them for an individual file.
 */
static schema* generateDiagramSchema(Tree t)
{
	Tree	id;
	int		ins, outs;

	//cerr << t << " generateDiagramSchema " << boxpp(t)<< endl;

	if (getDefNameProperty(t, id)) {
		stringstream 	s; s << tree2str(id);
		//cerr << t << "\tNAMED : " << s.str() << endl;
	}

	if ( sFoldingFlag && /*(gOccurrences->getCount(t) > 0) &&*/
			(boxComplexity(t) > 2) && getDefNameProperty(t, id)) {
		char 	temp[1024];
		getBoxType(t, &ins, &outs);
		stringstream s, l;
		s << tree2str(id);
		l << legalFileName(t,1024,temp) << "." << gDevSuffix;
		scheduleDrawing(t);
		return makeBlockSchema(ins, outs, s.str(), linkcolor, l.str());

    } else  if (getDefNameProperty(t, id) && ! isPureRouting(t)) {
		// named case : not a slot, with a name
		// draw a line around the object with its name
		stringstream 	s; s << tree2str(id);
		return makeDecorateSchema(generateInsideSchema(t), 10, s.str());

	} else {
		// normal case
		return generateInsideSchema(t);
	}
}



/**
 * Generate the inside schema of a block diagram
 * according to its type
 */
static schema* generateInsideSchema(Tree t)
{
	Tree a, b, ff, l, type,name,file;
	int		i;
	double	r;
	prim0	p0;
	prim1	p1;
	prim2	p2;
	prim3	p3;
	prim4	p4;
	prim5	p5;


	xtended* xt = (xtended*)getUserData(t);

	if (xt)							{ return makeBlockSchema(xt->arity(), 1, xt->name(), normalcolor, ""); }

    else if (isInverter(t))         { return makeInverterSchema(invcolor); }

	else if (isBoxInt(t, &i))		{ stringstream 	s; s << i; return makeBlockSchema(0, 1, s.str(), numcolor, "" ); }
	else if (isBoxReal(t, &r)) 		{ stringstream 	s; s << r; return makeBlockSchema(0, 1, s.str(), numcolor, "" ); }
	else if (isBoxWire(t)) 			{ return makeCableSchema(); }
	else if (isBoxCut(t)) 			{ return makeCutSchema();  }

	else if (isBoxPrim0(t, &p0)) 	{ return makeBlockSchema(0, 1, prim0name(p0), normalcolor, ""); }
	else if (isBoxPrim1(t, &p1)) 	{ return makeBlockSchema(1, 1, prim1name(p1), normalcolor, ""); }
	else if (isBoxPrim2(t, &p2)) 	{ return makeBlockSchema(2, 1, prim2name(p2), normalcolor, ""); }
	else if (isBoxPrim3(t, &p3)) 	{ return makeBlockSchema(3, 1, prim3name(p3), normalcolor, ""); }
	else if (isBoxPrim4(t, &p4)) 	{ return makeBlockSchema(4, 1, prim4name(p4), normalcolor, ""); }
	else if (isBoxPrim5(t, &p5)) 	{ return makeBlockSchema(5, 1, prim5name(p5), normalcolor, ""); }

	else if (isBoxFFun(t, ff)) 					{ return makeBlockSchema(ffarity(ff), 1, ffname(ff), normalcolor, ""); }
    else if (isBoxFConst(t, type,name,file))    { return makeBlockSchema(0, 1, tree2str(name), normalcolor, ""); }
    else if (isBoxFVar (t, type, name,file))    { return makeBlockSchema(0, 1, tree2str(name), normalcolor, ""); }

	else if (isBoxButton(t)) 		{ return generateUserInterfaceSchema(t); }
	else if (isBoxCheckbox(t)) 		{ return generateUserInterfaceSchema(t); }
	else if (isBoxVSlider(t)) 		{ return generateUserInterfaceSchema(t); }
	else if (isBoxHSlider(t)) 		{ return generateUserInterfaceSchema(t); }
	else if (isBoxNumEntry(t)) 		{ return generateUserInterfaceSchema(t); }
	else if (isBoxVBargraph(t))		{ return generateBargraphSchema(t); }
	else if (isBoxHBargraph(t))		{ return generateBargraphSchema(t); }

	// don't draw group rectangle when labels are empty (ie "")
    else if (isBoxVGroup(t,l,a))	{ 	stringstream s; s << "vgroup(" << extractName(l) << ")";
										schema* r = generateDiagramSchema(a);
									  	return makeDecorateSchema(r, 10, s.str()); }
    else if (isBoxHGroup(t,l,a))	{ 	stringstream s; s << "hgroup(" << extractName(l) << ")";
										schema* r = generateDiagramSchema(a);
									  	return makeDecorateSchema(r, 10, s.str()); }
    else if (isBoxTGroup(t,l,a))	{ 	stringstream s; s << "tgroup(" << extractName(l) << ")";
										schema* r = generateDiagramSchema(a);
									  	return makeDecorateSchema(r, 10, s.str()); }

	else if (isBoxSeq(t, a, b)) 	{ return makeSeqSchema(generateDiagramSchema(a), generateDiagramSchema(b)); }
	else if (isBoxPar(t, a, b)) 	{ return makeParSchema(generateDiagramSchema(a), generateDiagramSchema(b)); }
	else if (isBoxSplit(t, a, b)) 	{ return makeSplitSchema(generateDiagramSchema(a), generateDiagramSchema(b)); }
	else if (isBoxMerge(t, a, b)) 	{ return makeMergeSchema(generateDiagramSchema(a), generateDiagramSchema(b)); }
	else if (isBoxRec(t, a, b)) 	{ return makeRecSchema(generateDiagramSchema(a), generateDiagramSchema(b)); }

	else if (isBoxSlot(t, &i))		{ return generateOutputSlotSchema(t); }
	else if (isBoxSymbolic(t,a,b))	{
		Tree 	id;
		if (getDefNameProperty(t, id)) {
			return generateAbstractionSchema(generateInputSlotSchema(a), b);
		} else {
			return makeDecorateSchema(generateAbstractionSchema(generateInputSlotSchema(a), b), 10, "Abstraction");
		}
	}

	else {

		fprintf(stderr, "Internal Error, box expression not recognized : "); print(t, stderr); fprintf(stderr, "\n");
		exit(1);

	}
}

/**
 * Convert User interface element into a textual representation
 */
static void UserInterfaceDescription(Tree box, string& d)
{
    Tree    t1, label, cur, min, max, step;
    stringstream 	fout;
    // user interface
         if (isBoxButton(box, label))	fout << "button(" << extractName(label) << ')';
    else if (isBoxCheckbox(box, label))	fout << "checkbox(" << extractName(label) << ')';
    else if (isBoxVSlider(box, label, cur, min, max, step)) 	{
        fout << "vslider("
             << extractName(label) << ", "
             << boxpp(cur) << ", "
             << boxpp(min) << ", "
             << boxpp(max) << ", "
             << boxpp(step)<< ')';
    }
    else if (isBoxHSlider(box, label, cur, min, max, step)) 	{
        fout << "hslider("
             << extractName(label) << ", "
             << boxpp(cur) << ", "
             << boxpp(min) << ", "
             << boxpp(max) << ", "
             << boxpp(step)<< ')';
    }
    else if (isBoxVGroup(box, label, t1)) {
        fout << "vgroup(" << extractName(label) << ", " << boxpp(t1, 0) << ')';
    }
    else if (isBoxHGroup(box, label, t1)) {
        fout << "hgroup(" << extractName(label) << ", " << boxpp(t1, 0) << ')';
    }
    else if (isBoxTGroup(box, label, t1)) {
        fout << "tgroup(" << extractName(label) << ", " << boxpp(t1, 0) << ')';
    }
    else if (isBoxHBargraph(box, label, min, max)) 	{
        fout << "hbargraph("
             << extractName(label) << ", "
             << boxpp(min) << ", "
             << boxpp(max) << ')';
    }
    else if (isBoxVBargraph(box, label, min, max)) 	{
        fout << "vbargraph("
             << extractName(label) << ", "
             << boxpp(min) << ", "
             << boxpp(max) << ')';
    }
    else if (isBoxNumEntry(box, label, cur, min, max, step)) 	{
        fout << "nentry("
             << extractName(label) << ", "
             << boxpp(cur) << ", "
             << boxpp(min) << ", "
             << boxpp(max) << ", "
             << boxpp(step)<< ')';
    }
    else {
        cerr << "INTERNAL ERROR : unknow user interface element " << endl;
        exit(0);
    }
    d = fout.str();
}


/**
 * Generate a 0->1 block schema for a user interface element
 */
static schema* 	generateUserInterfaceSchema(Tree t)
{
    string s; UserInterfaceDescription(t,s);
    return makeBlockSchema(0, 1, s, uicolor, "");
}


/**
 * Generate a 1->1 block schema for a user interface bargraph
 */
static schema* generateBargraphSchema(Tree t)
{
    string s; UserInterfaceDescription(t,s);
    return makeBlockSchema(1, 1, s, uicolor, "");
}



/**
 * Generate a 1->0 block schema for an input slot
 */
static schema* generateInputSlotSchema(Tree a)
{
	Tree id; assert(getDefNameProperty(a, id));
	stringstream s; s << tree2str(id);
	return makeBlockSchema(1, 0, s.str(), slotcolor, "");
}



/**
 * Generate a 0->1 block schema for an output slot
 */
static schema* generateOutputSlotSchema(Tree a)
{
	Tree id; assert(getDefNameProperty(a, id));
	stringstream s; s << tree2str(id);
	return makeBlockSchema(0, 1, s.str(), slotcolor, "");
}



/**
 * Generate an abstraction schema by placing in sequence
 * the input slots and the body
 */
static schema* generateAbstractionSchema(schema* x, Tree t)
{
	Tree 	a,b;

	while (isBoxSymbolic(t,a,b)) {
		x = makeParSchema(x, generateInputSlotSchema(a));
		t = b;
	}
	return makeSeqSchema(x, generateDiagramSchema(t));
}

static schema* addSchemaInputs(int ins, schema* x)
{
    if (ins==0) {
        return x;
    } else {
        schema* y = 0;
        do {
            schema* z = makeConnectorSchema();
            if (y != 0) {
                y = makeParSchema(y,z);
            } else {
                y = z;
            }
        } while (--ins);
        return makeSeqSchema(y,x);
    }
}


static schema* addSchemaOutputs(int outs, schema* x)
{
    if (outs==0) {
        return x;
    } else {
        schema* y = 0;
        do {
            schema* z = makeConnectorSchema();
            if (y != 0) {
                y = makeParSchema(y,z);
            } else {
                y = z;
            }
        } while (--outs);
        return makeSeqSchema(x,y);
    }
}
