#include <list>
#include "tags_mapping_table.h"

#ifdef _USE_NAMESPACES
    namespace Else { 
#endif // _USE_NAMESPACES

// Use vector's (and string's) built-in lexicographical comparison.
bool
LessPredicates::operator()(const ContractedTag& t1, const ContractedTag& t2) const { return (t1 < t2); }

// Use string's built-in lexicographical comparison.
bool
LessPredicates::operator()(const AtomicTag& a1, const AtomicTag& a2) const { return (a1 < a2); }

TagsMappingTable::TagsMappingTable()
: systemFileName(sysFileName),
  referenceFileName(refFileName),
  systemNeutralTag(sysNeutralTag),
  referenceNeutralTag(refNeutralTag),
  refConfig(0),
  sysConfig(0),
  refNeutralTag(),
  sysNeutralTag(),
  refDirectPrj(),
  sysDirectPrj(),
  refReversePrj(),
  sysReversePrj(),
  mappedSystem(),
  mappedPivot()
{ }

TagsMappingTable::~TagsMappingTable()
{ }

const string&
TagsMappingTable::nickname() const { return TOOLS_NICKNAME; }
	
const unsigned char&
TagsMappingTable::id() const { 	return TOOLS_ID; }

bool
TagsMappingTable::readReference(const UserConfiguration& config, const string& filename, const TypeTable& mapMode)
{
	const StringCPtr in(filename.empty() ? &config.mapName : &filename);
	const TypeTable mode(filename.empty() ? config.mapMode : mapMode);
	if (makeProjection(*in, mode, config, true))
	{
		refConfig = &config;
		refFileName = *in;
		sysConfig = 0;
		sysFileName.clear();
		sysDirectPrj.clear();
		sysReversePrj.clear();
		return true;
	} else return false;
}

bool
TagsMappingTable::readSystem(const UserConfiguration& config, const string& filename, const TypeTable& mapMode)
{
	const StringCPtr in(filename.empty() ? &config.mapName : &filename);
	const TypeTable mode(filename.empty() ? config.mapMode : mapMode);
	if (makeProjection(*in, mode, config, false)) 
	{
		sysConfig = &config;
		sysFileName = *in;
		return true;
	} else return false;
}

Natural
TagsMappingTable::sizeRef() const { return refReversePrj.size(); }

Natural
TagsMappingTable::sizePivot() const { return refDirectPrj.size(); }

Natural
TagsMappingTable::sizeSys() const { return sysDirectPrj.size(); }
	
void
TagsMappingTable::reset()
{
	refConfig = sysConfig = 0;
	refFileName.clear();
	sysFileName.clear();
	refDirectPrj.clear();
	sysDirectPrj.clear();
	refReversePrj.clear();
	sysReversePrj.clear();
	clearSavedMapping();	
}

bool
TagsMappingTable::buffersEmpty() const { return (mappedSystem.empty() && mappedPivot.empty()); }

void
TagsMappingTable::clearSavedMapping() { mappedSystem.clear(); mappedPivot.clear(); }

bool
TagsMappingTable::existRef(const AtomicTag& refTag) const // search in 'pivot-in' tags set
{
	if (refTag == refConfig->irrelevantTag) return true;
	return (refDirectPrj.find(refTag) != refDirectPrj.end());
}

bool
TagsMappingTable::existRef(const ContractedTag& refTag) const // search in 'pivot-in' tags set
{
	return (refDirectPrj.find(refTag) != refDirectPrj.end());
}

bool
TagsMappingTable::existSys(const AtomicTag& sysTag) const // search in 'input' tags set
{
	if (sysTag == sysConfig->irrelevantTag) return true;
	return (sysDirectPrj.find(sysTag) != sysDirectPrj.end());
}

bool
TagsMappingTable::existSys(const ContractedTag& sysTag) const // search in 'input' tags set
{
	return (sysDirectPrj.find(sysTag) != sysDirectPrj.end());
}

bool
TagsMappingTable::isDefinedAsRef(const ContractedTag& tags) const // check if all tags are defined
{
    if (!existRef(tags))
	    for (ContractedTag::const_iterator i = tags.begin(); i != tags.end(); ++i)
		    if (!existRef(*i)) return false;
	return true;
}

bool
TagsMappingTable::isDefinedAsRef(const AlternatedTagsSet& tags) const // check if all tags are defined
{
	for (AlternatedConstIterator i = tags.begin(); i != tags.end(); ++i)
		if (!isDefinedAsRef(*i)) return false;
	return true;
}

bool
TagsMappingTable::isDefinedAsSys(const ContractedTag& tags) const // check if all tags are defined
{
    if (!existSys(tags))
	    for (ContractedTag::const_iterator i = tags.begin(); i != tags.end(); ++i)
		    if (!existSys(*i)) return false;
	return true;
}
	
bool
TagsMappingTable::isDefinedAsSys(const AlternatedTagsSet& tags) const // check if all tags are defined
{
	for (AlternatedConstIterator i = tags.begin(); i != tags.end(); ++i)
		if (!isDefinedAsSys(*i)) return false;
	return true;
}

bool
TagsMappingTable::checkRef(const GraceTools& tools, const string& origin, const LocalisedTag& tag) const
{
	bool res(tag.checkSyntax(tools, origin, refConfig->unknownTag));
	if (res)
	{
		string::size_type x1(0);
		AlternatedTagsSet alt(tag);
		for (AlternatedIterator ctrct = alt.begin(); ctrct != alt.end(); ++ctrct)
		{
		    const_cast<Segmentation&>(ctrct->segmentation).reset();  // Wy I need a cast ????
   			if (!existRef(*ctrct))
			    for (ContractedConstIterator atom = ctrct->begin(); atom != ctrct->end(); ++atom)
			    if (!existRef(*atom))
			    {
				   res = false;
				   msg.error(tools.id(), 9, 9,
					      	 string("Undefined reference tag"),
						     string("The atomic tag '") + *atom + string("' is not defined in mapping table."),
						     Location(&origin, tag.from+x1, tag.from+x1+ctrct->length()));
   				}
			x1 += ctrct->length() + 1;
		}
	}
	return res;
}

bool
TagsMappingTable::checkSys(const GraceTools& tools, const string& origin, const LocalisedTag& tag) const
{
	if (tag.find(sysConfig->unknownTag)==0) return true;
	bool res(tag.checkSyntax(tools, origin, sysConfig->unknownTag));
	if (res)
	{
		string::size_type x1(0);
		AlternatedTagsSet alt(tag);
		for (AlternatedIterator ctrct = alt.begin(); ctrct != alt.end(); ++ctrct)
		{
		    const_cast<Segmentation&>(ctrct->segmentation).reset();  // Wy I need a cast ????
		    if (!existSys(*ctrct))
			    for (ContractedConstIterator atom = ctrct->begin(); atom != ctrct->end(); ++atom)
			    if (!existSys(*atom))
			    {
				   res = false;
				   msg.error(tools.id(), 9, 9,
						     string("Undefined system tag"),
						     string("The atomic tag '") + *atom + string("' is not defined in mapping table."),
						     Location(&origin, tag.from+x1, tag.from+x1+ctrct->length()));
			    }
			x1 += ctrct->length() + 1;
		}
	}
	return res;
}

void
TagsMappingTable::dump() const // debug only
{
	cout << "\nSystem -> Pivot : #" << sysDirectPrj.size() << '\n';
	for (Projection::const_iterator i = sysDirectPrj.begin(); i != sysDirectPrj.end(); ++i)
		cout << '<' << i->first.str() << "> -> {" << i->second.str() << "}\n";
	cout << "\nPivot -> Reference : #" << refDirectPrj.size() << '\n';
	for (Projection::const_iterator i = refDirectPrj.begin(); i != refDirectPrj.end(); ++i)
		cout << '<' << i->first.str() << "> -> {" << i->second.str() << "}\n";
	cout << "\nReference -> Pivot : #" << refReversePrj.size() << '\n';
	for (Projection::const_iterator i = refReversePrj.begin(); i != refReversePrj.end(); ++i)
		cout << '<' << i->first.str() << "> -> {" << i->second.str() << "}\n";
	cout << "\nPivot -> System : #" << sysReversePrj.size() << '\n';
	for (Projection::const_iterator i = sysReversePrj.begin(); i != sysReversePrj.end(); ++i)
		cout << '<' << i->first.str() << "> -> {" << i->second.str() << "}\n";
}

void
TagsMappingTable::dumpInv() const // debug only
{
	cout << "# Pivot -> System : #" << sysReversePrj.size() << '\n';
	for (Projection::const_iterator i = sysReversePrj.begin(); i != sysReversePrj.end(); ) {
		cout << i->first.str() << " ";
		AlternatedTagsSet tag;
		for (Projection::size_type j = sysReversePrj.count(i->first); j > 0; --j, ++i) tag.insert(i->second);
		cout << tag.str() << '\n';
	}
}
//
// I thing it's better to define this reader into the 'makeProjection' methode...
// but gcc does not think it. (It make an internal error on static member, or on empty line (without static
// member), when compiling with optimization level greather than 0... then...)
// 
typedef list<LocalisedString> StringsCollection;
class TagReader : public CharFlowListener, public StringsCollection {
public:
	TagReader(const GraceTools& tools, const UserConfiguration& cfg, const string& file)
	: StringsCollection(),
	  lastString()
	{
		ifstream in(file.c_str(), ios::in);
		if (in)
		{
			char c;
			const conform_string& startCmt(cfg.sep(StartCmtTbl));
			const conform_string& endCmt(cfg.sep(EndCmtTbl));
			QuietTranslateFilter step2(startCmt + endCmt, " ", " ", *this);
			BasicQuietFilter     step1(startCmt, endCmt, false, true, step2);
			VeryLongNatural      count(0);
			while (in.get(c)) step1 <<= ichar(c, count++);
			in.close();
			// Il reste a 'vider' le pipe...
			if (step1.getState() == BasicQuietFilter::InMarks)
			{
				msg.warning(tools.id(), 1, 1, 
						    string("Unterminated comment"), 
						    string("No valid comment's terminator ('") + endCmt + string("') found"),
				    	    Location(&file, lastString.to + 1, count));
				for (conform_string::const_iterator i(endCmt.begin()); i != endCmt.end(); ++i)
					step1 <<= ichar(*i, count);
			}
			step1.flush();
			step2.flush();
			if (!lastString.empty()) push_back(lastString);
			if (size() % 2)
				msg.warning(tools.id(), 1, 2,
				            string("Corrupted file"), 
					        string("Missing projected tag for '") + back() + string("'."),
					        Location(&file, back().from, count));
		}
		else msg.seriousError(tools.nickname(), string("Mapping tags file: " + file + " not found!"));
	}
    void computeFlowItem(const ichar& data)
    {
      	if (lastString.empty())
       	{
       		if (!is_space(data.c))
       		{
       			lastString.to = lastString.from = data.i;
       			lastString.push_back(data.c); 
       		}
       	}
       	else
       	{
       		if (is_space(data.c)) 
       		{
       			push_back(lastString);
       			lastString.conform_string::clear();
       		}
       		else
       		{
       			lastString.to = data.i;
       			lastString.push_back(data.c);
       		}
       	}
    }
	const LocalisedString& getLastString() { return lastString; }

private:
	LocalisedString lastString;
};
	
bool
TagsMappingTable::makeProjection(const string& fileName,
                                 TypeTable mode,
                                 const UserConfiguration& config,
                                 const bool reference)
{
	Projection*    direct;
	Projection*    reverse;
	TagReader      reader(*this, config, fileName);

	if (reader.empty()) return false;

	if (reference) { direct = &refDirectPrj; reverse = &refReversePrj; }
	else { direct = &sysDirectPrj; reverse = &sysReversePrj; }
	direct->clear();
	reverse->clear();

	TagReader::const_iterator tagInPtr, tagOutPtr;
	
	for (TagReader::const_iterator iterator(reader.begin()); iterator != reader.end(); ) 
	{
		tagInPtr = iterator;
		tagOutPtr = ++iterator;
		if (tagOutPtr != reader.end())
		{
			++iterator;
			BasicTag tagIn(*tagInPtr);
			BasicTag tagOut(*tagOutPtr);
			msg.message("Mapper", tagIn + string(" -> ") + tagOut, DEBUG_MSG);
			bool readyForAdd (tagIn.checkSyntax(*this, fileName, tagInPtr->from, config.unknownTag)
			                 & // Force checking of second tag...
			                 (tagOut.checkSyntax(*this, fileName, tagOutPtr->from, config.unknownTag)));
			if (tagIn.isMultiple() || tagIn.isSegmented())
			{
				msg.error(id(), 5, 2,
						  string("Invalid mark in elementary tag"),
						  string("Entry tag has prohibed tokenization or composition marks;")
							  + string(" rejecting the projection [")+tagIn+string("] -> {")+tagOut+string("}."),
						  Location(&fileName, tagInPtr->from, tagInPtr->to));
				readyForAdd = false;
			}
			if (readyForAdd)
			{ 
				//
				// Ready for the last part:
				// a) In unknown mode, test if table is direct or reverse,
				// b) check the syntax of entry and target tag
				// c) In system mode, check if target tags are defined (*).
				//
				VeryLongNatural index(tagOutPtr->from);
				switch (mode) {
				case DirectTbl: // [tagIn]=Pivot/SysTag -> {[secondPart]}=Reference/Pivot(*)
				{ 
					ContractedTag	 entry(tagIn);
					AlternatedTagsSet target(tagOut);
					if (direct->find(entry) != direct->end())
						msg.warning(id(), 3, 2,
									string("Multiple definition of a tag"),
									string("The tag '") + tagIn + string("' was already defined."),
									Location(&fileName, tagInPtr->from, tagInPtr->to));
					for (AlternatedConstIterator i = target.begin(); i != target.end(); ++i)
					{
						if (i->isSegmented())
							msg.error(id(), 5, 3,
									  string("Prohibed tokenization marks in tag"),
									  string("Target tag '") + i->str()
										  + string("' has prohibited tokenization marks. ")
										  + string("Rejecting the projection [") + tagIn + string("] -> {")
										  + i->str() + string("}."),
									  Location(&fileName, index, index + i->length()));
						else
						{
							if (reference || isDefinedAsRef(*i))
							{
								direct->insert(make_pair(entry, *i));
								reverse->insert(make_pair(*i, entry));
							}
							else msg.error(id(), 5, 2,
										   string("Invalid tag"),
										   string("The tag '") + i->str()
											   + string("' is not a contraction of reference tags. ")
											   + string("Rejecting the projection [")
											   + tagIn + string("] -> [") + i->str() + string("]."),
										   Location(&fileName, index, index + i->length()));
						}
						index += i->length() + 1; // i->length() + ALTERNATE_MARK.size()
					}
					break;
				}

				case ReverseTbl: // [entryTag]=Ref/Pivot(*) -> {[secondPart]}=Pivot/Sys
				{ 
					ContractedTag	 entry(tagIn);
					AlternatedTagsSet target(tagOut);
					if (! (reference || isDefinedAsRef(entry)))
						msg.error(id(), 5, 2,
								  string("Invalid tag"),
								  string("The reverse entry tag '") + tagIn
									  + string("' is not a contraction of reference tags. ")
									  + string("Rejecting the entire projection [")
									  + tagIn + string("] <- {") + tagOut + string("}."),
								  Location(&fileName, tagInPtr->from, tagInPtr->from + tagIn.size()));
					else for (AlternatedConstIterator i = target.begin(); i != target.end(); ++i)
					{
						if (i->isSegmented())
							msg.error(id(), 5, 3,
									  string("Prohibed tokenization marks in tag"),
									  string("Target tag '") + i->str()
										  + string("' has prohibited tokenization marks. ")
										  + string("Rejecting the projection [") + tagIn + string("] -> [")
										  + i->str() + string("]."),
									  Location(&fileName, index, index + i->length()));
						else
						{
							direct->insert(make_pair(*i, entry));
							reverse->insert(make_pair(entry, *i));
						}
						index += i->length() + 1; // i->length() + ALTERNATE_MARK.size()
					}
					break;
				}

				case UnknownTbl:
				{
					assert(!reference);
					ContractedTag	  entry(tagIn);
					AlternatedTagsSet target(tagOut);
					//
					// Testing direct: secondPart must be a list of referenceTags (ev. contracted)
					// 
					msg.message(nickname(), "Testing the mapping mode (direct/reverse)....", DETAIL_MSG);
					if (isDefinedAsRef(target))
					{
						mode = DirectTbl;
						msg.message(nickname(), "Map in direct mode recognized.", DETAIL_MSG);
						if (direct->find(entry) != direct->end())
							msg.warning(id(), 3, 2,
										string("Multiple definition of a tag"),
										string("The tag '") + tagIn + string("' was already defined."),
										Location(&fileName, tagInPtr->from, tagInPtr->from + entry.size()));
						for (AlternatedConstIterator i = target.begin(); i != target.end(); ++i)
						{
							direct->insert(make_pair(entry, *i));
							reverse->insert(make_pair(*i, entry));
						}
						break;
					} else if (isDefinedAsRef(entry)) {
						// We suppose that the mode is 'reverse'.
						mode = ReverseTbl;
						msg.message(nickname(), "Map in reverse mode detected.", DETAIL_MSG);
						for (AlternatedConstIterator i = target.begin(); i != target.end(); ++i)
						{
							if (i->isSegmented())
								msg.error(id(), 5, 3,
										  string("Prohibed tokenization marks in tag"),
										  string("Target tag '") + i->str()
											  + string("' has prohibited tokenization marks. ")
											  + string("Rejecting the projection [") + tagIn + string("] -> [")
											  + i->str() + string("]."),
										  Location(&fileName, index, index + i->length()));
							else
							{
								direct->insert(make_pair(*i, entry));
								reverse->insert(make_pair(entry, *i));
							}
							index += i->length() + 1; // i->length() + ALTERNATE_MARK.size()
						}
						break;
					} else
						msg.error(id(), 5, 4,
								  string("UnknownTbl Mode"),
								  string("The entry [") + tagIn + string("] <-?-> [") + tagOut
										 + string("] contain some errors and/or unrecognized tags ")
										 + string("which prevent the detection mode."),
								  Location(&fileName, tagInPtr->from, tagOutPtr->to));
					break;
				}

				default :
					THROW(domain_error(nickname() + string ("(Projection): invalid mapping mode.")));
					break;
				}
			}
		}
	}
	if (reference) 
	{
		refNeutralTag = ContractedTag(config.neutralizedTag);
		if (!refNeutralTag.isSegmented()) refNeutralTag.segmentation = Segmentation::NeutralSegmentation;
	} else {
		sysNeutralTag = ContractedTag(config.neutralizedTag);
		if (!sysNeutralTag.isSegmented()) sysNeutralTag.segmentation = Segmentation::NeutralSegmentation;
	}
	return true;
}

AlternatedTagsSet
TagsMappingTable::mapPivotToRef(const BasicTag& tags) const 
{ 
    msg.message(nickname(), string("Mapping Pivot -> reference: ") + tags, DEBUG_MSG);

	MappedTag::const_iterator mapped(mappedPivot.find(tags));
	if (mapped != mappedPivot.end()) return mapped->second;

#ifdef CONTRACTED_PATCH
    //
    // (0) Transforme the 'A+B/1.2|C+D' to 'A+B/1.2|A/1.2|B/1.2|C+D|C|D' form...
    // 

    AlternatedTagsSet res;
	AlternatedTagsSet _tags(tags);
	for (AlternatedIterator i = _tags.begin(); i != _tags.end(); ++i)
   	{
   		if (i->size() > 1) for (ContractedConstIterator j = i->begin(); j!= i->end(); ++j) 
   		{
           	ContractedTag cc(*j);
           	cc.segmentation = i->segmentation;
           	res.insert(cc);
       	}
       	res.insert(*i);
   	}
    msg.message(nickname(), string("Precomputing: patching tag to : ") + res.str(), DEBUG_MSG);
    AlternatedTagsSet final(map(res, refDirectPrj, refNeutralTag)); //, refConfig)); // (*)
#else
    AlternatedTagsSet final(map(tags, refDirectPrj, refNeutralTag)); // , refConfig)); // (*)
#endif
    mappedPivot.insert(make_pair(tags, final));
	return final;
}    

AlternatedTagsSet
TagsMappingTable::mapSysToRef(const BasicTag& tags) const 
{
	MappedTag::const_iterator mapped(mappedSystem.find(tags));
	if (mapped != mappedSystem.end()) return mapped->second;
//    AlternatedTagsSet final(map(map(tags, sysDirectPrj, sysNeutralTag, sysConfig), refDirectPrj, refNeutralTag, sysConfig)); // (*)
    AlternatedTagsSet final(map(map(tags, sysDirectPrj, sysNeutralTag), refDirectPrj, refNeutralTag)); 
    mappedSystem.insert(make_pair(tags, final));
	return final;
}

AlternatedTagsSet
TagsMappingTable::mapPivotToSys(const BasicTag& tags) const
{
    msg.message(nickname(), string("Mapping Pivot -> system: ") + tags, DEBUG_MSG);

	MappedTag::const_iterator mapped(mappedPivot.find(tags));
	if (mapped != mappedPivot.end()) return mapped->second;

#ifdef CONTRACTED_PATCH

    //
    // (0) Transforme the 'A+B/1.2|C+D' to 'A+B/1.2|A/1.2|B/1.2|C+D|C|D' form...
    // 

    AlternatedTagsSet res;
	AlternatedTagsSet _tags(tags);
	for (AlternatedIterator i = _tags.begin(); i != _tags.end(); ++i)
   	{
   		if (i->size() > 1) for (ContractedConstIterator j = i->begin(); j!= i->end(); ++j) 
   		{
           	ContractedTag cc(*j);
           	cc.segmentation = i->segmentation;
           	res.insert(cc);
       	}
       	res.insert(*i);
   	}
    msg.message(nickname(), string("Precomputing: patching tag to : ") + res.str(), DEBUG_MSG);
    AlternatedTagsSet final(map(res, sysReversePrj, sysNeutralTag)); //, refConfig)); // (*)
#else
    AlternatedTagsSet final(map(tags, sysReversePrj, sysNeutralTag)); //, refConfig)); // (*)(
#endif
    mappedPivot.insert(make_pair(tags, final));
	return final;
}


// A derecursifier et a integer dans map.
typedef pair<Projection::const_iterator, Projection::const_iterator> EqRange;
typedef vector<EqRange> ItColl;
void add(const ItColl::const_iterator& k, const ItColl::const_iterator& end,
         ContractedTag to, AlternatedTagsSet& tto, const Segmentation& segm)
{
	if (k == end)
	{
		to.segmentation = segm;
		tto.insert(to);
	}
	else for (Projection::const_iterator alt = k->first; alt != k->second; ++alt)
	{
		ContractedTag newTo(to);
		for (ContractedConstIterator atom = alt->second.begin(); atom != alt->second.end(); ++atom)
			newTo.push_back(*atom);
		add(k+1, end, newTo, tto, segm);
	}
}
	

AlternatedTagsSet
TagsMappingTable::map(AlternatedTagsSet tags, const Projection& projection, const ContractedTag& neutral) const
//TagsMappingTable::map(AlternatedTagsSet tags, const Projection& projection, const ContractedTag& neutral,
//                      const UserConfigurationCPtr conf) const
{
    AlternatedTagsSet res;
    
    //
    // (1) Map 'A+B|C' -> 'a+b|c'
    // 
    msg.message(nickname(), "Mapping " + tags.str() + "...", DEBUG_MSG);
    for (AlternatedIterator i = tags.begin(); i != tags.end(); ++i) 
    {
    
        Segmentation segm(i->segmentation);
 	    const_cast<Segmentation&>(i->segmentation).reset();  // Wy I need a cast ????
		//
		// Patch (a voir) -> simule ? -> ?
		// 
//		if ((*i) == ContractedTag(conf->unknownTag)) res.insert(*i);
		// fin patch
//		else {

        	//
	        // (1.1) Check if 'A+B' exist like this
			if (projection.find(*i) != projection.end()) // Older (false): if (existRef(*i)) 
	        {
				EqRange range = projection.equal_range(*i); // A merger avec le if precedent
            	for (Projection::const_iterator j = range.first; j != range.second; ++j)
	            {
    	            ContractedTag cc(j->second);
        	        cc.segmentation = segm;
            	    res.insert(cc);
            	}
	        } else {
		        //
	    	    // (1.2) We must decompose and distribute...
    	    	//  
	            ItColl collection;  // first, gather all bounds.
    	        for (ContractedConstIterator j = i->begin(); j != i->end(); ++j) 
        	    {
            	    EqRange range = projection.equal_range(*j);
                	if (range.first != range.second) collection.push_back(range);
	            }
				add(collection.begin(), collection.end(), ContractedTag(), res, segm);
			}
//      }
    }
#ifdef SPECIFIC
	if (res.size() > 1) res.erase(neutral);
#endif	
    return res;
}

const string TagsMappingTable::TOOLS_NICKNAME("TagMapping");
const unsigned char TagsMappingTable::TOOLS_ID('T');

#ifdef _USE_NAMESPACES
}
#endif // _USE_NAMESPACES


