// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// phrase_generator.cpp : Defines the entry point for the console application.

//

//-b -p r:\code\ryzom\data_leveldesign -o r:/code/ryzom/data_leveldesign/leveldesign/game_element/sphrase/magic/ magic_bricks.csv -d -m
//   -p r:\code\ryzom\data_leveldesign -o r:/code/ryzom/data_leveldesign/leveldesign/game_element/sphrase/magic/ magic_bricks.csv -d -m

#include "stdafx.h"





// Misc
#include <nel/misc/types_nl.h>
#include "nel/misc/path.h"
#include "nel/misc/file.h"
#include "nel/misc/smart_ptr.h"
#include "nel/misc/command.h"
#include "nel/misc/common.h"
#include "nel/misc/path.h"
#include <nel/misc/sstring.h>
#include <nel/misc/diff_tool.h>
#include "nel/misc/algo.h"
#include "nel/misc/words_dictionary.h"
// Georges
#include "nel/georges/u_form.h"
#include "nel/georges/u_form_elm.h"
#include "nel/georges/u_form_dfn.h"
#include "nel/georges/u_form_loader.h"
#include "nel/georges/u_type.h"
// Georges, bypassing interface
#include "georges/stdgeorges.h"
#include "georges/form.h"
// Game share
//#include "game_share/xml.h"
// Unicode language file
// C
#include <time.h>
#include <stdio.h>
#include <conio.h>
#include <fstream>
// stl
#include <map>

#include "skill_tree.h"

using namespace NLMISC;
using namespace NLGEORGES;
using namespace std;


typedef vector<string> vs;
typedef map< string, string > mss;
typedef map< string, vs > msvs;

bool				GenerateBrickProgression = false;
bool				ProduceDocFromExistingPhrases = false;
bool				MultipleDocFiles = false;
bool				Hypertext = true;
string				PhrasePath = "r:/code/ryzom/data_leveldesign/leveldesign/game_element/sphrase/";
uint				NbSheetsGenTries = 0;
uint				NbSheetsWritten = 0;
uint				NbSheetsRead = 0;
uint				NbSheetsRejected = 0;


bool				UseBricks;

const string brSheetType = "sbrick";
const string phSheetType = "sphrase";
const string PHRASE_MAGIC_PREFIX = "abm_"; // action bricks magic (bm for brick filter)

struct CBrickInfo
{
	CBrickInfo( const string& ls="", const string& t="",
				const vs& props=vs() ) :
	LearnSkills(ls), Text(t), Props(props) {}

	string	LearnSkills;
	string	Text; // UTF-8
	vs		Props;
};


vs					OptionBricks, CounterpartBricks, AllBricks, PhrasesWithInvalidCost, InvalidPhrases;
set<string>			UsedCounterpartBricks, UsedCounterpartBrickFamiliesInPhrase;
map<string, sint>	SabrinaCosts;
map<string, sint>	PhraseSabrinaCosts;
map<string, bool>	PhraseCastable;
map<string, CBrickInfo>	BrickInfo;
mss					TextToBrick;
multimap< uint, pair<string, string> >	Progression; // phrase code, min. skill
mss					PhraseNames, PhraseTitles;
vector<vs>			ValidPhrases;
set<string>			UsedBricks, GeneratedPhrases;
map<string, vs>		GrammarMandatParamBrickFamilies, GrammarOptionCreditBrickFamilies;
UFormLoader			*FormLoader;
vector<FILE*>		PhraseDocFiles( 26 );
string				DocFileName,DocFileNameRoot;
msvs				Phrases;
map<string,uint>	SkillNameToMaxSkill;
map<string,string>	PhraseCodeToLink;
CWordsDictionary	Dico;
CStaticSkillsTree	SkillsTree;


/*
 *
 */
string				inputSheetPath;
bool				inputSheetPathLoaded = false;
map<string, string>	inputSheetPathContent; // short filename without ext, full filename with path


//-----------------------------------------------
//	getBrickTypeLetterRPos
//
//-----------------------------------------------
uint getBrickTypeLetterRPos( string& brick )
{
	/*
	uint i =0;
	while( i<brick.size() && !isdigit(brick[i]) ) 
		i++;

	nlassert(i<brick.size());

	return (brick.size() - i + 2);
	*/

	uint i =brick.size()-1;
	while( i>=0 && (isdigit(brick[i]) || brick[i]=='_') ) 
		i--;

	return (brick.size() - i + 1);

} // getBrickTypeLetterRPos //




//-----------------------------------------------
//	loadSheetPath
//
// from georges2csv
//-----------------------------------------------
void	loadSheetPath()
{
	if (inputSheetPathLoaded)
		return;

	NLMISC::createDebug();
	NLMISC::WarningLog->addNegativeFilter( "CPath::insertFileInMap" );

	CPath::addSearchPath(inputSheetPath, true, false); // for Georges to work properly

	vector<string>	files;
	CPath::getPathContent (inputSheetPath, true, false, true, files);

	uint	i;
	for (i=0; i<files.size(); ++i)
	{
		string	filename = files[i];
		string	filebase = CFile::getFilenameWithoutExtension(filename);
		if( CFile::getExtension(filename)!="saibrick" && CFile::getExtension(filename)!="saiphrase" )
		{
			inputSheetPathContent[filebase] = filename;
		}
	}

	inputSheetPathLoaded = true;

} // loadSheetPath //


/*
 *
 */
void displayList( const string& title, const vector<string>& v, CLog *log=DebugLog )
{
	if ( ! title.empty() )
		log->displayRaw( "%s: ", title.c_str() );
	vector<string>::const_iterator ist;
	for ( ist=v.begin(); ist!=v.end(); ++ist )
		log->displayRaw( "%s ", (*ist).c_str() );
	log->displayRawNL( "" );
}


/*
 *
 */
class CStrIComparator : public binary_function<string, string, bool>
{
public:
	bool	operator() ( const string& s1, const string& s2 ) const
	{
		return (nlstricmp( s1, s2 ) == 0);
	}
};


/*
 *
 */
uint getIndexFromString( const string& s, const vector<string>& v, bool displayWarning=true )
{
	if ( v.empty() )
	{
		nlwarning( "Can't find '%s' in empty array", s.c_str() );
		return ~0;
	}
	else
	{
		vector<string>::const_iterator ist = find_if( v.begin(), v.end(), bind2nd(CStrIComparator(), s) );
		if ( ist == v.end() )
		{
			if ( displayWarning )
			{
				nlwarning( "Can't find '%s' in:", s.c_str() );
				displayList( "", v, WarningLog );
			}
			return ~0;
		}
		else
			return ist - v.begin();
	}
}


//-----------------------------------------------
// Erase every carriage returns of the string
//
//-----------------------------------------------
void eraseCarriageReturns( string& s )
{
	const char CR = '\n';
	string::size_type p = s.find( CR );
	while ( (p=s.find( CR )) != string::npos )
		s.erase( p, 1 );
} //



// First param: vector of indices of columns matching wantedColumnNames
// Second param: vector of fields matching wantedColumnNames
typedef void (*TDeliveryCallback) ( mss& );


//-----------------------------------------------
// loadCSVFile
//
//-----------------------------------------------
void	loadCSVFile( const char *filename, TDeliveryCallback deliveryCallback )
{
	char lineBuffer[2048];
	FILE *file;
	const char *SEPARATOR = ";";
	vector<string> args;
	vector<string>::iterator iarg;

	if ( (file = nlfopen( filename, "r" )) == NULL )
	{
		nlwarning( "Can't find file %s", filename );
	}
	else
	{
		// Read first line as header with column names
		lineBuffer[0] = '\0';
		fgets( lineBuffer, 2048, file );
		explode( lineBuffer, SEPARATOR, args );

		// Store column names (and get rid of carriage returns!)
		vector < string > columnNames;
		mss valuesByName;
		for ( iarg=args.begin(); iarg!=args.end(); ++iarg )
		{
			eraseCarriageReturns( *iarg );
			columnNames.push_back( *iarg );
			valuesByName.insert( make_pair( *iarg, string("") ) );
		}

		// for each line, deliver the value of the fields
		while ( ! feof(file) )
		{
			// Get from file
			lineBuffer[0] = '\0';
			fgets( lineBuffer, 2048, file );
			explode( lineBuffer, SEPARATOR, args );

			// Set values (and get rid of carriage returns!)
			for ( iarg=args.begin(); iarg!=args.end(); ++iarg )
			{
				eraseCarriageReturns( *iarg );
				valuesByName[columnNames[iarg-args.begin()]] = *iarg;
			}

			// Deliver the wanted fields
			deliveryCallback( valuesByName );
		}
	}

} // loadCSVFile //


//set<string>	Skills;


//-----------------------------------------------
//	brickDeliveryCallback
//
// Fetch brick code and sabrina cost
// - AllBricks
// - BrickInfo
// - OptionBricks
// - CounterpartBricks
// - SabrinaCosts
//-----------------------------------------------
void brickDeliveryCallback( mss& values )
{
	string s = values["Brick_id"];
	if ( s.empty() )
	{
		s = values["FILE"];
		if ( s.empty() )
			s = values["fileName"];
	}

	string brick = CFile::getFilenameWithoutExtension( s );
	strupr( brick );
	if ( brick.empty() )
	{
		nlwarning("<brickDeliveryCallback> can't get root filename of %s",s.c_str());
		return;
	}

	string sc = values["Basics.SabrinaCost"];
	string ls = values["Basics.LearnRequiresOneOfSkills"];
	string txt = values["name"]; // TODO: only for combat
	string fmn = values["familyName"];
	string propIdent = "Basics.Property";

	if ( UseBricks )
	{
		AllBricks.push_back( brick );
		string name = (txt.empty()) ? fmn : txt;
		vs props;

		// Find all Basics.Property N (assumes they are subsequent)
		mss::const_iterator imv = values.find( propIdent + " 0" );
		for ( ; imv!=values.end(); ++imv )
		{
			const string& colName = (*imv).first;
			const string& v = (*imv).second;
			if ( colName.find( propIdent ) != string::npos )
			{
				if ( v != "NULL" && !v.empty() )
					props.push_back( v );
			}
			else
				break;
		}
		BrickInfo.insert( make_pair( brick, CBrickInfo( ls, name, props ) ) );
	}

	// Store brick in right container
	string::size_type p = brick.size() - getBrickTypeLetterRPos(brick);
	if ( ((sint)p) >= 0 )
	{
		switch ( brick[p] )
		{
		case 'O': OptionBricks.push_back( brick );
			break;
		case 'C': CounterpartBricks.push_back( brick );
			break;
		}
	}
	else
	{
		nlwarning( "Invalid brick code: %s", brick.c_str() );
		return;
	}

	// Store cost
	sint sabrinaCost;
	if ( sc.empty() )
	{
		nldebug( "No sabrina cost for %s, assuming cost 0", brick.c_str() );
		sabrinaCost = 0;
	}
	else
	{
		sabrinaCost = atoi( sc.c_str() );
	}
	SabrinaCosts.insert( make_pair( brick, sabrinaCost ) );

	/* // Quick hack to generate skill codes
	string skill = brick.substr( 1, p-1 );
	if ( ! skill.empty() )
		Skills.insert( skill );*/

} // brickDeliveryCallback //



//-----------------------------------------------
//	loadBricks
//
//-----------------------------------------------
void	loadBricks( const char* filename )
{
	loadCSVFile( filename, brickDeliveryCallback );
	if ( ProduceDocFromExistingPhrases )
		nlinfo( "Loaded %u option bricks, %u counterpart bricks, %u sabrina costs", OptionBricks.size(), CounterpartBricks.size(), SabrinaCosts.size() );
	else if ( UseBricks )
		nlinfo( "Loaded %u bricks", AllBricks.size() );

	/*set<string>::const_iterator iss;
	for ( iss=Skills.begin(); iss!=Skills.end(); ++iss )
	{
		const string& skill = (*iss);
		InfoLog->displayRawNL( "  <DEFINITION Label=\"S%s\" Value=\"S%s\"/>", skill.c_str(), skill.c_str() );
	}*/

} // loadBricks //


/*
 *
 */
string	getRootBrickForOptionOrCredit( const string& ob )
{
	// Extract brick code radix
	string::size_type p = ob.size() - getBrickTypeLetterRPos(const_cast<string&>(ob));
	if ( (ob.size() <= getBrickTypeLetterRPos(const_cast<string&>(ob))) ||
		 ((ob[p] != 'O') && (ob[p] != 'C')) )
		nlerror( "%s is not an option or credit brick", ob.c_str() );
	string radix = ob.substr( 0, p );
	
	// Append root brick suffix
	return radix + "PA01";
}


/*
 *
 */
