#include <colors.h>
#include <dongle.h>
#include <real.h>
#include <xml.h>

#include <incstr.h>

///////////////////////////////////////////////////////////
// Utilities
///////////////////////////////////////////////////////////

void Spaces(ostream& outf, int nSpaces)
{
	outf << '\n';
	if (nSpaces > 0)
	{
		TString str(nSpaces, ' '); 
		str.print_on(outf);
	}
}

int hex2int(const char* str)
{
	int n = 0;
	for (int i = 0; str[i]; i++)
	{
		if (str[i] >= '0' && str[i] <= '9')
		{
  		n *= 16;
      n += str[i]-'0';
		} else
		if (str[i] >= 'A' && str[i] <= 'F')
		{
  		n *= 16;
		  n += str[i]-'A'+10;
		}
		else
			break;
	}
	return n;
}

const TString& EscapeSequence(char cStart, istream& inf)
{
	TString& str = get_tmp_string();

	if (cStart == '&')
	{
    char c;
		for (c = inf.get(); c != ';' && c > ' '; c = inf.get())
			str << c;
    if (c == ';') // Ho trovato il carattere di fine sequenza
    {
      if (str[0] == '#')
      {
		    str[0] = hex2int((const char*)str+1);
        str[1] = '\0';
      } else
      if (str[0] >= '0' && str[0] <= '9')
      {
		    str[0] = atoi(str);
        str[1] = '\0';
      }
      else
      {
		    if (str == "lt") return str ="<";
		    if (str == "gt") return str =">";
		    if (str == "nbsp") return str =" ";
		    if (str == "Agrave") return str ="�";
		    if (str == "Egrave") return str ="�";
		    if (str == "Eacuto") return str ="�";
		    if (str == "Igrave") return str ="�";
		    if (str == "Ograve") return str ="�";
		    if (str == "Ugrave") return str ="�";
		    if (str == "agrave") return str ="�";
		    if (str == "egrave") return str ="�";
		    if (str == "eacuto") return str ="�";
		    if (str == "igrave") return str ="�";
		    if (str == "ograve") return str ="�";
		    if (str == "ugrave") return str ="�";

        const char tmp[2] = { cStart, '\0' };
		    str.insert(tmp);
      }
	  }
    else
    {
      str.insert("&");  // Non era un escape!
    }
  }
  
	return str;
}

void WriteXmlString(ostream& outf, const char* str)
{
	for (int i = 0; str[i]; i++)
	{
		const char c = str[i];
		if ((c < ' ' && c != '\n' && c != '\r') || strchr("<>/&", c) != NULL)
		{
			const unsigned int n = (unsigned char)c;
			TString8 tmp; tmp.format("&#%02X;", n);
			tmp.print_on(outf);
		}
		else
			outf << c;
	}
}

void WriteXmlColor(ostream& out, COLOR rgb)
{
  TString8 str;
  str.format("#%02X%02X%02X", XVT_COLOR_GET_RED(rgb), XVT_COLOR_GET_GREEN(rgb), XVT_COLOR_GET_BLUE(rgb));
  out << str;
}

///////////////////////////////////////////////////////////
// TXmlAttr
///////////////////////////////////////////////////////////

class TXmlAttr : public TString
{
public:
	void Write(ostream& outf) const;
	TXmlAttr(const char* str) : TString(str) { }
};

void TXmlAttr::Write(ostream& outf) const
{
	outf << '"';
  WriteXmlString(outf, *this);
	outf << '"';
}

///////////////////////////////////////////////////////////
// TXmlItem
///////////////////////////////////////////////////////////

TXmlItem& TXmlItem::SetAttr(const char* strAttr, const char* strVal)
{
	if (m_Attributes == NULL)
		m_Attributes = new TAssoc_array;
  m_Attributes->remove(strAttr);
  if (strVal && *strVal)
    m_Attributes->add(strAttr, new TXmlAttr(strVal));
	return *this;
}

