#include <utility>
#include "html_tag.h"

#ifdef _USE_NAMESPACES
    namespace Else { 
#endif // _USE_NAMESPACES

const string TagAttribute::nullString = string();
	
TagAttribute::TagAttribute(const bool extended, const bool optional, const bool multi)
: extended(extended), 
  optional(optional),
  multivaluate(multi)
{ }

TagAttribute::~TagAttribute()
{ }

void 
TagAttribute::read(istream& in)
{
	string s;
	string::size_type delim;

	reset(); in >> ws;
	if (extended)
	{
		if (in.peek() != '=') { assert(optional); return; }
		in.ignore(1);
		if (multivaluate) { assert(in.peek() == '{'); in.ignore(1); }
	} else {
		if (in.peek() == '>') return;
		if (multivaluate) { assert(in.peek() == '{'); in.ignore(1); }
	}

	if (multivaluate)
	{
		prepareReadSequence();
		do {
			delim = extendedGetLine(in, s, string(",}"));
			istringstream i(s);
			readNext(i);
		} while (!(delim));
	} else {
		delim = extendedGet(in, s, string("> \t\r\n\v\b\f"));
		istringstream i(s);
		readSingle(i);
//		if (!delim) in.putback('>'); -> getExtendedLine(...)
	}
}

void 
TagAttribute::write(ostream& os) const
{
	if (! (optional && isDefault()))
	{
		if (extended) os << ' ' << name() << '=';
		if (multivaluate)
		{
			os << '{';
		  	if (prepareWriteSequence()) while(writeNext(os)) { os << ','; }
		   	os << '}';
		}
		else writeSingle(os);
	}
}
	
// const string&
// TagAttribute::name() const { assert(false); return nullString; }

bool
TagAttribute::isDefault() const { return false; }
	
bool
TagAttribute::isExtended() const { return extended; }

bool
TagAttribute::isOptional() const { return optional; }

bool
TagAttribute::isMultiValuate() const { return multivaluate; }

void 
TagAttribute::reset() { }

void 
TagAttribute::readSingle(istream& in) { assert(false); }

void 
TagAttribute::writeSingle(ostream& os) const { assert(false);}

void
TagAttribute::prepareReadSequence() const { assert(false); }

void
TagAttribute::readNext(istream& in) { assert(false); }

bool 
TagAttribute::prepareWriteSequence() const { assert(false); return false; }

bool 
TagAttribute::writeNext(ostream& os) const { assert(false); return false; }
	

HtmlField::Start::Start(const HtmlField& outer)
: TagAttribute(false, false, false),
  outer(outer) 
{ }

const string&
HtmlField::Start::name() const { return nullString; }

void
HtmlField::Start::writeSingle(ostream& os) const
{
	os << '<' << outer.tagName();
	for (AttribOrder::const_iterator i = outer.headOrder.begin(); i != outer.headOrder.end(); ++i) (**i).write(os);
	os << '>';
}

HtmlField::Stop::Stop(const HtmlField& outer)
: TagAttribute(false, false, false),
  outer(outer) 
{ }

const string&
HtmlField::Stop::name() const { return nullString; }

void
HtmlField::Stop::writeSingle(ostream& os) const
{
	os << "</" << outer.tagName();
	for (AttribOrder::const_iterator i = outer.tailOrder.begin(); i != outer.tailOrder.end(); ++i) (**i).write(os);
	os << '>';
}

HtmlField::HtmlFieldPtrs HtmlField::knownTags = HtmlField::HtmlFieldPtrs();

bool
HtmlField::defineHtmlTag(HtmlField& field)
{
	return knownTags.insert(make_pair(&field.tagName(), &field)).second;
}

bool
HtmlField::undefineHtmlTag(HtmlField& field)
{
	return (knownTags.erase(&field.tagName()));
}

HtmlField*
HtmlField::nextTag(istream& in)
{
	string tagid;
	string::size_type delim;
	const string ending(" \t\n\r>");
	in.ignore(SKIP_UNTIL_EOF, '<');
	delim = extendedGetLine(in, tagid, ending);
	HtmlFieldPtrs::iterator field(knownTags.find(&tagid));
	if (field == knownTags.end())
	{
	 	// Si cela ne marche pas, utiliser la meme tech. que le checker (resynchro = reset du flux).
		if (delim != ending.npos) in.putback(ending[delim]);
		for (string::reverse_iterator c = tagid.rbegin(); c != tagid.rend(); ++c) // const riterator fail with gcc2.8.1
			in.putback(*c);
		in.putback('<');
		return 0;
	}
	return field->second;
}

HtmlField*
HtmlField::readHtmlTag(istream& in)
{
	HtmlField*const res(nextTag(in));
	if (res) res->read(in);
	return res;
}

void
HtmlField::skipNextTag(istream& in) { in.ignore(SKIP_UNTIL_EOF, '>'); }
	
bool
HtmlField::skipToTag(istream& in, HtmlField*const tag)
{
	string tagid;
	const string ending(" \t\n\r>");
	do {
		in.ignore(SKIP_UNTIL_EOF, '<');
		extendedGetLine(in, tagid, ending);
	} while ((in) && (tagid != tag->tagName()));
	if (in)
	{
		tag->read(in);
		return true;
	} else return false;
}
		

HtmlField::HtmlField(const bool implicitEnding, const bool autoDefine)
: startTag(*this),
  stopTag(*this),
  implicitEnding(implicitEnding),
  headAttributes(),
  headOrder(),
  tailAttributes(),
  tailOrder()
{ 
	if (autoDefine) defineHtmlTag(*this);
}

HtmlField::~HtmlField()
{ }

HtmlField&
HtmlField::operator=(const HtmlField& fld)
{
	implicitEnding = fld.implicitEnding;
	headAttributes = fld.headAttributes;
  	headOrder = fld.headOrder;
	tailAttributes = fld.tailAttributes;
  	tailOrder = fld.tailOrder;
	return *this;
}

void
HtmlField::read(istream& in)
{
	struct GetCompactAttrib {
		GetCompactAttrib(const AttribOrder& collection)
		: collection(collection), current(collection.begin())
		{ }

		AttribOrder::const_iterator next() 
		{
			while ((current != collection.end()) && (*current)->isExtended()) ++current;
			return current;
		}

		const AttribOrder& collection;
		AttribOrder::const_iterator current;
	};

	for (AttribOrder::iterator i = headOrder.begin(); i != headOrder.end(); ++i) (*i)->reset(); 
	for (AttribOrder::iterator i = tailOrder.begin(); i != tailOrder.end(); ++i) (*i)->reset();
	
	string attribId;
	string::size_type delim;
	const string ending(">= ");
	GetCompactAttrib nextCompact(headOrder);
	do {
		in >> ws;
		delim = extendedGet(in, attribId, ending);
		if (!attribId.empty())
		{
			if (ending[delim] == '=') {
				AttributePtrs::iterator attrib(headAttributes.find(&attribId));
				if (attrib != headAttributes.end()) attrib->second->read(in);
				else {
					for (string::reverse_iterator r = attribId.rbegin(); r != attribId.rend(); ++r)
						in.putback(*r);
					AttribOrder::const_iterator compact(nextCompact.next());
					if (compact != headOrder.end()) (*compact)->read(in);
				}
			} else {
				istringstream in_str(attribId);
				AttribOrder::const_iterator compact(nextCompact.next());
				if (compact != headOrder.end()) (*compact)->read(in_str);
			}
		}
	} while ((delim != attribId.npos) && (ending[delim] != '>'));  // bricolage
	if (delim == attribId.npos) return;
	in.ignore(1);
	readField(in);
	if (!implicitEnding)
	{
		in >> ws;
		delim = extendedGet(in, attribId, ending);
		if  (attribId == (string("</")+tagName()))
		{
			GetCompactAttrib nextCompactEnding(tailOrder);
			do {
				in >> ws;
				delim = extendedGet(in, attribId, ending);
				if (!attribId.empty())
				{
					if (ending[delim] == '=') {
						AttributePtrs::iterator attrib(tailAttributes.find(&attribId));
						if (attrib != tailAttributes.end()) attrib->second->read(in);
						else {
							for (string::reverse_iterator r = attribId.rbegin(); r != attribId.rend(); ++r)
								in.putback(*r);
							AttribOrder::const_iterator compact(nextCompactEnding.next());
							if (compact != tailOrder.end()) (*compact)->read(in);
						}
					} else {
						istringstream in_str(attribId);
						AttribOrder::const_iterator compact(nextCompactEnding.next());
						if (compact != tailOrder.end()) (*compact)->read(in_str);
					}
				}
			} while (ending[delim] != '>');
			in.ignore(1);
		}
		else for (string::reverse_iterator r = attribId.rbegin(); r != attribId.rend(); ++r)
			in.putback(*r);
	}
}

void
HtmlField::write(ostream& os) const
{
	startTag.write(os);
	writeField(os);
	if (!implicitEnding) stopTag.write(os);
}

void
HtmlField::readField(istream& in) { }

void
HtmlField::writeField(ostream& os) const { }


bool
HtmlField::defineHeadAttribute(TagAttribute& attribute)
{
	if (headAttributes.insert(make_pair(&attribute.name(), &attribute)).second)
	{
		headOrder.push_back(&attribute);
		return true;
	}
	return false;
}

bool
HtmlField::defineTailAttribute(TagAttribute& attribute)
{
	if (tailAttributes.insert(make_pair(&attribute.name(), &attribute)).second)
	{
		tailOrder.push_back(&attribute);
		return true;
	}
	return false;
}

#ifdef _USE_NAMESPACES
	}
#endif // _USE_NAMESPACES