string	getBrickFamily( const string& b )
{
	if ( b.size() >= getBrickTypeLetterRPos(const_cast<string&>(b))+2 )
	{
		string::size_type p = b.size() - getBrickTypeLetterRPos(const_cast<string&>(b));
		return b.substr( 0, p+2 );
	}
	return string();
}


/*
 *
 */
bool	isFromBrickFamily( const string& brick, const string& fam )
{
	return nlstricmp( brick.substr( 0, fam.size() ), fam ) == 0;
}


/*
 *
 */
string	getFirstBrickOfFamily( const string& family )
{
	vs::const_iterator ib;
	for ( ib=AllBricks.begin(); ib!=AllBricks.end(); ++ib )
	{
		const string& brick = *ib;
		if ( isFromBrickFamily( brick, family ) )
			return brick;
	}
	return string();
}


/*
 *
 */
vs		getAllBricksOfFamily( const string& family )
{
	vs res;
	vs::const_iterator ib;
	for ( ib=AllBricks.begin(); ib!=AllBricks.end(); ++ib )
	{
		const string& brick = *ib;
		if ( isFromBrickFamily( brick, family ) )
			res.push_back( brick );
	}
	return res;
}


/*
 *
 */
uint getCompatibleCounterpartBrickForCost( uint phraseCost, vs& phrase )
{
	//nlinfo( "Searching credit for cost %u", phraseCost );

	// Get the lowest matching counterpart brick
	uint minHigherCounterpartValue = ~0, maxLowerCounterpartValue = 0, counterpartValue;
	vs::const_iterator icb, iPerfectMatch = CounterpartBricks.end(), iMinCb = CounterpartBricks.end(), iMaxCb = CounterpartBricks.end();
	for ( icb=CounterpartBricks.begin(); icb!=CounterpartBricks.end(); ++icb)
	{
		const string& cb = *icb;

		// Skip if family already used in current phrase
		if ( UsedCounterpartBrickFamiliesInPhrase.find( getBrickFamily( cb ) ) != UsedCounterpartBrickFamiliesInPhrase.end() )
			continue;

		counterpartValue = abs( SabrinaCosts[cb] );
		//nldebug( "Trying with credit %u", counterpartValue );
		if ( counterpartValue == phraseCost )
		{
			// Perfect match, check if not already taken
			if ( UsedCounterpartBricks.insert( cb ).second )
			{
				UsedCounterpartBrickFamiliesInPhrase.insert( getBrickFamily( cb ) );
				phrase.push_back( cb );
				return counterpartValue;
			}
			else
			{
				// If already taken, we will come back to it later
				iPerfectMatch = icb;
			}
		}
		else if ( counterpartValue > phraseCost )
		{
			// Higher => get the minimum
			if ( counterpartValue < minHigherCounterpartValue )
			{
				minHigherCounterpartValue = counterpartValue;
				iMinCb = icb;
			}
		}
		else // counterpartValue < phraseCost : store the max
		{
			if ( counterpartValue >= maxLowerCounterpartValue )
			{
				maxLowerCounterpartValue = counterpartValue;
				iMaxCb = icb;
			}
		}
	}
	if ( iPerfectMatch != CounterpartBricks.end() )
	{
		// We skipped a perfect match in order to try to get a new value. But none found. Now get back to the last value.
		phrase.push_back( *iPerfectMatch );
		UsedCounterpartBrickFamiliesInPhrase.insert( getBrickFamily( *iPerfectMatch ) );
		return abs( SabrinaCosts[*iPerfectMatch] );
	}
	else if ( iMinCb == CounterpartBricks.end() )
	{
		if ( iMaxCb == CounterpartBricks.end() )
		{
			nlerror( "No matching counterpart" );
			return ~0;
		}
		else
		{
			// No phrase possible with only one (more) counterpart, try with the max and more (recurse)
			UsedCounterpartBricks.insert( *iMaxCb );
			UsedCounterpartBrickFamiliesInPhrase.insert( getBrickFamily( *iMaxCb ) );
			phrase.push_back( *iMaxCb );
			return maxLowerCounterpartValue + getCompatibleCounterpartBrickForCost( phraseCost - maxLowerCounterpartValue, phrase );
		}
	}
	else
	{
		// Phrase possible with one (more) counterpart
		UsedCounterpartBricks.insert( *iMinCb );
		UsedCounterpartBrickFamiliesInPhrase.insert( getBrickFamily( *iMinCb ) );
		phrase.push_back( *iMinCb );
		return minHigherCounterpartValue;
	}
}


/*
 *
 */
void getCompatibleCounterpartBricks( vs& phrase )
{
	// Calculate the cost of the phrase
	sint phraseCost = 0;
	string phraseStr;
	vs::const_iterator ip;
	for ( ip=phrase.begin(); ip!=phrase.end(); ++ip )
	{
		const string& brick = *ip;
		sint sabrinaCost;
		map<string, sint>::const_iterator isc = SabrinaCosts.find( brick );
		if ( isc != SabrinaCosts.end() )
			sabrinaCost = (*isc).second;
		else
			sabrinaCost = 0;
		phraseCost += sabrinaCost;
		phraseStr += brick + " ";
	}
	
	// Find matching counterpart(s), only 1 per family
	UsedCounterpartBrickFamiliesInPhrase.clear();
	uint counterpartValue = getCompatibleCounterpartBrickForCost( phraseCost, phrase );

	displayList( toString( "+%3u -%3u", phraseCost, counterpartValue ), phrase );
}


/*
 *
 */
/*void getCompatiblePhraseByCounterpart( const string& counterpartBrick, vs& phrase )
{
	sint sabrinaCost = SabrinaCosts[counterpartBrick];

	// Assuming root brick cost is zero!
	vs::const_iterator iob;
	for ( iob=OptionBricks.begin(); iob!=OptionBricks.end(); ++iob )
	{
		// TODO: Find the highest cost that is lower or equal than the counterpart value
		const string& ob = *iob;
		if ( SabrinaCosts[ob] <= SabrinaCosts[counterpartBrick] )
			break; // currently, take the first found
	}
	if ( iob != OptionBricks.end() )
	{
		string rb = getRootBrickForOptionOrCredit( *iob );
		phrase.push_back( rb );
		phrase.push_back( *iob );
		phrase.push_back( counterpartBrick );
		nldebug( "%s %s %s: +%u -%u", rb.c_str(), (*iob).c_str(), counterpartBrick.c_str(),
				 SabrinaCosts[rb]+SabrinaCosts[*iob], SabrinaCosts[counterpartBrick] );
	}
	else
		nlwarning( "No matching phrase for counterpart %s", counterpartBrick.c_str() );
}*/


/*
 * Clear the form to reuse it (and all contents below node)
 */
void clearSheet( CForm *form, UFormElm* node )
{
	((CFormElm*)node)->clean();
	form->clean();
}



/*
 *
 */
inline void	explodeBrickAndParameters( const string& brickAndParams, vs& bricks )
{
	explode( brickAndParams, " ", bricks );
}


/*
 *
 */
string getBrickType( const string& brick )
{
	if ( brick.size() < 4 )
		return "INVALID TYPE in " + brick;
	else
	{
		switch ( brick[brick.size()-getBrickTypeLetterRPos(const_cast<string&>(brick))] )
		{
		case 'P': return "Root";
			break;
		case 'E': return "Effect";
			break;
		case 'O': return "Option";
			break;
		case 'M': return "Modifier";
			break;
		case 'C': return "Credit";
			break;
		default:
			return "INVALID TYPE in " + brick;
		}
	}
}


//-----------------------------------------------
//	printBrickInfo
//
//-----------------------------------------------
void	printBrickInfo( FILE *htmlfile, const string& brick, const string& grammarError, sint& sabrinaCost, uint& minSkillValue, string& minSkill )
{
	minSkill.clear();
	string b = brick;
	strupr( b );
	string brickType = getBrickType( b );
	sint sc = (brickType=="Credit") ? -abs( SabrinaCosts[b] ) : SabrinaCosts[b];
	CBrickInfo& bInfo = BrickInfo[b];
	fprintf( htmlfile, "<LI><B>%s %s</B> %s<BR>\n", brickType.c_str(), b.c_str(), bInfo.Text.c_str() );
	if ( ! grammarError.empty() )
	{
		fprintf( htmlfile, "<FONT COLOR=\"RED\">%s</FONT><BR>\n", grammarError.c_str() );
	}
	else	
	{
		fprintf( htmlfile, "Sabrina Cost: %d <BR>\n", sc );
		if( !bInfo.LearnSkills.empty() )
		{
			fprintf( htmlfile, "Skills required: %s<BR>\n", bInfo.LearnSkills.c_str() );
		}
		if( bInfo.Props.size() )
		{
			fprintf( htmlfile, "Properties:" );
			for ( vs::const_iterator ip = bInfo.Props.begin(); ip!=bInfo.Props.end(); ++ip )
			{
				fprintf( htmlfile, " %s", (*ip) );
			}
		}
	}
	fprintf( htmlfile, "</LI>\n" );

	// Calculate sabrina cost & skill value
	sabrinaCost = sc;
	if ( bInfo.LearnSkills.empty() )
		minSkillValue = 0;
	else
	{
		minSkillValue = ~0;
		vector<string> skillsAndValues;
		explode( bInfo.LearnSkills, ":", skillsAndValues, true );
		vector<uint> skillValues( skillsAndValues.size(), ~0 );
		vector<string>::iterator isv;
		for ( isv=skillsAndValues.begin(); isv!=skillsAndValues.end(); ++isv )
		{
			const string& sav = *isv;
			string::size_type p = sav.find( ' ' );
			if ( (p == string::npos) || (sav.size() == p+1) )
				nlwarning( "Invalid LearnRequiresOneOfSkills value '%s'", sav.c_str() );
			else
			{
				uint sv = atoi( sav.substr( p+1 ).c_str() );
				skillValues[isv-skillsAndValues.begin()] = sv;
				if ( sv < minSkillValue )
					minSkillValue = sv;
			}
		}

		for ( isv=skillsAndValues.begin(); isv!=skillsAndValues.end(); ++isv )
		{
			if ( skillValues[isv-skillsAndValues.begin()] == minSkillValue )
			{
				string& sav = *isv;
				if ( (! sav.empty()) && (sav[0] != 'S') )
					sav = 'S' + sav;
				if ( minSkill.find( sav ) == string::npos )
				{
					if ( ! minSkill.empty() )
						minSkill += ", ";
					minSkill += sav;
				}
			}
		}
	}

} // printBrickInfo //


//-----------------------------------------------
//	loadBrickGrammar
//
//-----------------------------------------------
void	loadBrickGrammar()
{
	uint nbRootBricks = 0;
	vs::const_iterator ib;
	for ( ib=AllBricks.begin(); ib!=AllBricks.end(); ++ib )
	{
		string brick = *ib;
		strupr( brick );
		if ( brick.size() >= 4 )
		{
			char brickType = brick[brick.size()-getBrickTypeLetterRPos(brick)];

			/*// As the root bricks may be absent from the table, deduce them (obsolete)
			if ( brickType == 'O' )
			{
				string rootBrick = getRootBrickForOptionOrCredit( brick );
				if ( GrammarOptionCreditBrickFamilies.find( rootBrick ) == GrammarOptionCreditBrickFamilies.end() )
				{
					brick = rootBrick;
					brickType = 'P';
				}
				else
				{
					continue;
				}
			}*/

			// If not skipped by previous 'continue'
			if ( (brickType == 'P') || (brickType == 'E') || (brickType == 'O' ) ) // root, effect, option
			{
				NLMISC::CSmartPtr<CForm> form = (CForm*)FormLoader->loadForm( (strlwr(static_cast<const string&>(brick))+"."+brSheetType).c_str() );
				if ( ! form )
				{
					nlwarning( "Can't load sheet %s", ((strlwr(static_cast<const string&>(brick)))+"."+phSheetType).c_str() );
					continue;
				}
				for ( uint i=0; i!=12; ++i )
				{
					string value;
					form->getRootNode().getValueByName( value, toString( "Mandatory.f%u", i ).c_str() );
					if ( (! value.empty()) && (value != "Unknown") )
					{
						GrammarMandatParamBrickFamilies[brick].push_back( value );
					}
				}
				if ( brickType == 'O' )
				{
					for ( uint i=0; i!=4; ++i )
					{
						string value;
						form->getRootNode().getValueByName( value, toString( "Parameter.f%u", i ).c_str() );
						if ( (! value.empty()) && (value != "Unknown") )
						{
							GrammarMandatParamBrickFamilies[brick].push_back( value );
						}
					}
				}
				if ( brickType == 'P' ) // root
				{
					++nbRootBricks;
					for ( uint i=0; i!=32; ++i )
					{
						string value;
						form->getRootNode().getValueByName( value, toString( "Optional.f%u", i ).c_str() );
						if ( (! value.empty()) && (value != "Unknown") )
						{
							GrammarOptionCreditBrickFamilies[brick].push_back( value );
						}
					}
					for ( uint i=0; i!=12; ++i )
					{
						string value;
						form->getRootNode().getValueByName( value, toString( "Credit.f%u", i ).c_str() );
						if ( (! value.empty()) && (value != "Unknown") )
						{
							GrammarOptionCreditBrickFamilies[brick].push_back( value );
						}
					}
				}
			}
		}
		else
		{
			nlwarning( "Invalid brick code %s", brick.c_str() );
		}
	}
	nlinfo( "%u bricks have mandatory/parameter grammar rules", GrammarMandatParamBrickFamilies.size() );
	nlinfo( "%u bricks have option/credit grammar rules", GrammarOptionCreditBrickFamilies.size() );
	nlinfo( "Found or deduced %u root bricks", nbRootBricks );

} // loadBrickGrammar //