const TString& TXmlItem::GetAttr(const char* strAttr) const
{
	if (m_Attributes != NULL)
	{
	  const TXmlAttr* str = (const TXmlAttr*)m_Attributes->objptr(strAttr);
		if (str != NULL)
			return *str;
	}
	return EMPTY_STRING;
}

TXmlItem& TXmlItem::SetAttr(const char* strAttr, int n)
{
  TString16 str; 
  if (n != 0) str << n;
  return SetAttr(strAttr, str);
}

TXmlItem& TXmlItem::SetColorAttr(const char* strAttr, COLOR rgb)
{
  TString8 str;
  str.format("#%02X%02X%02X", XVT_COLOR_GET_RED(rgb), XVT_COLOR_GET_GREEN(rgb), XVT_COLOR_GET_BLUE(rgb));
  return SetAttr(strAttr, str);
}

int TXmlItem::GetIntAttr(const char* strAttr, int def) const
{
  const TString& str = GetAttr(strAttr);
  if (str.not_empty())
    def = atoi(str);
  return def;
}

TXmlItem& TXmlItem::SetAttr(const char* strAttr, bool b)
{
  return SetAttr(strAttr, b ? "1" : "");
}

bool TXmlItem::GetBoolAttr(const char* strAttr) const
{
  return GetIntAttr(strAttr, 0) != 0;
}

int TXmlItem::GetChildren() const
{
	int n = 0;
	if (m_Children != NULL)
		n = m_Children->items();
	return n;
}

TXmlItem* TXmlItem::GetChild(size_t n) const
{
	TXmlItem* i = NULL;
	if (m_Children != NULL)
    i = (TXmlItem*)m_Children->objptr(n);
	return i;
}


bool TXmlItem::GetWord(istream& inf, TString& str) const
{
  str.cut(0);

  int cFirstChar = EOF;
	while (!inf.eof())
	{
	  cFirstChar = inf.get();
	  if (cFirstChar <= 0 || cFirstChar > ' ')
			break;
	}
	if (cFirstChar == EOF)
		return false;
  
  str << char(cFirstChar);

	const bool bIsString = cFirstChar == '"' || cFirstChar == '\'';
	if (!bIsString)
	{
		if (strchr("<=/>", cFirstChar) != NULL)
			return true; // Simboli terminali
 		if (cFirstChar == '&')
  		str = EscapeSequence(cFirstChar, inf);
	}

	while (!inf.eof())
	{
		int c = inf.get();
    if (bIsString)
		{
   		if (c == '&')
  		  str << EscapeSequence(c, inf);
      else
      {
  			if (c >= '\0' && c <= ' ')
	  			c = ' ';
		    str << char(c);
      }
			if (c == cFirstChar)
			  break;
		}
		else
		{
			if (c >= '\0' && c <= ' ')
				break;
      if (strchr("<=/>", c))
			{
				inf.putback(char(c));
				break;
			}
   		if (c == '&')
    		str << EscapeSequence(c, inf);
      else
			  str << char(c);
		}
	}
  return str.not_empty();
}

