#include "tags.h"
#include "messages_manager.h"

#ifdef _USE_NAMESPACES
    namespace Else { 
#endif // _USE_NAMESPACES

const Segmentation Segmentation::NeutralSegmentation(0,0);
const Segmentation Segmentation::InvalidSegmentation(MAX_SHORT_NATURAL,0);
    
Segmentation::Segmentation()
: specific(false), segmentRank(1), numberOfSegments(1)
{ }
	
Segmentation::Segmentation(const ShortNatural rank, const ShortNatural total)
: specific(true), segmentRank(rank), numberOfSegments(total)
{ }

void 
Segmentation::reset()
{
    specific = false;
    segmentRank = 1;
    numberOfSegments = 1;
}

bool
Segmentation::deepEqual(const Segmentation& sgm) const
{
	return ((segmentRank == sgm.segmentRank) && (numberOfSegments == sgm.numberOfSegments));
}

bool
Segmentation::operator==(const Segmentation& sgm) const
{  // Probleme: NeutralSegmentation == Invalid ? (il faut des flags booleen)
	return ( deepEqual(NeutralSegmentation) || sgm.deepEqual(NeutralSegmentation) || 
	         (!deepEqual(InvalidSegmentation) && deepEqual(sgm)) );
}

bool
Segmentation::operator!=(const Segmentation& sgm) const { return !(*this == sgm); }

bool
Segmentation::operator<(const Segmentation& sgm) const
{
	// 
	// The ratio between 'segmentRank' and 'numberOfSegments' seems to be a good idea, but
	// in this case, no difference are made between: A/1.1 and A/2.2 ... not very good.
	// 
	// Odrer: NeutralSegmentation < ... < 1.2 < ... < 3.17 < 3.18 < ... < InvalidSegmentation ( < ultra-larger...)
 	if (segmentRank == sgm.segmentRank)
 	  return (numberOfSegments < sgm.numberOfSegments);
 	return (segmentRank < sgm.segmentRank);
}

BasicTag::BasicTag():conform_string() { }

BasicTag::BasicTag(const string& tag): conform_string(tag) { }

bool
BasicTag::isMultiple() const { return (find(ALTERNATE_MARK) != npos); }

bool
BasicTag::isContracted() const { return (find(CONTRACTION_MARK) != npos); }

bool
BasicTag::isSegmented() const { return (find(SEGMENTATION_MARK) != npos); }

bool
BasicTag::isAtomic() const { return (!isMultiple() && !isContracted()); }

bool
BasicTag::checkSyntax(const GraceTools& tools,
                      const string& source,
                      const VeryLongNatural x0,
                      const string& unknownSeq) const
{
	// Work only if meta-sequences are single charactere.
	// 
	// Syntax:
	//  <atomic tag > = a tag defined in map table.  !! Not checked !!
	//  <elementary tag> = <atomic tag>{<CONTRACTION><atomic tag>}[<SEGMENT>i.j]
	//  tag = (<UNKNOWN>*)|(<elementary tag>{<ALTERNATE><elementary tag>})
	//
	msg.message(tools.nickname(), string("Checking the composite tag '") + *this + string("'."), DEBUG_MSG);

	if (find(unknownSeq)==0) return true;
		
	bool res(true);
	const size_type len(size());
	enum State { Start, InTag, NoSegm, WrongSegMark, NbSegm };
	size_type transition(0);
	State state(Start);
	conform_string segmNo, segmNb;
	const char END(0);
	for (size_type i = 0; i <= len; ++i)
	{
		const char* ptr;
		if (i == len) ptr = &END;
		else ptr = &(this->at(i));
		const char& c(*ptr);
			switch (state)
		{
			case Start :
			{
				switch (c)
				{
					case END			   :
					case ALTERNATE_MARK	   :
					case CONTRACTION_MARK  :
					case SEGMENTATION_MARK : msg.error(tools.id(), 9, 0, string("Empty tag"),
												       string("Illegal empty atomic tag in '") + *this + string("'."),
												       Location(&source, x0+transition, x0+i));
											 res = false;
											 break;
																								  
					default				   : state = InTag;
										     transition = i;
											 break;
				}
				break;
			}

			case InTag :
			{
				switch (c)
				{
					case ALTERNATE_MARK	   :
					case CONTRACTION_MARK  : state = Start;
											 transition = i;
											 break;

					case SEGMENTATION_MARK : segmNo.clear();
											 segmNb.clear();
											 state = NoSegm; 
											 transition = i; 
											 break;
				}
				break;
			}

			case NoSegm :
			{
				if (c == END)
				{
					msg.error(tools.id(), 9, 1,
							  string("Corrupt tag"),
							  string("The tag '") + *this + string ("' has an incomplete segmentation."),
							  Location(&source, x0 + transition, x0 + i));
					res = false;
					break;
				}
				if (c == SEGMENTATION_SUBMARK)
				{
					state = NbSegm;
					break;
				}
				if (isdigit(c)) segmNo.push_back(c);
				else state = WrongSegMark;
				break;
			}

			case WrongSegMark :
			{
				if (isdigit(c))
				{
					msg.warning(tools.id(), 9, 1,
								string("Wrong Segmentation format"),
								string("The tag '") + *this + string("' hasn't a correct separator (")
									+ SEGMENTATION_SUBMARK + string(") between segment rank and segments total."),
								Location(&source, x0 + i - 1, x0 + i));
					segmNb.push_back(c);
					state = NbSegm;
				} else {
					msg.error(tools.id(), 9, 2,
							  string("Wrong Segmentation format"),
							  string("The tag '") + *this + string ("' contain invalid segmentation field."),
							  Location(&source, x0 + transition, x0 + i));
					state = Start;
					transition = i;
					res = false;
				}
				break;
			}

			case NbSegm :
			{
				if (isdigit(c)) segmNb.push_back(c);
				else switch (c)
				{
					case SEGMENTATION_MARK:
					{
						msg.error(tools.id(), 9, 8,
								  string("Invalid Tag format"),
								  string("The tag '") + *this + string("' has broken contracted tag."),
								  Location(&source, x0 + transition, x0 + i));
						res = false;  // no break !!
					}
					case ALTERNATE_MARK:
					case END:
					{
						if (segmNo.empty())
						{
							msg.error(tools.id(), 9, 3,
									  string("Invalid Segment Rank"),
									  string("The tag '") + *this + string("' has an invalid null segment id."),
									  Location(&source, x0 + transition, x0 + transition + 1));
							res = false;
						}
						if (segmNb.empty())
						{
							msg.error(tools.id(), 9, 4,
									  string("Invalid Segments total"),
									  string("The tag '") + *this + string("' has an invalid null segments total."),
									  Location(&source, x0 + transition + segmNo.size(), x0 + i));
							res = false;
						}
						if ( !segmNo.empty() && (segmNo.find_first_not_of('0') == segmNo.npos))
							msg.warning(tools.id(), 9, 5,
										string("Invalid Segment Rank"),
										string("The tag '") + *this + string("' has invalid zero segment id (")
											+ segmNo + string(")."),
										Location(&source, x0 + transition, x0 + transition + segmNo.size()));
						if ( !segmNb.empty() && (segmNb.find_first_not_of('0') == segmNb.npos))
							msg.warning(tools.id(), 9, 6,
										string("Invalid Segments Total"),
										string("The tag '") + *this + string("' has invalid zero segments total (")
											+ segmNb + string(")."),
										Location(&source, x0 + transition + segmNo.size(), x0 + i));
						if (segmNo > segmNb)
							msg.warning(tools.id(), 9, 7,
										string("Invalid Segment Rank"),
										string("The tag '") + *this + string("' has wrong segmentation field: )")
											+ string(" segment rank '") + segmNo + string("' > segments total '")
											+ segmNb + string("'."),
										Location(&source, x0 + transition, x0 + i));
						state = Start;
						transition = i;
						break;
					  
					}

					default :
					{
						msg.error(tools.id(), 9, 8,
								  string("Invalid Segment format"),
								  string("The tag '") + *this 
									  + string("' has invalid segments total, or missed alt/contr mark."),
								  Location(&source, x0 + transition, x0 + i));
						res = false;
					}
				}
				break;
			}
		}
	}
	return res;
}

LocalisedTag::LocalisedTag()
: BasicTag(),
  LocalisedData()
{ }

LocalisedTag::LocalisedTag(const string& tag)
: BasicTag(tag),
  LocalisedData()
{ }

LocalisedTag::LocalisedTag(const string& tag, const LocalisedData& location)
: BasicTag(tag),
  LocalisedData(location)
{ }
	
LocalisedTag::LocalisedTag(const string& tag, const VeryLongNatural& from, const VeryLongNatural& to)
: BasicTag(tag),
  LocalisedData(from, to)
{ }

bool LocalisedTag::checkSyntax(const GraceTools& tools, const string& source, const string& unknown) const
{
	return BasicTag::checkSyntax(tools, source, from, unknown);
}


ContractedTag::ContractedTag()
: AtomicTags(),
  segmentation(),
  len(0)
{ }

ContractedTag::ContractedTag(const BasicTag& tags)
: AtomicTags(),
  segmentation(),
  len(tags.size())
{

#ifdef UNCHECKED_TAG
	bool invalid(false);
#endif

	char c;
	AtomicTag atomic;
	istringstream stream(tags);
	while (stream.get(c)) switch (c)
	{
		case CONTRACTION_MARK:
		{
#ifdef UNCHECKED_TAG
			invalid = segmentation.specific;
#endif			
			AtomicTags::push_back(atomic);
			atomic.clear();
			break;
		}
		case SEGMENTATION_MARK:
		{
			AtomicTags::push_back(atomic);
			atomic.clear();
			segmentation.specific = true;
			stream >> segmentation.segmentRank;
			stream.ignore(1);
			stream >> segmentation.numberOfSegments;

#ifndef UNCHECKED_TAG
			return;
#endif
  			break;
		}
		default:
		{
			atomic.push_back(c);
		}
	}
	if (!atomic.empty()) AtomicTags::push_back(atomic);
#ifdef UNCHECKED_TAG
	if (invalid) segmentation = Segmentation::InvalidSegmentation;
#endif
}

void 
ContractedTag::push_back(const AtomicTag& tag)
{
   if (!empty()) ++len;
   len += tag.size();
   AtomicTags::push_back(tag);
}
bool ContractedTag::isSegmented() const { return segmentation.specific; }

const AtomicTag::size_type& 
ContractedTag::length() const { return len; }
	
conform_string
ContractedTag::str() const { ostringstream os; out(os); return os.str(); }

bool
ContractedTag::operator<(const ContractedTag& tag) const
{
	if (static_cast<const AtomicTags&>(*this) == static_cast<const AtomicTags&>(tag))
		return (segmentation < tag.segmentation);
	else return (static_cast<const AtomicTags&>(*this) < static_cast<const AtomicTags&>(tag));
}

bool
ContractedTag::isSameBase(const ContractedTag& tag) const
{ return (static_cast<const AtomicTags&>(*this) == static_cast<const AtomicTags&>(tag)); }


bool
ContractedTag::isSameBase(const BasicTag& tag) const
{
	string self;
	const string::size_type stop(tag.find(SEGMENTATION_MARK));
	if ( !empty())
	{
		const_iterator i = begin();
		self += *i;
		while ( ++i != end() ) { self += CONTRACTION_MARK; self += *i; }
	}
	return ( self == tag.substr(0, stop));
}


ostream&
ContractedTag::out(ostream& os) const
{
	if ( !empty())
	{
		const_iterator i = begin();
		os << *i;
		while ( ++i != end() )
			os << CONTRACTION_MARK << *i;
	}
	if (segmentation.specific)
	{
		os << SEGMENTATION_MARK;
		os << segmentation.segmentRank;
		os << SEGMENTATION_SUBMARK;
		os << segmentation.numberOfSegments;
	}
	return os;
}

AlternatedTagsSet::AlternatedTagsSet()
: ContractedTagsSet(),
  len(0)
{ }

AlternatedTagsSet::AlternatedTagsSet(const BasicTag& tags)
: ContractedTagsSet(),
  len(tags.size())
{
	istringstream stream(tags);
	while (!stream.eof())
	{
		BasicTag contracted;
		getline(stream, contracted, ALTERNATE_MARK);
		ContractedTagsSet::insert(ContractedTag(contracted));
	}
}

void
AlternatedTagsSet::insert(const ContractedTag& tag)
{
/*
 * Debug only
 * 	cout << "Adding Alternate tag set from: '" << tag << "' to '" << this->str();
 * 	if (tag.empty()) cout << "' (Empty-";
 * 	else cout << "' (not empt-";
 * 	if (this->empty()) cout << " Empty) ";
 * 	else cout << " not empt) ";
*/
	if (!tag.empty())
	{
		if (!empty()) len++;
		len += tag.length();
		ContractedTagsSet::insert(tag);
	}

/*
	cout << "' = '" << this->str() << "' ";
	if (this->empty()) cout << " (Empty)\n";
	else cout << " (not empty)\n";	
*/

}

const Natural&
AlternatedTagsSet::length() const { return len; }

conform_string
AlternatedTagsSet::str() const
{   
	ostringstream os;
	out(os);
	return os.str();
}
	
ostream&
AlternatedTagsSet::out(ostream& os) const
{
	if ( !empty())
	{
		const_iterator i = begin();
		os << *i;
		while ( ++i != end() )
			os << ALTERNATE_MARK << *i;
	}
	return os;
}

ostream&
operator << (ostream& os, const ContractedTag& tag) { return tag.out(os); }

ostream&
operator << (ostream& os, const AlternatedTagsSet& tag) { return tag.out(os); }

#ifdef _USE_NAMESPACES
	}
#endif // _USE_NAMESPACES