//-----------------------------------------------
//	loadPhraseTitles
//
//-----------------------------------------------
void	loadPhraseTitles()
{
	STRING_MANAGER::TWorksheet worksheet;
	STRING_MANAGER::loadExcelSheet( "r:/code/ryzom/translation/translated/sphrase_words_en.txt", worksheet );
	uint cp, cn;
	if ( worksheet.findCol( ucstring("sphrase ID"), cp ) && worksheet.findCol( ucstring("name"), cn ) )
	{
		for ( std::vector<STRING_MANAGER::TWorksheet::TRow>::iterator ip = worksheet.begin(); ip!=worksheet.end(); ++ip )
		{
			if ( ip == worksheet.begin() ) // skip first row
				continue;
			STRING_MANAGER::TWorksheet::TRow& row = *ip;
			PhraseTitles.insert( make_pair( strlwr(row[cp].toString()), row[cn].toUtf8() ) );
		}
	}
	else
		nlwarning( "sphrase ID or name not found" );

	nlinfo( "Loaded %u phrase titles", PhraseTitles.size() );

} // loadPhraseTitles //


//-----------------------------------------------
//	loadBrickTitles
//
//-----------------------------------------------
void	loadBrickTitles()
{
	STRING_MANAGER::TWorksheet worksheet;
	STRING_MANAGER::loadExcelSheet( "r:/code/ryzom/translation/translated/sbrick_words_en.txt", worksheet );
	uint cp, cn, nbTitles = 0;
	if ( worksheet.findCol( ucstring("sbrick ID"), cp ) && worksheet.findCol( ucstring("name"), cn ) )
	{
		for ( std::vector<STRING_MANAGER::TWorksheet::TRow>::iterator ip = worksheet.begin(); ip!=worksheet.end(); ++ip )
		{
			if ( ip == worksheet.begin() ) // skip first row
				continue;
			STRING_MANAGER::TWorksheet::TRow& row = *ip;
			BrickInfo[strupr(row[cp].toString())].Text = row[cn].toUtf8();;
			++nbTitles;
		}
	}
	else
		nlwarning( "sbrick ID or name not found" );

	nlinfo( "Loaded %u brick titles", nbTitles );

} // loadBrickTitles //


/*
 *
 */
void	getChildrenBricks( const string& brick, vs& chFamilies )
{
	chFamilies = GrammarMandatParamBrickFamilies[brick];
}


/*
 *
 */
void	addError( string& errorStr, string& newError, uint& nbGrammarErrors )
{
	if ( ! errorStr.empty() )
		errorStr += "<BR>";
	errorStr += newError;
	++nbGrammarErrors;
}


/*
 *
 */
void	checkOptionOrCreditCompatibility( string& errorStr, const string& currentBrick, const string& rootBrick, uint& nbGrammarErrors )
{
	string brick = currentBrick;
	strupr( brick );
	if ( brick.size() >= 4 && brick[1]!='C' && brick[1]!='H') // C & H for craft and harvest
	{
		char brickType = brick[brick.size()-getBrickTypeLetterRPos(brick)];
		if ( (brickType == 'O') || (brickType == 'C') )
		{
			string rootBrick = getRootBrickForOptionOrCredit( brick );
			const vs& compatibleOptionOrCredits = GrammarOptionCreditBrickFamilies[rootBrick];
			vs::const_iterator ic;
			for ( ic=compatibleOptionOrCredits.begin(); ic!=compatibleOptionOrCredits.end(); ++ic )
			{
				if ( isFromBrickFamily( brick, (*ic) ) )
					break;
			}
			if ( ic == compatibleOptionOrCredits.end() )
			{
				addError( errorStr, toString( "This family is not compatible with options/credits of root %s", rootBrick.c_str() ), nbGrammarErrors );
			}
		}
	}
}


/*
 * Preconditions:
 * - grammarErrors.size() == phrase.size()
 * - r < phrase.size()
 *
 * Note: does not check that all bricks should have a different family
 */
void	checkGrammar( const vs& phrase, uint& r, vs& grammarErrors, uint& nbGrammarErrors, const string& rootBrick, bool readNext=true )
{
	uint origR = r;
	string grammarBrick = phrase[origR];
	strupr( grammarBrick );

	// Check option/credit
	checkOptionOrCreditCompatibility( grammarErrors[r], phrase[r], rootBrick, nbGrammarErrors );

	// Check mandatory/parameter
	vs chFamilies;
	getChildrenBricks( grammarBrick, chFamilies );
	++r;
	for ( vs::const_iterator icf=chFamilies.begin(); icf!=chFamilies.end(); ++icf )
	{
		// Detect incomplete phrase
		if ( r >= phrase.size() )
		{
			addError( grammarErrors[origR], "Missing mandatory/parameter " + (*icf) + " at the end", nbGrammarErrors );
			break;
		}

		// Detect wrong brick family
		if ( isFromBrickFamily( phrase[r], (*icf) ) )
		{
			// Check grammar using child as root
			checkGrammar( phrase, r, grammarErrors, nbGrammarErrors, phrase[r], false );
		}
		else
		{
			addError( grammarErrors[r], "Error: " + (*icf) + " expected (mandatory/parameter of " + grammarBrick + ")", nbGrammarErrors );
			++r;
		}
	}

	// Next
	if ( readNext && (r < phrase.size()) )
	{
		checkGrammar( phrase, r, grammarErrors, nbGrammarErrors, rootBrick );
	}
}


/*
 *
 */
char	getDocFileLetter( const string& sheetName )
{
	// skip abm_mt_, abm_ml_...
	char letter = 'a';
	uint nbUnderscoresToSkip = 2, nbUnderscoresFound = 0;
	for ( uint c=0; c!=sheetName.size(); ++c )
	{
		if ( nbUnderscoresFound == nbUnderscoresToSkip )
		{
			letter = sheetName[c];
			break;
		}
		if ( sheetName[c] == '_' )
			++nbUnderscoresFound;
	}
	return tolower( letter );
}


//-----------------------------------------------
//	testPhraseGrammarAndProduceDoc
//
//-----------------------------------------------
bool	testPhraseGrammarAndProduceDoc( const string& sheetName, const vs& phrase )
{
	string filename = strlwr( sheetName ) + "." + phSheetType;
	string phraseStatus;
	bool isPhraseCorrect = true;
	const char *rejectedstr = "(grammatically invalid)";
	
	// Check grammar for this phrase
	vs grammarErrors( phrase.size() );
	uint nbGrammarErrors = 0, r = 0;
	checkGrammar( phrase, r, grammarErrors, nbGrammarErrors, phrase[0] );
	if ( nbGrammarErrors != 0 )
	{
		InvalidPhrases.push_back( sheetName );
		isPhraseCorrect = false;
		phraseStatus = rejectedstr;
	}
	
	// Look-up phrase title
	string phraseTitle = PhraseTitles[sheetName];

	// Output phrase description
	char letter = 'a';
	if ( (! MultipleDocFiles) && (sheetName.size() > 3)  )
	{
		letter = tolower( sheetName[3] );
	}
	else
	{
		letter = getDocFileLetter( sheetName );
	}
	if ( letter < 'a' )
		letter = 'a';
	else if ( letter > 'z' )
		letter = 'z';
	FILE *htmlFile = PhraseDocFiles[letter - 'a'];
	sint sabrinaCost;
	fprintf( htmlFile, "<A NAME=\"%s\"></A><P><B>%s</B> %s %s<BR><UL>\n", sheetName.c_str(), filename.c_str(), phraseTitle.c_str(), phraseStatus.c_str() );
	vector<string> minBrickSkills( phrase.size() );
	vector<uint> minBrickSkillValues( phrase.size(), 0 );
	string brickMinSkill, maxSkill;
	sint posCost = 0, negCost = 0, totalCost;
	uint maxSkillValue = 0, brickMinSkillValue;
	for ( uint i=0; i!=phrase.size(); ++i )
	{
		printBrickInfo( htmlFile, phrase[i], grammarErrors[i], sabrinaCost, brickMinSkillValue, brickMinSkill );
		if ( sabrinaCost > 0 )
			posCost += sabrinaCost;
		else
			negCost += sabrinaCost;
		minBrickSkillValues[i] = brickMinSkillValue;
		minBrickSkills[i] = brickMinSkill;
		if ( brickMinSkillValue > maxSkillValue )
			maxSkillValue = brickMinSkillValue;
	}
	for ( uint i=0; i!=phrase.size(); ++i )
	{
		if ( minBrickSkillValues[i] == maxSkillValue )
		{
			if ( maxSkill.find( minBrickSkills[i] ) == string::npos )
			{
				if ( ! maxSkill.empty() )
					maxSkill += "; ";
				maxSkill += minBrickSkills[i];
			}
		}
	}
	if ( phrase.size() > 1 )
	{
		string effectOrOptionBrick = phrase[1];
		strupr( effectOrOptionBrick );
		if ( ! PhraseNames.insert( make_pair( sheetName, BrickInfo[effectOrOptionBrick].Text ) ).second )
			nlwarning( "Found duplicate phrase %s", sheetName.c_str() );
	}
	Progression.insert( make_pair( maxSkillValue, make_pair( sheetName, maxSkill ) ) );
	totalCost = posCost + negCost;
	PhraseSabrinaCosts.insert( make_pair(sheetName,totalCost) );
	char *redbegin = "", *redend = "";
	if ( totalCost > 0 )
	{
		map<string,bool>::const_iterator itCastable = PhraseCastable.find(sheetName);
		if( itCastable != PhraseCastable.end() )
		{
			if( (*itCastable).second )
			{
				redbegin = "<FONT COLOR=\"RED\">";
				redend = "</FONT>";
				PhrasesWithInvalidCost.push_back( sheetName );
				isPhraseCorrect = false;
			}
		}
	}
	fprintf( htmlFile, "<LI>%s<B>Total sabrina cost: </B>+%d %d = %d%s</LI>\n", redbegin, posCost, negCost, totalCost, redend );
	fprintf( htmlFile, "<LI><B>Minimum skill value required: %d</B></LI>\n", maxSkillValue );
	fprintf( htmlFile, "</UL></P>\n" );
	if ( ! isPhraseCorrect )
	{
		++NbSheetsRejected;
	}
	return isPhraseCorrect;

} // testPhraseGrammarAndProduceDoc //





/*
 *
 */
inline bool isSeparator( char c )
{
	return (c == ' ') || (c == '\t');
}



//-----------------------------------------------
//	produceDocFromExistingPhrases
//
// - Phrases
//-----------------------------------------------
void	produceDocFromExistingPhrases()
{
	vs files;
	CPath::getPathContent( PhrasePath, true, false, true, files );

	NbSheetsRead = 0;
	for ( vs::const_iterator ip=files.begin(); ip!=files.end(); ++ip )
	{
		if ( CFile::getExtension( *ip ) == phSheetType )
		{
			// Read george sheet
			NLMISC::CSmartPtr<UForm> form = (UForm*)FormLoader->loadForm( (*ip).c_str() );
			if ( ! form )
				nlerror( "Can't load sheet %s", (*ip).c_str() );

			// Get the bricks of the phrase
			vs phrase;
			for ( uint i=0; i!=100; ++i )
			{
				string value;
				form->getRootNode().getValueByName( value, toString( "brick %u", i ).c_str() );
				if ( !value.empty() )
				{	
					strupr( value );
					phrase.push_back( CFile::getFilenameWithoutExtension( value ) );
				}
			}

			Phrases.insert( make_pair(CFile::getFilenameWithoutExtension( *ip ), phrase) );

			// look if phrase is castable
			bool castable;
			form->getRootNode().getValueByName( castable, "castable");
			PhraseCastable.insert( make_pair(CFile::getFilenameWithoutExtension( *ip ), castable) );

			// Test grammar and produce doc
			testPhraseGrammarAndProduceDoc( CFile::getFilenameWithoutExtension( *ip ), phrase );

			++NbSheetsRead;
		}
	}
	nlinfo( "Total: %u phrases", NbSheetsRead );

} // produceDocFromExistingPhrases //