int TXmlItem::ReadTag(istream& inf)
{
	TString str;
  if (!GetWord(inf, str))
		return -1;

	if (str[0] != '<')  // No tag = sequence of words
	{
		bool bFirstChar = true;

		while (!inf.eof())
		{
		  char c = inf.get();
			if (c == '<')
			{
				inf.putback(c);
				break;
			}
			if (bFirstChar)
			{
        if (c != ' ' && c != '=')
				  str << ' ';
        bFirstChar = false;
			}
  		if (c == '&')
				str << EscapeSequence(c, inf);
			else
			  str << c;
		}
		SetTag("");
		SetText(str);  
		return 0;
	}

	TString name, tmp; 

	bool bChildrenFollow = true;
	GetWord(inf, m_strTag);
	if (m_strTag == "/")     // Sto leggendo un tag di chiusura del tipo </SOAP-ENV>
	{
		bChildrenFollow = false;
    GetWord(inf, tmp);
    m_strTag << tmp;
	}

	while (GetWord(inf, name))
	{
    if (name[0] == '>')
			return bChildrenFollow ? +1 : 0;

		if (name[0] == '/' || name[0] == '?')  // Gestisce i casi "/>"  e "?>")
		{
			bChildrenFollow = false;
			continue;
		}

		// Ho letto un nome di attributo
		GetWord(inf, tmp);
    if (tmp.empty() || tmp[0] != '=')
			break;
		// Leggo il valore dell'attributo
    GetWord(inf, tmp);
    const int len = tmp.len();
    if (len >= 2 && (tmp[0] == '"' || tmp[0] == '\'') && tmp[len-1] == tmp[0])
    { tmp.rtrim(1); tmp.ltrim(1); }
 		SetAttr(name, tmp);
	}
	return -1;
}

TXmlItem& TXmlItem::AddChild(TXmlItem* pItem)
{
	if (m_Children == NULL)
    m_Children = new TArray;
	if (pItem == NULL)
    pItem = new TXmlItem;
	m_Children->add(pItem);
	return *pItem;
}

TXmlItem& TXmlItem::AddChild(const char* strTagName)
{
	TXmlItem& i = AddChild((TXmlItem*)NULL);
	i.SetTag(strTagName);
	return i;
}

TXmlItem& TXmlItem::AddSoapString(const char* name, const char* value, bool typized)
{
	TXmlItem& xmlVar = AddChild(name);
	if (typized)
	  xmlVar.SetAttr("xsi:type", "xsd:string");
	if (value && *value)
		xmlVar.AddChild("").SetText(value);
	return xmlVar;
}

TXmlItem& TXmlItem::AddSoapInt(const char* name, int value, bool typized)
{
	TXmlItem& xmlVar = AddChild(name);
	if (typized)
  	xmlVar.SetAttr("xsi:type", "xsd:int");
	TString16 str; str << value;
  xmlVar.AddChild("").SetText(str);
	return xmlVar;
}

void TXmlItem::RemoveLastChild()
{
	if (m_Children != NULL)
    m_Children->destroy(m_Children->last());
}

bool TXmlItem::Read(istream& inf)
{
  Destroy();
	int res = ReadTag(inf);

  // Ignora la eventuale riga <?xml version="1.0" encoding="UTF-8" ?>
  if (res == 0 && GetTag()[0] == '?') 
  {
    Destroy();
    res = ReadTag(inf);
  }

	if (res > 0)  // There are children ahead
	{
		while (!inf.eof())
		{
		  TXmlItem& item = AddChild("/");  // Add dummy child
		  if (item.Read(inf))
			{
				if (item.m_strTag[0] == '/')
					break;
			}
			else
				break;
		}
		RemoveLastChild();                  // Remove dummy child
	}
	return res >= 0;
}

TXmlItem* TXmlItem::ForEach(XmlItemCallback cb, long jolly)
{
	if (cb(*this, jolly))
		return this;

	for (int n = 0; ; n++)
	{
		TXmlItem* c = GetChild(n);
		if (c == NULL)
			break;
		c = c->ForEach(cb, jolly);
  	if (c)
	  	return c;
	}

	return NULL;
}

static bool GetEnclosedTextCallback(TXmlItem& item, long jolly)
{
  TString* strText = (TString*)jolly;
	const TString& str = item.GetText();
	if (!str.empty())
	{
		if (!strText->empty())
			*strText << " ";
	  *strText << str;
	}
	return false;
}

bool TXmlItem::GetEnclosedText(TString& text) const
{
  text.cut(0);
	((TXmlItem*)this)->ForEach(GetEnclosedTextCallback, (long)&text);
	return text.not_empty();
}

TXmlItem& TXmlItem::AddEnclosedText(const char* str)
{
  TXmlItem* item = FindFirst("");
	if (item == NULL)
		item = &AddChild("");
  if (item->m_strText == NULL)
    item->SetText(str);
  else
    *item->m_strText << str;
	return *item;
}