/*
 *
 */
string	getLink( const string& phrase )
{
	string res;
	if ( MultipleDocFiles && (! phrase.empty()) )
	{
		res += DocFileName + "_" + getDocFileLetter( phrase ) + ".html";
	}
	else
	{
		res += DocFileName + ".html";
	}
	res += "#" + phrase;
	//nlinfo( "%s", res.c_str() );
	return res;
}


/*
 *
 */
void	usage(char *argv0, FILE *out)
{
	fprintf(out, "\n");
	fprintf(out, "Syntax: %s [-p <sheet path>] <bricksFilename> [-o <phrasePath>] [-b] [-d] [-m] [-n]\n", argv0);
	fprintf(out, "-o: output phrase path (or input if -d is set)\n");
	fprintf(out, "-b: produce doc about brick learning infos\n");
	fprintf(out, "-d: browse existing phrases in <phrasePath> (and subdirs) and produce doc\n");
	fprintf(out, "-m: multiple doc html files, alphabetically (use with -g,-c,-d with numerous phrases)\n");
	fprintf(out, "-n: no hypertext (don't produce links phrases)\n");
	
	fprintf(out, "\n");
}




//-----------------------------------------------
//	makeIndexFile
//
//-----------------------------------------------
void makeIndexFile()
{
	FILE * indexFile = nlfopen( "_" + DocFileNameRoot + "_INDEX.html", "wt" );
	if( indexFile )
	{
		fprintf( indexFile, ("<html><head>\n<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n<title>Summary of " + DocFileNameRoot + "</title>\n</head><body>\n").c_str() );
	
		DocFileName = DocFileNameRoot + "_actions";
	
		if ( MultipleDocFiles )
		{
			// One HTML file per alphabet letter
			for ( uint l=0; l!=26; ++l )
			{
				string filename = toString( "%s_%c.html", DocFileName.c_str(), 'a'+l );
				PhraseDocFiles[l] = nlfopen( filename, "wt" );
				fprintf( PhraseDocFiles[l], ("<html><head>\n<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n<title>" + DocFileName + toString( " - %c", (char)('A'+l) ) + "</title>\n</head><body>\n").c_str() );
				fprintf( indexFile, ("<A HREF=\"" + filename + "\">" + (char)('A'+l) + "</A> ").c_str() );
			}
		}
		else
		{
			// One single HTML file
			fprintf( indexFile, ("<A HREF=\"" + DocFileName + ".html\">Go to action details</A>").c_str() );
			PhraseDocFiles[0] = nlfopen( DocFileName + ".html", "wt" );
			fprintf( PhraseDocFiles[0], ("<html><head>\n<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n<title>" + DocFileName + "</title>\n</head><body>\n").c_str() );
			for ( uint l=1; l!=26; ++l )
			{
				PhraseDocFiles[l] = PhraseDocFiles[0];
			}
		}
		fprintf( indexFile, ("<BR><A HREF=\"" + DocFileName + "__by_skill_value.html" + "\">Go to action by skill value</A>\n").c_str() );
		fprintf( indexFile, ("<BR><A HREF=\"" + DocFileName + "__by_skill_value_detail.html" + "\">Go to action by skill value (detail)</A>\n").c_str() );
		fprintf( indexFile, ("<BR><A HREF=\"" + DocFileName + "__by_skill.html" + "\">Go to action by skill</A><BR>\n").c_str() );

		if( GenerateBrickProgression )
		{
			fprintf( indexFile, ("<BR><BR><A HREF=\"" + DocFileNameRoot + ".html" + "\">Go to brick list</A><BR>\n").c_str() );
		}
				
		produceDocFromExistingPhrases();

		for ( map< uint, pair<string, string> >::const_iterator ip=Progression.begin(); ip!=Progression.end(); ++ip )
		{
			const string& phraseCode = (*ip).second.first;
			string link = Hypertext ? toString( "<A HREF=\"%s\">%s</A>", getLink(phraseCode).c_str(), phraseCode.c_str() ) : "<B>" + phraseCode + "</B>";
			PhraseCodeToLink.insert( make_pair(phraseCode,link) );
		}

		// Summary (errors in phrases)
		fprintf( indexFile, "<BR><A NAME=\"summary\"></A>\n" );
		fprintf( indexFile, ("<FONT SIZE=\"20\">Summary of " + DocFileName + "</FONT><BR>\n").c_str() );
		if ( NbSheetsGenTries != 0 )
			fprintf( indexFile, "<P>%u valid sheets written on %u</P>\n", NbSheetsWritten, NbSheetsGenTries );
		if ( NbSheetsRead != 0 )
			fprintf( indexFile, "<P>%u sheets read</P>\n", NbSheetsRead );
		fprintf( indexFile, "<P>%u invalid sheets rejected", NbSheetsRejected );
		if ( ! PhrasesWithInvalidCost.empty() )
		{
			fprintf( indexFile, "<P><B>Phrases with invalid sabrina cost:</B><BR>\n" );
			for ( vs::const_iterator iip=PhrasesWithInvalidCost.begin(); iip!=PhrasesWithInvalidCost.end(); ++iip )
			{
				string link = Hypertext ? toString( "<A HREF=\"%s\">%s</A>", getLink(*iip).c_str(), (*iip).c_str() ) : "<B>" + (*iip) + "</B>";
				fprintf( indexFile, "%s<BR>\n", link.c_str() );
			}
			fprintf( indexFile, "</P>\n" );
		}
		else
		{
			fprintf( indexFile, "<P><B>All phrases have valid sabrina cost.</B></P>\n" );
		}
		if ( ! InvalidPhrases.empty() )
		{
			fprintf( indexFile, "<P><B>Grammatically invalid phrases:</B><BR>\n" );
			for ( vs::const_iterator iip=InvalidPhrases.begin(); iip!=InvalidPhrases.end(); ++iip )
			{
				string link = Hypertext ? toString( "<A HREF=\"%s\">%s</A>", getLink(*iip).c_str(), (*iip).c_str() ) : "<B>" + (*iip) + "</B>";
				fprintf( indexFile, "%s<BR>\n", link.c_str() );
			}
			fprintf( indexFile, "</P>\n" );
		}
		else
		{
			fprintf( indexFile, "<P><B>All phrases are grammatically valid.</B></P>\n" );
		}
		fprintf( indexFile, "</body></html>\n" );
		fclose( indexFile );
		
		if ( MultipleDocFiles )
		{
			for ( uint l=0; l!=26; ++l )
			{
				fprintf( PhraseDocFiles[l], "</body></html>\n" );
				fclose( PhraseDocFiles[l] );
			}
		}
		else
		{
			fprintf( PhraseDocFiles[0], "</body></html>\n" );
			fclose( PhraseDocFiles[0] );
		}
	}

} // makeIndexFile //


//-----------------------------------------------
//	makeActionsBySkillGroupFile
//
//-----------------------------------------------
void makeActionsBySkillGroupFile()
{
	// progression by skill
	FILE * actionsBySkillGroupFile = nlfopen( DocFileName + "__by_skill.html", "wt" );
	if( actionsBySkillGroupFile )
	{
		fprintf( actionsBySkillGroupFile, ("<html><head>\n<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n<title>Progression of " + DocFileName + "</title>\n</head><body>\n").c_str() );
		fprintf( actionsBySkillGroupFile, "<BR><A NAME=\"by_skill_group\"></A>\n" );
		fprintf( actionsBySkillGroupFile, "<P><B>ACTIONS BY SKILL GROUP:</B><BR>\n<table cellpadding=\"0\" cellspacing=\"1\" border=\"0\" style=\"text-align: left;\"><tbody>\n" );
		map<string, multimap<uint,string> > phrasesBySkill;
		for ( map< uint, pair<string, string> >::const_iterator ip=Progression.begin(); ip!=Progression.end(); ++ip )
		{
			const string& phraseCode = (*ip).second.first;
			string skillName = (*ip).second.second.substr(0,(*ip).second.second.find(" "));
			string skillValueStr = (*ip).second.second.substr((*ip).second.second.find(" ")+1,(*ip).second.second.size()-(*ip).second.second.find(" ")-1);
			uint skillValue = atoi(skillValueStr.c_str());
			
			map<string, multimap<uint,string> >::iterator it = phrasesBySkill.find(skillName);
			if( it != phrasesBySkill.end() )
			{
				(*it).second.insert(make_pair(skillValue,phraseCode));
			}
			else
			{
				multimap<uint,string> m;
				m.insert(make_pair(skillValue,phraseCode));
				phrasesBySkill.insert( make_pair(skillName,m) );
			}
		}

		map<string, multimap<uint,string> >::iterator itPhrasesBySkill;
		for( itPhrasesBySkill = phrasesBySkill.begin(); itPhrasesBySkill != phrasesBySkill.end(); ++itPhrasesBySkill )
		{
			CVectorSString dicoResult;
			Dico.lookup( (*itPhrasesBySkill).first, dicoResult, true );
			if( !dicoResult.empty() )
				fprintf( actionsBySkillGroupFile, "<tr><td><A HREF=\"#%s\">%s</A></td></tr>\n", (*itPhrasesBySkill).first.c_str(),dicoResult[0].c_str());
			else
				fprintf( actionsBySkillGroupFile, "<tr><td><A HREF=\"#%s\">%s</A></td></tr>\n", (*itPhrasesBySkill).first.c_str(),(*itPhrasesBySkill).first.c_str());
		}
		for( itPhrasesBySkill = phrasesBySkill.begin(); itPhrasesBySkill != phrasesBySkill.end(); ++itPhrasesBySkill )
		{
			CVectorSString dicoResult;
			Dico.lookup( (*itPhrasesBySkill).first, dicoResult, true );
			if( !dicoResult.empty() )
				fprintf( actionsBySkillGroupFile, "<tr><td><A NAME=\"%s\"><B>%s</B></A><BR></td></tr>\n", (*itPhrasesBySkill).first.c_str(), dicoResult[0].c_str() );
			else
				fprintf( actionsBySkillGroupFile, "<tr><td><A NAME=\"%s\"><B>%s</B></A><BR></td></tr>\n", (*itPhrasesBySkill).first.c_str(),(*itPhrasesBySkill).first.c_str() );
			
			multimap<uint,string>::iterator it;
			for( it = (*itPhrasesBySkill).second.begin(); it != (*itPhrasesBySkill).second.end(); ++it )
			{
				fprintf( actionsBySkillGroupFile, "<tr><td>%d</td><td>%s</td><td>%s<BR></td></tr>\n", (*it).first, PhraseCodeToLink[(*it).second].c_str(), PhraseTitles[(*it).second].c_str());
			}
		}
		fprintf( actionsBySkillGroupFile, "</tbody><table></P>\n" );
		fprintf( actionsBySkillGroupFile, "</body></html>\n" );
		fclose( actionsBySkillGroupFile );
	}

} // makeActionsBySkillGroupFile //



//-----------------------------------------------
//	makeActionsBySkillValueFile
//
//-----------------------------------------------
void makeActionsBySkillValueFile()
{
	FILE * actionsBySkillValueFile = fopen( (DocFileName + "__by_skill_value.html").c_str(), "wt" );
	if( actionsBySkillValueFile )
	{
		fprintf( actionsBySkillValueFile, ("<html><head>\n<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n<title>Progression of " + DocFileName + "</title>\n</head><body>\n").c_str() );

		// Progression (phrases sorted by skill value)
		fprintf( actionsBySkillValueFile, "<BR><A NAME=\"by_skill_value\"></A>\n" );
		fprintf( actionsBySkillValueFile, "<P><B>ACTIONS BY SKILL VALUE: <A HREF=\"%s\">[detail]</A></B>\n<table cellpadding=\"0\" cellspacing=\"1\" border=\"0\" style=\"text-align: left;\"><tbody>\n",(DocFileName + "__by_skill_value_detail.html").c_str() );
		fprintf( actionsBySkillValueFile, "<tr><td><B>File</B></td><td><B>Name</B></td><td><B>Skill needed</B><BR></td></tr>\n");
		map<string,string> phraseCodeToLink;
		for ( map< uint, pair<string, string> >::const_iterator ip=Progression.begin(); ip!=Progression.end(); ++ip )
		{
			const string& phraseCode = (*ip).second.first;
			fprintf( actionsBySkillValueFile, "<tr><td><font size=2>%s</font></td><td><font size=2>%s</font></td><td><B><font size=2>%s</font></B><BR></td></tr>\n", PhraseCodeToLink[phraseCode].c_str(), /*newbrickTitle.c_str(),*/ PhraseTitles[phraseCode].c_str(), (*ip).second.second.c_str() );
		}
		fprintf( actionsBySkillValueFile, "</tbody><table></P>\n" );
		fprintf( actionsBySkillValueFile, "</body></html>\n" );
		fclose( actionsBySkillValueFile );
	}

} // makeActionsBySkillValueFile //


//-----------------------------------------------
//	makeActionsBySkillValueDetailFile
//
//-----------------------------------------------
void makeActionsBySkillValueDetailFile()
{
	FILE * actionsBySkillValueDetailFile = nlfopen( DocFileName + "__by_skill_value_detail.html", "wt" );
	if( actionsBySkillValueDetailFile )
	{
		fprintf( actionsBySkillValueDetailFile, ("<html><head>\n<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n<title>Progression of " + DocFileName + "</title>\n</head><body>\n").c_str() );

		// Progression summary (phrases sorted by skill value)
		fprintf( actionsBySkillValueDetailFile, "<BR><A NAME=\"progression\"></A>\n" );
		fprintf( actionsBySkillValueDetailFile, "<P><B>ACTIONS BY SKILL VALUE:</B><BR>\n<table cellpadding=\"0\" cellspacing=\"1\" border=\"0\" style=\"text-align: left;\"><tbody>\n" );
		fprintf( actionsBySkillValueDetailFile, "<tr><td><B>File</B></td><td><B>Name</B></td><td><B>Skill needed</B><BR></td><td><B>Sabrina cost</B></td><td><B>Bricks ...</B></td></tr>\n");
		
		set<string> effects;
		map<string,set<string> > effectAndModifiers;
		for ( map< uint, pair<string, string> >::const_iterator ip=Progression.begin(); ip!=Progression.end(); ++ip )
		{
			const string& phraseCode = (*ip).second.first;
			fprintf( actionsBySkillValueDetailFile, "<tr><td><font size=2>%s</font></td><td><font size=2>%s</font></td><td><B><font size=2>%s</font></B></td><td><font size=2>%d</font></td>",PhraseCodeToLink[phraseCode].c_str(), PhraseTitles[phraseCode].c_str(), (*ip).second.second.c_str(),PhraseSabrinaCosts[phraseCode]);
						
			msvs::iterator itPhrases = Phrases.find( phraseCode );
			if( itPhrases != Phrases.end() )
			{
				string effect;
				uint modifierCount = 0;
				uint creditCount = 0;
				for( uint i = 0; i<(*itPhrases).second.size(); ++i )
				{
					string brick = (*itPhrases).second[i];
					string color;
					switch ( brick[brick.size()-getBrickTypeLetterRPos(brick)] )
					{
					case 'P': color = "Black";
						break;
					case 'E': 
						{
							color = "Brown"; 
							effects.insert(brick);
							if( effectAndModifiers.find(brick) == effectAndModifiers.end() )
							{
								set<string> s;
								effectAndModifiers.insert( make_pair(brick,s) );
							}
							effect = brick;
						}
						break;
					case 'O': color = "Green";
						break;
					case 'M': 
						{
							color = "Blue";
							effectAndModifiers[effect].insert(brick);
							modifierCount++;
						}
						break;
					case 'C': color = "Red"; creditCount++;
						break;
					default:
						color = "Black";
					}
					string text = BrickInfo[brick].Text;
					if( text.empty() )
					{
						text = strlwr(brick);
						nlwarning("%s not found in BrickInfo",brick.c_str());
					}
					else
					{
						if(text.find("$|sap")!=-1)
						{
							text = text.substr(0,text.size()-5);
							string str = brick.substr(brick.size()-5,5);
							text += toString(atoi(str.c_str()));
						}
					}
					fprintf( actionsBySkillValueDetailFile, "<td><FONT COLOR=\"%s\" SIZE=2>%s</FONT></td>",color.c_str(),text.c_str());
				}
			}
			else
			{
				nlerror("not found : %s",phraseCode.c_str());
			}
			fprintf( actionsBySkillValueDetailFile, "</tr>\n");
		}
		fprintf( actionsBySkillValueDetailFile, "</tbody><table></P>\n" );
		fprintf( actionsBySkillValueDetailFile, "</body></html>\n" );
		fclose( actionsBySkillValueDetailFile );
	}

} // makeActionsBySkillValueDetailFile //


//-----------------------------------------------
//		validateBrick
//
//-----------------------------------------------
bool validateBrick( const string& brk )
{
	if(brk[1]=='C') return true;
	if(brk[1]=='F') return true;
	if(brk[1]=='H') return true;
	if(brk[1]=='M') return true;
	if(brk[1]=='S') return true;
	return false;

} // validateBrick //