TXmlItem& operator<<(TXmlItem& item, const char* str)
{
	item.AddEnclosedText(str);
	return item;
}

void TXmlItem::Write(ostream& outf, int tab) const
{
  if (tab == 0)
	  outf << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>";

	if (!GetTag().empty())
	{
		Spaces(outf, tab);
	  outf << '<';
    GetTag().print_on(outf);
		if (m_Attributes != NULL)
		{
      TAssoc_array& ass = *m_Attributes;
      FOR_EACH_ASSOC_OBJECT(ass, h, k, a)
      {
				outf << ' ' << k << '=';
				const TXmlAttr* attr = (const TXmlAttr*)a;
				attr->Write(outf);
      }
		}
		if (GetChildren() > 0)
		{
			int n;
			
			outf << '>';
      for (n = 0; ; n++)
			{
        TXmlItem* c = GetChild(n);
				if (c == NULL)
					break;
				c->Write(outf, tab+1);
			}
			if (GetChild(n-1)->GetText().empty())
			  Spaces(outf, tab);
			outf << '<' << '/';
      GetTag().print_on(outf);
      outf << '>';
		}
		else
		  outf << ' ' << '/' << '>';
	}
	else
  {
    WriteXmlString(outf, GetText());
  }
}

void TXmlItem::AsString(TString& str) const
{
	for (size_t nSize = 8192; ; nSize *= 2)
	{
	  char* buf = str.get_buffer(nSize); 
		memset(buf, 0, nSize);
#ifdef WIN32
		ostrstream outf(buf, nSize);
#else
		ostringstream outf(buf);
#endif

		Write(outf, 0);
		if (buf[nSize-1] == '\0')
			break;
	}
}

void TXmlItem::Save(const char* strFilename) const
{
	ofstream outf(strFilename);
	Write(outf, 0);
}

bool TXmlItem::Load(const char* strFilename)
{
  ifstream qry(strFilename);
  return Read(qry);
}

static bool FindFirstCallback(TXmlItem& item, long jolly)
{
  const char* strTag = (const char*)jolly;
	return item.GetTag() == strTag;
}

void TXmlItem::Destroy()
{
  m_strTag.cut(0);
  if (m_strText)
    m_strText->cut(0);
	if (m_Attributes)
    m_Attributes->destroy();
	if (m_Children)
    m_Children->destroy();
}

TXmlItem* TXmlItem::FindFirst(const char* strTag) const
{
	return ((TXmlItem*)this)->ForEach(FindFirstCallback, (long)strTag);
}

TXmlItem* TXmlItem::FindFirstChild(const char* strTag) const
{
	for (int i = 0; i < GetChildren(); i++)
  {
    TXmlItem* c = GetChild(i);
    if (c->GetTag() == strTag)
      return c;
  }
  return NULL;
}


TXmlItem::TXmlItem()
        : m_strTag(15), m_strText(NULL), m_Attributes(NULL), m_Children(NULL)
{ }

TXmlItem::~TXmlItem()
{ 
  if (m_strText)
    delete m_strText;
	if (m_Attributes)
		delete m_Attributes;
	if (m_Children)
		delete m_Children;
} 

void save_html_head(ostream& out, const char* title)
{
  out << "<head>" << endl;
  out << " <title>" << title << "</title>" << endl;
  out << " <meta name=Author content=\"" << user() << "\" />" << endl;
  out << " <meta name=Generator content=\"" << dongle().product() << "\"/>" << endl;
  out << " <meta name=Vendor content=\"" << dongle().reseller() << "\"/>" << endl;
  out << " <style type=\"text/css\">" << endl;
  out << "  th = { background-color:"; WriteXmlColor(out, BTN_BACK_COLOR); out << " }" << endl;
  out << " </style>" << endl;
  out << "</head>" << endl;
}