//-----------------------------------------------
//		makeSkillTreeFile
//
//-----------------------------------------------
void makeSkillTreeFile( char filter, string skillFamily, bool withTraduction )
{
	vector<map<string,uint16> > skillsArray;
	skillsArray.resize(6); // 6 tranches de skill
	uint i;
	for( i = 0; i<SkillsTree.SkillsTree.size(); ++i )
	{
		string skillCode = SkillsTree.SkillsTree[i].SkillCode;
		if( skillCode[1] == filter )
		{
			uint sIdx = skillCode.length()-2; // -1 for 'S', -1 for 0
			skillsArray[sIdx].insert( make_pair(skillCode,SkillsTree.SkillsTree[i].MaxSkillValue) );
		}
	}
	
	uint16 maxLine = 0;
	for( i=0; i<skillsArray.size(); ++i )
	{
		if( skillsArray[i].size() > maxLine )
		{
			maxLine = skillsArray[i].size();
		}
	}
	
	string filename = skillFamily + "_skill_tree.html";
	string filenameWithTraduction = skillFamily + "_skill_tree_detailed.html";
	FILE * skillTreeFile;
	if( withTraduction )
		skillTreeFile = nlfopen( filenameWithTraduction, "wt" );
	else
		skillTreeFile = nlfopen( filename, "wt" );
	fprintf( skillTreeFile,"<html><head>\n");
	fprintf( skillTreeFile,"<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
	fprintf( skillTreeFile,"<title>SKILL TREE ( %s )</title>\n",skillFamily.c_str());
	fprintf( skillTreeFile,"</head><body>\n");
	if( withTraduction )
		fprintf( skillTreeFile,"<b>SKILL TREE ( %s )</b>\n",skillFamily.c_str());
	else
		fprintf( skillTreeFile,"<b>SKILL TREE ( %s )</b>  [<A HREF=\"%s\">display traduction</A>]<BR>\n",skillFamily.c_str(),filenameWithTraduction.c_str());
	fprintf( skillTreeFile,"<table cellpadding=\"2\" cellspacing=\"2\" border=\"0\" style=\"text-align: left;\"><tbody>\n");
	fprintf( skillTreeFile,"<tr><td><b>0 to 20</b></td><td><b>20 to 50</b></td><td><b>50 to 100</b></td><td><b>100 to 150</b></td><td><b>150 to 200</b></td><td><b>200 to 250</b></td></tr>\n");
	
	uint j;
	// print line by line
	for( j=0; j<maxLine; ++j )
	{
		fprintf( skillTreeFile,"<tr>");
		// for each column
		for( i=0; i<skillsArray.size(); ++i )
		{
			uint p;
			map<string,uint16>::iterator itSkillcode;
			for( itSkillcode = skillsArray[i].begin(), p=0; itSkillcode != skillsArray[i].end() && p<j; ++itSkillcode,++p );
			if( itSkillcode != skillsArray[i].end() )
			{
				if( withTraduction )
				{
					CVectorSString dicoResult;
					Dico.lookup( (*itSkillcode).first, dicoResult, true );
					if(dicoResult.empty())
						fprintf( skillTreeFile,"<td>%s : ???</td>",(*itSkillcode).first.c_str());
					else
						fprintf( skillTreeFile,"<td>%s</td>",dicoResult[0].c_str());
				}
				else
					fprintf( skillTreeFile,"<td>%s</td>",(*itSkillcode).first.c_str());
			}
			else
				fprintf( skillTreeFile,"<td></td>");
		}
		fprintf( skillTreeFile,"</tr>\n");
	}
	
	fprintf( skillTreeFile, "</tbody><table></P>\n" );
	fprintf( skillTreeFile, "</body></html>\n" );
	fclose( skillTreeFile );

} // makeSkillTreeFile //





//-----------------------------------------------
//		MAIN
//
//-----------------------------------------------
int main(int argc, char* argv[])
{
	// parse command line
	const char *inputFilename = NULL;
	for ( uint i=1; (sint)i!=argc; i++ )
	{
		const char	*arg = argv[i];
		if ( arg[0] == '-' )
		{
			switch ( arg[1] )
			{
			case 'p':
				++i;
				if ( (sint)i == argc )
				{
					fprintf( stderr, "Missing <sheet path> after -p option\n" );
					usage( argv[0], stderr );
					exit( 0 );
				}
				inputSheetPath = argv[i];
				break;
			case 'o':
				++i;
				if ( (sint)i == argc )
				{
					fprintf( stderr, "Missing <phrasePath> after -o option\n" );
					usage( argv[0], stderr );
					exit( 0 );
				}
				PhrasePath = argv[i];
				if ( PhrasePath[PhrasePath.size()-1] != '/' )
					PhrasePath += '/';
				break;
			case 'b' :
				GenerateBrickProgression = true;
				break;
			case 'd':
				ProduceDocFromExistingPhrases = true;
				break;
			case 'm':
				MultipleDocFiles = true;
				break;
			case 'n':
				Hypertext = false;
				break;
			}
		}
		else
		{
			if ( CFile::getExtension(arg) == "csv" )
			{
				inputFilename = arg;
			}
			else
				nlerror( "Unrecognized extension in %s", arg );

		}
	}

	Dico.init();

	loadSheetPath();
	FormLoader = UFormLoader::createLoader();
	CSheetId::init();
	
	CSheetId skillTreeSheet("skills.skill_tree");
	CSmartPtr<UForm> skillTreeForm = FormLoader->loadForm( "skills.skill_tree" );
	SkillsTree.readGeorges( skillTreeForm, skillTreeSheet );


	makeSkillTreeFile('C',"craft", false);
	makeSkillTreeFile('F',"fight", false);
	makeSkillTreeFile('H',"forage", false);
	makeSkillTreeFile('M',"magic", false);
	
	makeSkillTreeFile('C',"craft", true);
	makeSkillTreeFile('F',"fight", true);
	makeSkillTreeFile('H',"forage", true);
	makeSkillTreeFile('M',"magic", true);
	

	// Load bricks from the csv
	UseBricks = ProduceDocFromExistingPhrases;
	if ( UseBricks )
	{
		if ( ! inputFilename )
		{
			usage( argv[0], stderr );
			exit( 0 );
		}
		loadBricks( inputFilename );
	}


	// Phrases
	if ( ProduceDocFromExistingPhrases )
	{
		loadBrickGrammar();
		loadBrickTitles();
		loadPhraseTitles();

		DocFileNameRoot = toString( "%s", CFile::getFilenameWithoutExtension( inputFilename ).c_str() );

		// index
		makeIndexFile();
				
		// progression by skill
		makeActionsBySkillGroupFile();
		
		// Progression (phrases sorted by skill value)
		makeActionsBySkillValueFile();
		
		// Progression (phrases sorted by skill value + detail)
		makeActionsBySkillValueDetailFile();
		
	}

	
	if( GenerateBrickProgression )
	{
		map<uint,map<string,set<string> > > levelToBrick;

		map<string,string> phraseToSkill;

		for ( map< uint, pair<string, string> >::const_iterator ip=Progression.begin(); ip!=Progression.end(); ++ip )
		{
			const string& phraseCode = (*ip).second.first;
					
			string skillTmp = (*ip).second.second.c_str();

			phraseToSkill.insert( make_pair(phraseCode,skillTmp) );

			if(skillTmp.empty()==false)
			{
				// get skill
				string skill = skillTmp.substr(0,skillTmp.find_first_of(" "));

				// get level
				string levelStr;
				if( skillTmp.find(";") != -1 )
				{
					sint idx = skillTmp.find_first_of(" ");
					levelStr = skillTmp.substr(idx+1,skillTmp.find_first_of(";")-idx-1);	
				}
				else
				{
					sint idx = skillTmp.find_first_of(" ");
					levelStr = skillTmp.substr(idx+1,skillTmp.size()-idx);
				}
				if(levelStr.find(".")!=-1) levelStr = levelStr.substr(0,levelStr.size()-1);
				uint level = atoi(levelStr.c_str());


				map<uint,map<string,set<string> > >::iterator itLvl = levelToBrick.find(level);
				if( itLvl == levelToBrick.end() )
				{
					set<string> s;
					map<string,set<string> > mp;
					mp.insert(make_pair(skill,s));
					levelToBrick.insert(make_pair(level,mp));
				}
				else
				{
					if( (*itLvl).second.find(skill) == (*itLvl).second.end() )
					{
						set<string> s;
						(*itLvl).second.insert( make_pair(skill,s) );
					}
				}
				
				msvs::iterator itPhrases = Phrases.find( phraseCode );
				if( itPhrases != Phrases.end() )
				{
					string effect;
					for( uint i = 0; i<(*itPhrases).second.size(); ++i )
					{
						string brick = (*itPhrases).second[i];
												
						if( levelToBrick[level][skill].find(brick) == levelToBrick[level][skill].end() )
						{
							levelToBrick[level][skill].insert(brick);
						}
					}
				}
			}
		}




		// get family & color
		map<string,string> brickToColor;
		map<string,string> brickToFamily;
		map<string, CBrickInfo>::iterator itBInf;
		for( itBInf=BrickInfo.begin(); itBInf!=BrickInfo.end(); ++itBInf )
		{
			string brk = (*itBInf).first;

			if(!validateBrick(brk)) continue;
			
			string color;
			string family;
			if( brk[brk.size()-getBrickTypeLetterRPos(brk)] =='M' )
			{
				color = "Blue";
				family = "Modifier";
			}
			if( brk[brk.size()-getBrickTypeLetterRPos(brk)] =='C' )
			{
				color = "Red";
				family = "Credit";
			}
			if( brk[brk.size()-getBrickTypeLetterRPos(brk)] =='O' )
			{
				color = "Green";
				family = "Option";
			}
			if( brk[brk.size()-getBrickTypeLetterRPos(brk)] =='P' )
			{
				color = "Black";
				family = "Root";
			}
			if( brk[brk.size()-getBrickTypeLetterRPos(brk)] =='E' )
			{
				color = "Brown";
				family = "Effect";
			}
			
			brickToColor.insert(make_pair(brk,color));
			brickToFamily.insert(make_pair(brk,family));
		}

		// get phrases where the brick can be found
		map<string,map<string,string> > brickToPhrases;
		msvs::iterator itPhrases;
		for( itPhrases=Phrases.begin(); itPhrases!=Phrases.end(); ++itPhrases )
		{
			for( uint i = 0; i<(*itPhrases).second.size(); ++i )
			{
				string brick = (*itPhrases).second[i];
				if( brickToPhrases.find(brick)==brickToPhrases.end() )
				{
					map<string,string> m;
					
					m.insert(make_pair((*itPhrases).first,phraseToSkill[(*itPhrases).first]));
					brickToPhrases.insert(make_pair(brick,m));
				}
				else
				{
					brickToPhrases[brick].insert(make_pair((*itPhrases).first,phraseToSkill[(*itPhrases).first]));
				}
			}
		}

		// get skill when a brick is learnt
		map<string,string> brickToLearnSkill;
		map<string,map<string,string> >::iterator itLearn;
		for( itLearn=brickToPhrases.begin(); itLearn!=brickToPhrases.end(); ++itLearn )
		{
			string minSkill;
			uint minLevel = 250;

			mss::iterator itPh;
			for( itPh=(*itLearn).second.begin(); itPh!=(*itLearn).second.end(); ++itPh )
			{
				string skillTmp = (*itPh).second;
				string levelStr;
				if( skillTmp.find(";") != -1 )
				{
					sint idx = skillTmp.find_first_of(" ");
					levelStr = skillTmp.substr(idx+1,skillTmp.find_first_of(";")-idx-1);	
				}
				else
				{
					sint idx = skillTmp.find_first_of(" ");
					levelStr = skillTmp.substr(idx+1,skillTmp.size()-idx);
				}
				uint level = atoi(levelStr.c_str());

				if( level<minLevel || minSkill.empty() )
				{
					minSkill = skillTmp;
					minLevel = level;
				}
			}

			brickToLearnSkill.insert(make_pair((*itLearn).first,minSkill));
		}

		// PHRASES

		// write header and title bar
		string filename;
		filename = DocFileNameRoot + "_m.html";
		FILE * brickPhraseDocFile_m = nlfopen( filename, "wt" );
		fprintf( brickPhraseDocFile_m,"<html><head>\n");
		fprintf( brickPhraseDocFile_m,"<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
		fprintf( brickPhraseDocFile_m,"<title>Brick phrases</title>\n");
		fprintf( brickPhraseDocFile_m,"</head><body>\n");
		fprintf( brickPhraseDocFile_m,"<table cellpadding=\"0\" cellspacing=\"1\" border=\"0\" style=\"text-align: left;\"><tbody>\n");

		filename = DocFileNameRoot + "_c.html";
		FILE * brickPhraseDocFile_c = nlfopen( filename, "wt" );
		fprintf( brickPhraseDocFile_c,"<html><head>\n");
		fprintf( brickPhraseDocFile_c,"<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
		fprintf( brickPhraseDocFile_c,"<title>Brick phrases</title>\n");
		fprintf( brickPhraseDocFile_c,"</head><body>\n");
		fprintf( brickPhraseDocFile_c,"<table cellpadding=\"0\" cellspacing=\"1\" border=\"0\" style=\"text-align: left;\"><tbody>\n");

		filename = DocFileNameRoot + "_o.html";
		FILE * brickPhraseDocFile_o = nlfopen( filename, "wt" );
		fprintf( brickPhraseDocFile_o,"<html><head>\n");
		fprintf( brickPhraseDocFile_o,"<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
		fprintf( brickPhraseDocFile_o,"<title>Brick phrases</title>\n");
		fprintf( brickPhraseDocFile_o,"</head><body>\n");
		fprintf( brickPhraseDocFile_o,"<table cellpadding=\"0\" cellspacing=\"1\" border=\"0\" style=\"text-align: left;\"><tbody>\n");

		filename = DocFileNameRoot + "_p.html";
		FILE * brickPhraseDocFile_p = nlfopen( filename, "wt" );
		fprintf( brickPhraseDocFile_p,"<html><head>\n");
		fprintf( brickPhraseDocFile_p,"<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
		fprintf( brickPhraseDocFile_p,"<title>Brick phrases</title>\n");
		fprintf( brickPhraseDocFile_p,"</head><body>\n");
		fprintf( brickPhraseDocFile_p,"<table cellpadding=\"0\" cellspacing=\"1\" border=\"0\" style=\"text-align: left;\"><tbody>\n");

		filename = DocFileNameRoot + "_e.html";
		FILE * brickPhraseDocFile_e = nlfopen( filename, "wt" );
		fprintf( brickPhraseDocFile_e,"<html><head>\n");
		fprintf( brickPhraseDocFile_e,"<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
		fprintf( brickPhraseDocFile_e,"<title>Brick phrases</title>\n");
		fprintf( brickPhraseDocFile_e,"</head><body>\n");
		fprintf( brickPhraseDocFile_e,"<table cellpadding=\"0\" cellspacing=\"1\" border=\"0\" style=\"text-align: left;\"><tbody>\n");

		for( itBInf=BrickInfo.begin(); itBInf!=BrickInfo.end(); ++itBInf )
		{
			string brk = (*itBInf).first;
			
			if(!validateBrick(brk)) continue;
			
			string code = strlwr(brk.c_str());

			if(brickToFamily[brk]=="Modifier")
				fprintf( brickPhraseDocFile_m,"<tr><td><A NAME=\"%s\"><FONT COLOR=\"Blue\">%s</FONT></A></td><td></td></tr>\n",brk.c_str(),code.c_str());
			if(brickToFamily[brk]=="Credit")
				fprintf( brickPhraseDocFile_c,"<tr><td><A NAME=\"%s\"><FONT COLOR=\"Red\">%s</FONT></A></td><td></td></tr>\n",brk.c_str(),code.c_str());
			if(brickToFamily[brk]=="Option")
				fprintf( brickPhraseDocFile_o,"<tr><td><A NAME=\"%s\"><FONT COLOR=\"Green\">%s</FONT></A></td><td></td></tr>\n",brk.c_str(),code.c_str());
			if(brickToFamily[brk]=="Effect")
				fprintf( brickPhraseDocFile_e,"<tr><td><A NAME=\"%s\"><FONT COLOR=\"Brown\">%s</FONT></A></td><td></td></tr>\n",brk.c_str(),code.c_str());
			if(brickToFamily[brk]=="Root")
				fprintf( brickPhraseDocFile_p,"<tr><td><A NAME=\"%s\"><FONT COLOR=\"Black\">%s</FONT></A></td><td></td></tr>\n",brk.c_str(),code.c_str());
			
			map<string,map<string,string> >::iterator itPhrases = brickToPhrases.find(brk);
			if( itPhrases != brickToPhrases.end() )
			{
				map<string,string>::iterator itPh;
				for( itPh=(*itPhrases).second.begin(); itPh!=(*itPhrases).second.end(); ++itPh )
				{
					if(brickToFamily[brk]=="Modifier")
						fprintf( brickPhraseDocFile_m,"<tr><td></td><td><A HREF=\"%s_%c.html#%s\">%s</A></td></tr>\n",DocFileName.c_str(),(*itPh).first[7],(*itPh).first.c_str(),(*itPh).first.c_str());
					if(brickToFamily[brk]=="Credit")
						fprintf( brickPhraseDocFile_c,"<tr><td></td><td><A HREF=\"%s_%c.html#%s\">%s</A></td></tr>\n",DocFileName.c_str(),(*itPh).first[7],(*itPh).first.c_str(),(*itPh).first.c_str());
					if(brickToFamily[brk]=="Option")
						fprintf( brickPhraseDocFile_o,"<tr><td></td><td><A HREF=\"%s_%c.html#%s\">%s</A></td></tr>\n",DocFileName.c_str(),(*itPh).first[7],(*itPh).first.c_str(),(*itPh).first.c_str());
					if(brickToFamily[brk]=="Effect")
						fprintf( brickPhraseDocFile_e,"<tr><td></td><td><A HREF=\"%s_%c.html#%s\">%s</A></td></tr>\n",DocFileName.c_str(),(*itPh).first[7],(*itPh).first.c_str(),(*itPh).first.c_str());
					if(brickToFamily[brk]=="Root")
						fprintf( brickPhraseDocFile_p,"<tr><td></td><td><A HREF=\"%s_%c.html#%s\">%s</A></td></tr>\n",DocFileName.c_str(),(*itPh).first[7],(*itPh).first.c_str(),(*itPh).first.c_str());
				}
			}
		}
		fprintf( brickPhraseDocFile_m, "</tbody><table></P>\n" );
		fprintf( brickPhraseDocFile_m, "</body></html>\n" );
		fclose( brickPhraseDocFile_m );

		fprintf( brickPhraseDocFile_c, "</tbody><table></P>\n" );
		fprintf( brickPhraseDocFile_c, "</body></html>\n" );
		fclose( brickPhraseDocFile_c );

		fprintf( brickPhraseDocFile_o, "</tbody><table></P>\n" );
		fprintf( brickPhraseDocFile_o, "</body></html>\n" );
		fclose( brickPhraseDocFile_o );

		fprintf( brickPhraseDocFile_e, "</tbody><table></P>\n" );
		fprintf( brickPhraseDocFile_e, "</body></html>\n" );
		fclose( brickPhraseDocFile_e );

		fprintf( brickPhraseDocFile_p, "</tbody><table></P>\n" );
		fprintf( brickPhraseDocFile_p, "</body></html>\n" );
		fclose( brickPhraseDocFile_p );


		// CODE

		// write header and title bar
		filename = DocFileNameRoot + ".html";
		FILE * brickDocFile = nlfopen( filename, "wt" );
		fprintf( brickDocFile,"<html><head>\n");
		fprintf( brickDocFile,"<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
		fprintf( brickDocFile,"<title>Bricks infos</title>\n");
		fprintf( brickDocFile,"</head><body>\n");
		fprintf( brickDocFile,"<table cellpadding=\"1\" cellspacing=\"1\" border=\"0\"><tbody>\n");
		fprintf( brickDocFile,"<tr>\n");
		fprintf( brickDocFile,"<td><b>*Code*</b></td>\n");
		fprintf( brickDocFile,"<td><b><a href=\"%s_name.html\">Name</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickDocFile,"<td><b><a href=\"%s_family.html\">Family</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickDocFile,"<td><b>Required Skill Name</b></td>\n");
		fprintf( brickDocFile,"<td><b><a href=\"%s_required_skill_value.html\">Required Skill Value</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickDocFile,"<td><b>Learn Skill Name</b></td>\n");
		fprintf( brickDocFile,"<td><b><a href=\"%s_learn_skill_value.html\">Learn Skill Value</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickDocFile,"<td><b>Found In Phrases</b></td>\n");
		fprintf( brickDocFile,"</tr>\n");


		// write infos
		for( itBInf=BrickInfo.begin(); itBInf!=BrickInfo.end(); ++itBInf )
		{
			string brk = (*itBInf).first;

			if(!validateBrick(brk)) continue;
			
			string skillTmp = (*itBInf).second.LearnSkills;
			string skill = skillTmp.substr(0,skillTmp.find_first_of(" "));
			CVectorSString dicoResult;
			Dico.lookup( skill, dicoResult, true );
			if(dicoResult.empty()) continue;
			
			// color
			string color = brickToColor[brk];

			// code
			string code = strlwr(brk.c_str());
			fprintf( brickDocFile, "<tr><td><FONT COLOR=\"%s\">%s</FONT></td>\n",color.c_str(),code.c_str());
			
			// name
			string name = (*itBInf).second.Text;
			fprintf( brickDocFile, "<td>%s</td>\n",name.c_str());

			// family
			string family = brickToFamily[brk];
			fprintf( brickDocFile, "<td><FONT COLOR=\"%s\">%s</FONT></td>\n",color.c_str(),family.c_str());

			// required skill name
			fprintf( brickDocFile, "<td>%s</td>\n",dicoResult[0].c_str());
			
			// required skill value
			string levelStr;
			if( skillTmp.find(";") != -1 )
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.find_first_of(";")-idx-1);	
			}
			else
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.size()-idx);
			}
			fprintf( brickDocFile, "<td>%s</td>\n",levelStr.c_str());

			// learn skill name
			string learnSkillTmp = brickToLearnSkill[brk];
			skill = learnSkillTmp.substr(0,learnSkillTmp.find_first_of(" "));
			fprintf( brickDocFile, "<td>%s</td>\n",skill.c_str());

			// learn skill value
			if( learnSkillTmp.find(";") != -1 )
			{
				sint idx = learnSkillTmp.find_first_of(" ");
				levelStr = learnSkillTmp.substr(idx+1,learnSkillTmp.find_first_of(";")-idx-1);	
			}
			else
			{
				sint idx = learnSkillTmp.find_first_of(" ");
				levelStr = learnSkillTmp.substr(idx+1,learnSkillTmp.size()-idx);
			}
			fprintf( brickDocFile, "<td>%s</td>\n",levelStr.c_str());


			// phrase list
			fprintf( brickDocFile, "<td>");
			map<string,map<string,string> >::iterator itPhrases = brickToPhrases.find(brk);
			if( itPhrases != brickToPhrases.end() )
			{
				map<string,string>::iterator itPh;
				uint i;
				for( itPh=(*itPhrases).second.begin(),i=0; itPh!=(*itPhrases).second.end() && i<2; ++itPh,++i )
				{
					if( MultipleDocFiles )
						fprintf( brickDocFile,"<A HREF=\"%s_%c.html#%s\">%s</A>,&nbsp&nbsp",DocFileName.c_str(),(*itPh).first[7],(*itPh).first.c_str(),(*itPh).first.c_str());
					else
						fprintf( brickDocFile,"<A HREF=\"%s.html#%s\">%s</A>,&nbsp&nbsp",DocFileName.c_str(),(*itPh).first.c_str(),(*itPh).first.c_str());
				}
				if( i==2 )
				{
					char type = family[0];
					fprintf( brickDocFile,"[<A HREF=\"%s_%c.html#%s\">...</A>]",DocFileNameRoot.c_str(),type,brk.c_str());
				}
			}

			fprintf( brickDocFile, "</td></tr>\n");
		}
		fprintf( brickDocFile, "</tbody><table></P>\n" );
		fprintf( brickDocFile, "</body></html>\n" );
		fclose( brickDocFile );



		// NAME

		// write header and title bar
		filename = DocFileNameRoot + "_name.html";
		FILE * brickNameDocFile = nlfopen( filename, "wt" );
		fprintf( brickNameDocFile,"<html><head>\n");
		fprintf( brickNameDocFile,"<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
		fprintf( brickNameDocFile,"<title>Bricks infos</title>\n");
		fprintf( brickNameDocFile,"</head><body>\n");
		fprintf( brickNameDocFile,"<table cellpadding=\"1\" cellspacing=\"1\" border=\"0\"><tbody>\n");
		fprintf( brickNameDocFile,"<tr>\n");
		fprintf( brickNameDocFile,"<td><b><a href=\"%s.html\">Code</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickNameDocFile,"<td><b>*Name*</b></td>\n");
		fprintf( brickNameDocFile,"<td><b><a href=\"%s_family.html\">Family</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickNameDocFile,"<td><b>Required Skill Name</b></td>\n");
		fprintf( brickNameDocFile,"<td><b><a href=\"%s_required_skill_value.html\">Required Skill Value</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickNameDocFile,"<td><b>Learn Skill Name</b></td>\n");
		fprintf( brickNameDocFile,"<td><b><a href=\"%s_learn_skill_value.html\">Learn Skill Value</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickNameDocFile,"<td><b>Found In Phrases</b></td>\n");
		fprintf( brickNameDocFile,"</tr>\n");

		map<string,string> nameToCode;
		for( itBInf=BrickInfo.begin(); itBInf!=BrickInfo.end(); ++itBInf )
		{
			string brk = (*itBInf).first;
			
			if(!validateBrick(brk)) continue;
						
			// code
			string code = strlwr(brk.c_str());
				
			// name
			string name = (*itBInf).second.Text;
			if( !name.empty())
				nameToCode.insert( make_pair(name,brk) );
		}

		mss::iterator itNTC;
		for( itNTC=nameToCode.begin(); itNTC!=nameToCode.end(); ++itNTC )
		{
			itBInf=BrickInfo.find((*itNTC).second);

			string brk = (*itBInf).first;
			
			if(!validateBrick(brk)) continue;
			
			string skillTmp = (*itBInf).second.LearnSkills;
			string skill = skillTmp.substr(0,skillTmp.find_first_of(" "));
			CVectorSString dicoResult;
			Dico.lookup( skill, dicoResult, true );
			if(dicoResult.empty()) continue;
			
			// color
			string color = brickToColor[brk];
			
			// code
			string code = strlwr(brk.c_str());
			fprintf( brickNameDocFile, "<tr><td><FONT COLOR=\"%s\">%s</FONT></td>\n",color.c_str(),code.c_str());

			// name
			string name = (*itBInf).second.Text;
			fprintf( brickNameDocFile, "<td>%s</td>\n",name.c_str());
				
			// family
			string family = brickToFamily[brk];
			fprintf( brickNameDocFile, "<td><FONT COLOR=\"%s\">%s</FONT></td>\n",color.c_str(),family.c_str());
			
			// required skill name
			fprintf( brickNameDocFile, "<td>%s</td>\n",dicoResult[0].c_str());
				
			// required skill value
			string levelStr;
			if( skillTmp.find(";") != -1 )
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.find_first_of(";")-idx-1);	
			}
			else
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.size()-idx);
			}
			fprintf( brickNameDocFile, "<td>%s</td>\n",levelStr.c_str());
			
			// learn skill name
			skillTmp = brickToLearnSkill[brk];
			skill = skillTmp.substr(0,skillTmp.find_first_of(" "));
			fprintf( brickNameDocFile, "<td>%s</td>\n",skill.c_str());
			
			// learn skill value
			if( skillTmp.find(";") != -1 )
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.find_first_of(";")-idx-1);	
			}
			else
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.size()-idx);
			}
			fprintf( brickNameDocFile, "<td>%s</td>\n",levelStr.c_str());
			
			
			// phrase list
			fprintf( brickNameDocFile, "<td>");
			map<string,map<string,string> >::iterator itPhrases = brickToPhrases.find(brk);
			if( itPhrases != brickToPhrases.end() )
			{
				map<string,string>::iterator itPh;
				uint i;
				for( itPh=(*itPhrases).second.begin(),i=0; itPh!=(*itPhrases).second.end() && i<2; ++itPh,++i )
				{
					if( MultipleDocFiles )
						fprintf( brickNameDocFile,"<A HREF=\"%s_%c.html#%s\">%s</A>,&nbsp&nbsp",DocFileName.c_str(),(*itPh).first[7],(*itPh).first.c_str(),(*itPh).first.c_str());
					else
						fprintf( brickNameDocFile,"<A HREF=\"%s.html#%s\">%s</A>,&nbsp&nbsp",DocFileName.c_str(),(*itPh).first.c_str(),(*itPh).first.c_str());
				}
				if( i==2 )
				{
					char type = family[0];
					fprintf( brickNameDocFile,"[<A HREF=\"%s_%c.html#%s\">...</A>]",DocFileNameRoot.c_str(),type,brk.c_str());
				}
			}
			
			fprintf( brickNameDocFile, "</td></tr>\n");

		}

		fprintf( brickNameDocFile, "</tbody><table></P>\n" );
		fprintf( brickNameDocFile, "</body></html>\n" );
		fclose( brickNameDocFile );




		// FAMILY

		// write header and title bar
		filename = DocFileNameRoot + "_family.html";
		FILE * brickFamilyDocFile = nlfopen( filename, "wt" );
		fprintf( brickFamilyDocFile,"<html><head>\n");
		fprintf( brickFamilyDocFile,"<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
		fprintf( brickFamilyDocFile,"<title>Bricks infos</title>\n");
		fprintf( brickFamilyDocFile,"</head><body>\n");
		fprintf( brickFamilyDocFile,"<table cellpadding=\"1\" cellspacing=\"1\" border=\"0\"><tbody>\n");
		fprintf( brickFamilyDocFile,"<tr>\n");
		fprintf( brickFamilyDocFile,"<td><b><a href=\"%s.html\">Code</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickFamilyDocFile,"<td><b><a href=\"%s_name.html\">Name</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickFamilyDocFile,"<td><b>*Family*</b></td>\n");
		fprintf( brickFamilyDocFile,"<td><b>Required Skill Name</b></td>\n");
		fprintf( brickFamilyDocFile,"<td><b><a href=\"%s_required_skill_value.html\">Required Skill Value</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickFamilyDocFile,"<td><b>Learn Skill Name</b></td>\n");
		fprintf( brickFamilyDocFile,"<td><b><a href=\"%s_learn_skill_value.html\">Learn Skill Value</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickFamilyDocFile,"<td><b>Found In Phrases</b></td>\n");
		fprintf( brickFamilyDocFile,"</tr>\n");


		// write infos
		multimap<string,string> familyToCode;
		for( itBInf=BrickInfo.begin(); itBInf!=BrickInfo.end(); ++itBInf )
		{
			string brk = (*itBInf).first;
			
			if(!validateBrick(brk)) continue;
						
			// family
			string family = brickToFamily[brk];
			
			familyToCode.insert( make_pair(family,brk) );
		}

		multimap<string,string>::iterator itFTC;
		for( itFTC=familyToCode.begin(); itFTC!=familyToCode.end(); ++itFTC )
		{
			itBInf=BrickInfo.find((*itFTC).second);
			
			string brk = (*itBInf).first;

			if(!validateBrick(brk)) continue;
			
			string skillTmp = (*itBInf).second.LearnSkills;
			string skill = skillTmp.substr(0,skillTmp.find_first_of(" "));
			CVectorSString dicoResult;
			Dico.lookup( skill, dicoResult, true );
			if(dicoResult.empty()) continue;

			// color
			string color = brickToColor[brk];
			
			// code
			string code = strlwr(brk.c_str());
			fprintf( brickFamilyDocFile, "<tr><td><FONT COLOR=\"%s\">%s</FONT></td>\n",color.c_str(),code.c_str());
			
			// name
			string name = (*itBInf).second.Text;
			fprintf( brickFamilyDocFile, "<td>%s</td>\n",name.c_str());

			// family
			string family = brickToFamily[brk];
			fprintf( brickFamilyDocFile, "<td><FONT COLOR=\"%s\">%s</FONT></td>\n",color.c_str(),family.c_str());

			// required skill name
			fprintf( brickFamilyDocFile, "<td>%s</td>\n",dicoResult[0].c_str());
			
			// required skill value
			string levelStr;
			if( skillTmp.find(";") != -1 )
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.find_first_of(";")-idx-1);	
			}
			else
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.size()-idx);
			}
			fprintf( brickFamilyDocFile, "<td>%s</td>\n",levelStr.c_str());

			// learn skill name
			skillTmp = brickToLearnSkill[brk];
			skill = skillTmp.substr(0,skillTmp.find_first_of(" "));
			fprintf( brickFamilyDocFile, "<td>%s</td>\n",skill.c_str());

			// learn skill value
			if( skillTmp.find(";") != -1 )
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.find_first_of(";")-idx-1);	
			}
			else
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.size()-idx);
			}
			fprintf( brickFamilyDocFile, "<td>%s</td>\n",levelStr.c_str());


			// phrase list
			fprintf( brickFamilyDocFile, "<td>");
			map<string,map<string,string> >::iterator itPhrases = brickToPhrases.find(brk);
			if( itPhrases != brickToPhrases.end() )
			{
				map<string,string>::iterator itPh;
				uint i;
				for( itPh=(*itPhrases).second.begin(),i=0; itPh!=(*itPhrases).second.end() && i<2; ++itPh,++i )
				{
					if( MultipleDocFiles )
						fprintf( brickFamilyDocFile,"<A HREF=\"%s_%c.html#%s\">%s</A>,&nbsp&nbsp",DocFileName.c_str(),(*itPh).first[7],(*itPh).first.c_str(),(*itPh).first.c_str());
					else
						fprintf( brickFamilyDocFile,"<A HREF=\"%s.html#%s\">%s</A>,&nbsp&nbsp",DocFileName.c_str(),(*itPh).first.c_str(),(*itPh).first.c_str());
				}
				if( i==2 )
				{
					char type = family[0];
					fprintf( brickFamilyDocFile,"[<A HREF=\"%s_%c.html#%s\">...</A>]",DocFileNameRoot.c_str(),type,brk.c_str());
				}
			}

			fprintf( brickFamilyDocFile, "</td></tr>\n");
		}
		fprintf( brickFamilyDocFile, "</tbody><table></P>\n" );
		fprintf( brickFamilyDocFile, "</body></html>\n" );
		fclose( brickFamilyDocFile );




		// REQUIRED SKILL VALUE

		// write header and title bar
		filename = DocFileNameRoot + "_required_skill_value.html";
		FILE * brickRequiredDocFile = nlfopen( filename, "wt" );
		fprintf( brickRequiredDocFile,"<html><head>\n");
		fprintf( brickRequiredDocFile,"<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
		fprintf( brickRequiredDocFile,"<title>Bricks infos</title>\n");
		fprintf( brickRequiredDocFile,"</head><body>\n");
		fprintf( brickRequiredDocFile,"<table cellpadding=\"1\" cellspacing=\"1\" border=\"0\"><tbody>\n");
		fprintf( brickRequiredDocFile,"<tr>\n");
		fprintf( brickRequiredDocFile,"<td><b><a href=\"%s.html\">Code</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickRequiredDocFile,"<td><b><a href=\"%s_name.html\">Name</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickRequiredDocFile,"<td><b><a href=\"%s_family.html\">Family</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickRequiredDocFile,"<td><b>Required Skill Name</b></td>\n");
		fprintf( brickRequiredDocFile,"<td><b>*Required Skill Value*</b></td>\n");
		fprintf( brickRequiredDocFile,"<td><b>Learn Skill Name</b></td>\n");
		fprintf( brickRequiredDocFile,"<td><b><a href=\"%s_learn_skill_value.html\">Learn Skill Value</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickRequiredDocFile,"<td><b>Found In Phrases</b></td>\n");
		fprintf( brickRequiredDocFile,"</tr>\n");


		// write infos
		multimap<uint,string> requiredSkillValueToCode;
		for( itBInf=BrickInfo.begin(); itBInf!=BrickInfo.end(); ++itBInf )
		{
			string brk = (*itBInf).first;
			
			if(!validateBrick(brk)) continue;
						
			// required skill value
			string skillTmp = (*itBInf).second.LearnSkills;
			string levelStr;
			if( skillTmp.find(";") != -1 )
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.find_first_of(";")-idx-1);	
			}
			else
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.size()-idx);
			}
			uint level = atoi(levelStr.c_str());
				
			requiredSkillValueToCode.insert( make_pair(level,brk) );
		}

		multimap<uint,string>::iterator itRTC;
		for( itRTC=requiredSkillValueToCode.begin(); itRTC!=requiredSkillValueToCode.end(); ++itRTC )
		{
			itBInf=BrickInfo.find((*itRTC).second);

			string brk = (*itBInf).first;

			if(!validateBrick(brk)) continue;
			
			string skillTmp = (*itBInf).second.LearnSkills;
			string skill = skillTmp.substr(0,skillTmp.find_first_of(" "));
			CVectorSString dicoResult;
			Dico.lookup( skill, dicoResult, true );
			if(dicoResult.empty()) continue;
			
			// color
			string color = brickToColor[brk];

			// code
			string code = strlwr(brk.c_str());
			fprintf( brickRequiredDocFile, "<tr><td><FONT COLOR=\"%s\">%s</FONT></td>\n",color.c_str(),code.c_str());
			
			// name
			string name = (*itBInf).second.Text;
			fprintf( brickRequiredDocFile, "<td>%s</td>\n",name.c_str());

			// family
			string family = brickToFamily[brk];
			fprintf( brickRequiredDocFile, "<td><FONT COLOR=\"%s\">%s</FONT></td>\n",color.c_str(),family.c_str());

			// required skill name
			fprintf( brickRequiredDocFile, "<td>%s</td>\n",dicoResult[0].c_str());
			
			// required skill value
			string levelStr;
			if( skillTmp.find(";") != -1 )
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.find_first_of(";")-idx-1);	
			}
			else
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.size()-idx);
			}
			fprintf( brickRequiredDocFile, "<td>%s</td>\n",levelStr.c_str());

			// learn skill name
			skillTmp = brickToLearnSkill[brk];
			skill = skillTmp.substr(0,skillTmp.find_first_of(" "));
			fprintf( brickRequiredDocFile, "<td>%s</td>\n",skill.c_str());

			// learn skill value
			if( skillTmp.find(";") != -1 )
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.find_first_of(";")-idx-1);	
			}
			else
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.size()-idx);
			}
			fprintf( brickRequiredDocFile, "<td>%s</td>\n",levelStr.c_str());


			// phrase list
			fprintf( brickRequiredDocFile, "<td>");
			map<string,map<string,string> >::iterator itPhrases = brickToPhrases.find(brk);
			if( itPhrases != brickToPhrases.end() )
			{
				map<string,string>::iterator itPh;
				uint i;
				for( itPh=(*itPhrases).second.begin(),i=0; itPh!=(*itPhrases).second.end() && i<2; ++itPh,++i )
				{
					if( MultipleDocFiles )
						fprintf( brickRequiredDocFile,"<A HREF=\"%s_%c.html#%s\">%s</A>,&nbsp&nbsp",DocFileName.c_str(),(*itPh).first[7],(*itPh).first.c_str(),(*itPh).first.c_str());
					else
						fprintf( brickRequiredDocFile,"<A HREF=\"%s.html#%s\">%s</A>,&nbsp&nbsp",DocFileName.c_str(),(*itPh).first.c_str(),(*itPh).first.c_str());
				}
				if( i==2 )
				{
					char type = family[0];
					fprintf( brickRequiredDocFile,"[<A HREF=\"%s_%c.html#%s\">...</A>]",DocFileNameRoot.c_str(),type,brk.c_str());
				}
			}

			fprintf( brickRequiredDocFile, "</td></tr>\n");
		}
		fprintf( brickRequiredDocFile, "</tbody><table></P>\n" );
		fprintf( brickRequiredDocFile, "</body></html>\n" );
		fclose( brickRequiredDocFile );



		// LEARN SKILL VALUE

		// write header and title bar
		filename = DocFileNameRoot + "_learn_skill_value.html";
		FILE * brickLearnDocFile = nlfopen( filename, "wt" );
		fprintf( brickLearnDocFile,"<html><head>\n");
		fprintf( brickLearnDocFile,"<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
		fprintf( brickLearnDocFile,"<title>Bricks infos</title>\n");
		fprintf( brickLearnDocFile,"</head><body>\n");
		fprintf( brickLearnDocFile,"<table cellpadding=\"1\" cellspacing=\"1\" border=\"0\"><tbody>\n");
		fprintf( brickLearnDocFile,"<tr>\n");
		fprintf( brickLearnDocFile,"<td><b><a href=\"%s.html\">Code</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickLearnDocFile,"<td><b><a href=\"%s_name.html\">Name</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickLearnDocFile,"<td><b><a href=\"%s_family.html\">Family</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickLearnDocFile,"<td><b>Required Skill Name</b></td>\n");
		fprintf( brickLearnDocFile,"<td><b><a href=\"%s_required_skill_value.html\">Required Skill Value</a></b></td>\n",DocFileNameRoot.c_str());
		fprintf( brickLearnDocFile,"<td><b>Learn Skill Name</b></td>\n");
		fprintf( brickLearnDocFile,"<td><b>*Learn Skill Value*</b></td>\n");
		fprintf( brickLearnDocFile,"<td><b>Found In Phrases</b></td>\n");
		fprintf( brickLearnDocFile,"</tr>\n");


		// write infos
		multimap<uint,string> learnSkillValueToCode;
		for( itBInf=BrickInfo.begin(); itBInf!=BrickInfo.end(); ++itBInf )
		{
			string brk = (*itBInf).first;
			
			if(!validateBrick(brk)) continue;
						
			// learn skill value
			string skillTmp = brickToLearnSkill[brk];
			string levelStr;
			if( skillTmp.find(";") != -1 )
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.find_first_of(";")-idx-1);	
			}
			else
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.size()-idx);
			}
			uint level = atoi(levelStr.c_str());
				
			learnSkillValueToCode.insert( make_pair(level,brk) );
		}

		multimap<uint,string>::iterator itLTC;
		for( itLTC=learnSkillValueToCode.begin(); itLTC!=learnSkillValueToCode.end(); ++itLTC )
		{
			itBInf=BrickInfo.find((*itLTC).second);

			string brk = (*itBInf).first;

			if(!validateBrick(brk)) continue;
			
			string skillTmp = (*itBInf).second.LearnSkills;
			string skill = skillTmp.substr(0,skillTmp.find_first_of(" "));
			CVectorSString dicoResult;
			Dico.lookup( skill, dicoResult, true );
			if(dicoResult.empty()) continue;
			
			// color
			string color = brickToColor[brk];

			// code
			string code = strlwr(brk.c_str());
			fprintf( brickLearnDocFile, "<tr><td><FONT COLOR=\"%s\">%s</FONT></td>\n",color.c_str(),code.c_str());
			
			// name
			string name = (*itBInf).second.Text;
			fprintf( brickLearnDocFile, "<td>%s</td>\n",name.c_str());

			// family
			string family = brickToFamily[brk];
			fprintf( brickLearnDocFile, "<td><FONT COLOR=\"%s\">%s</FONT></td>\n",color.c_str(),family.c_str());

			// required skill name
			fprintf( brickLearnDocFile, "<td>%s</td>\n",dicoResult[0].c_str());
			
			// required skill value
			string levelStr;
			if( skillTmp.find(";") != -1 )
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.find_first_of(";")-idx-1);	
			}
			else
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.size()-idx);
			}
			fprintf( brickLearnDocFile, "<td>%s</td>\n",levelStr.c_str());

			// learn skill name
			skillTmp = brickToLearnSkill[brk];
			skill = skillTmp.substr(0,skillTmp.find_first_of(" "));
			fprintf( brickLearnDocFile, "<td>%s</td>\n",skill.c_str());

			// learn skill value
			if( skillTmp.find(";") != -1 )
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.find_first_of(";")-idx-1);	
			}
			else
			{
				sint idx = skillTmp.find_first_of(" ");
				levelStr = skillTmp.substr(idx+1,skillTmp.size()-idx);
			}
			fprintf( brickLearnDocFile, "<td>%s</td>\n",levelStr.c_str());


			// phrase list
			fprintf( brickLearnDocFile, "<td>");
			map<string,map<string,string> >::iterator itPhrases = brickToPhrases.find(brk);
			if( itPhrases != brickToPhrases.end() )
			{
				map<string,string>::iterator itPh;
				uint i;
				for( itPh=(*itPhrases).second.begin(),i=0; itPh!=(*itPhrases).second.end() && i<2; ++itPh,++i )
				{
					if( MultipleDocFiles )
						fprintf( brickLearnDocFile,"<A HREF=\"%s_%c.html#%s\">%s</A>,&nbsp&nbsp",DocFileName.c_str(),(*itPh).first[7],(*itPh).first.c_str(),(*itPh).first.c_str());
					else
						fprintf( brickLearnDocFile,"<A HREF=\"%s.html#%s\">%s</A>,&nbsp&nbsp",DocFileName.c_str(),(*itPh).first.c_str(),(*itPh).first.c_str());
				}
				if( i==2 )
				{
					char type = family[0];
					fprintf( brickLearnDocFile,"[<A HREF=\"%s_%c.html#%s\">...</A>]",DocFileNameRoot.c_str(),type,brk.c_str());
				}
			}

			fprintf( brickLearnDocFile, "</td></tr>\n");
		}
		fprintf( brickLearnDocFile, "</tbody><table></P>\n" );
		fprintf( brickLearnDocFile, "</body></html>\n" );
		fclose( brickLearnDocFile );
		
	}
	
	
	return 0;

} // main //