#include <wx/wx.h>
#include "wx/mstream.h"
#include <wx/wfstream.h>

#include "xml.h"

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

wxOutputStream& operator<<(wxOutputStream& outf, const wxChar* str)
{
	if (str && *str)
    outf.Write(str, wxStrlen(str));
	return outf;
}

wxOutputStream& operator<<(wxOutputStream& outf, wxString str)
{
	if (!str.IsEmpty())
    outf.Write(str, str.Length());
	return outf;
}

wxInputStream& operator>>(wxInputStream& inf, wxString& str)
{
	const off_t nStart = inf.TellI();
	wxChar* buf = str.GetWriteBuf(1024); *buf = '\0';
	inf.Read(buf, 1024);
	str.UngetWriteBuf();
	const int nEol = str.Find('\n');
	if (nEol >= 0)
	{
    if (str[(size_t)(nEol+1)] > '\0')
      inf.SeekI(nStart+nEol+1, (wxSeekMode) 0);
		str.Truncate(nEol);
		str.Trim();
	}
	else
  {
    if (!str.IsEmpty())
		  inf.SeekI(nStart+str.Length(), (wxSeekMode) 0);
  }

	return inf;
}

void Spaces(wxOutputStream& outf, int nSpaces)
{
	outf << "\n";
	if (nSpaces > 0)
	{
		wxString str; 
		str.Append(' ', nSpaces);
		outf << str;
	}
}

int hex2int(const wxChar* 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;
}

wxString EscapeSequence(char cStart, wxInputStream& inf)
{
	wxString str;

	if (cStart == '&')
	{
		for (wxChar c = inf.GetC(); c != ';'; c = inf.GetC())
			str += c;
    unsigned n = 0;
    sscanf(str, "#%X", &n);
    str = char(n);
	}
  else
    str = cStart;
	
	return str;
}

void WriteXmlString(wxOutputStream& outf, const wxChar* str)
{
	for (int i = 0; str[i]; i++)
	{
		wxChar c = str[i];
		if (c < 0 || c > 'z' || strchr("<>/&", c) != NULL)
		{
			unsigned int n = (unsigned char)c;
			char str[8]; sprintf(str, "&#%02X;", n);
			outf << str;
		}
		else
			outf << c;
	}
}

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

class TXmlAttr : public wxObject
{
public:
  wxString m_str;

	void Write(wxOutputStream& outf) const;
	
	TXmlAttr(const wxChar* str) : m_str(str) { }
};

void TXmlAttr::Write(wxOutputStream& outf) const
{
	if (m_str.IsEmpty())
	{
		outf << "\"\"";
	}
	else
	{
		if (m_str.IsNumber())
			outf << m_str;
		else
			outf << "\"" << m_str << "\"";
	}
}

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

IMPLEMENT_DYNAMIC_CLASS(TXmlItem, wxObject)

TXmlItem& TXmlItem::SetAttr(const wxChar* strAttr, const wxChar* strVal)
{
	if (m_Attributes == NULL)
	{
		m_Attributes = new wxHashTable(wxKEY_STRING, 17);
    m_Attributes->DeleteContents(true);
	}
  else
    m_Attributes->Delete(strAttr);
  m_Attributes->Put(strAttr, new TXmlAttr(strVal));
	return *this;
}

wxString TXmlItem::GetAttr(const wxChar* strAttr) const
{
	wxString strResult;
	if (m_Attributes != NULL)
	{
	  const TXmlAttr* str = (const TXmlAttr*)m_Attributes->Get(strAttr);
		if (str != NULL)
			strResult = str->m_str;
	}
	return strResult;
}

TXmlItem& TXmlItem::SetAttr(const wxChar* strAttr, long nVal)
{
  return SetAttr(strAttr, wxString::Format("%ld", nVal));
}

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

TXmlItem* TXmlItem::GetChild(size_t n) const
{
	TXmlItem* i = NULL;
	if (m_Children != NULL && n < m_Children->GetCount())
	{
		wxNode* pNode = m_Children->Item(n);
		if (pNode != NULL)
		  i = (TXmlItem*)pNode->GetData();
	}
	return i;
}


wxString TXmlItem::GetWord(wxInputStream& inf) const
{
	wxString str;

	int cFirstChar = EOF;
	while (!inf.Eof())
	{
	  cFirstChar = inf.GetC();
	  if (cFirstChar <= 0 || cFirstChar > ' ')
			break;
	}
	if (cFirstChar == EOF)
		return str;

	const bool bIsString = cFirstChar == '"' || cFirstChar == '\'';
	if (bIsString)
		str = "";
	else
	{
		str = char(cFirstChar);
		if (strchr("<=/>", cFirstChar))
			return str;            // Simboli terminali
	}

	while (!inf.Eof())
	{
		int c = inf.GetC();
    if (bIsString)
		{
			if (c == cFirstChar)
			  break;
			if (c >= '\0' && c <= ' ')
				c = ' ';
		  str += wxChar(c);
		}
		else
		{
			if (c >= '\0' && c <= ' ')
				break;
      if (strchr("<=/>", c))
			{
				inf.Ungetch(char(c));
				break;
			}
      if (c == '&')
        str += EscapeSequence(c, inf);
      else
			  str += wxChar(c);
		}
	}
  return str;
}

int TXmlItem::ReadTag(wxInputStream& inf)
{
	wxString str = GetWord(inf);
  if (str.IsEmpty())
		return -1;

	if (str[0u] != '<')  // No tag = sequence of words
	{
		bool bFirstChar = true;
		while (!inf.Eof())
		{
		  wxChar c = inf.GetC();
			if (c == '<')
			{
				inf.Ungetch(c);
				break;
			}
			if (bFirstChar)
			{
        if (c != ' ' && c != '=')
					str << ' ';
        bFirstChar = false;
			}
  		if (c == '&')
				str += EscapeSequence(c, inf);
			else
			  str << c;
		}
		SetTag("");
		SetText(str);  
		return 0;
	}

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

	wxString name = GetWord(inf);
	while (!name.IsEmpty())
	{
    if (name[ 0u] == '>')
			return bChildrenFollow ? +1 : 0;

		if (name[0u] == '/')
		{
			bChildrenFollow = false;
			name = GetWord(inf);
			continue;
		}

		// Ho letto un nome di attributo
		wxString str = GetWord(inf);
    if (str.IsEmpty() || str[0u] != '=')
			break;
		// Leggo il valore dell'attributo
		str = GetWord(inf);
    const size_t len = str.Len();
    if (len >= 2 && (str[0] == '"' || str[0] == '\'') && str[len-1] == str[0])
    {
			str = str.Mid(1, len - 2);
		}
 		SetAttr(name, str);

		name = GetWord(inf);
	}
	return -1;
}

TXmlItem& TXmlItem::AddChild(TXmlItem* pItem)
{
	if (m_Children == NULL)
	{
    m_Children = new wxList;
		m_Children->DeleteContents(true);
	}
	if (pItem == NULL)
    pItem = new TXmlItem;
	m_Children->Append(pItem);
	return *pItem;
}

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

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

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

wxString TXmlItem::GetSoapString(const wxChar* tag, const wxChar* def) const
{
  const TXmlItem* i = FindFirst(tag);
  return i != NULL ? i->GetEnclosedText().Trim() : def;
}
 
long TXmlItem::GetSoapInt(const wxChar* tag, long def) const
{
  const wxString str = GetSoapString(tag);
  long n; 
  if (str.ToLong(&n))
    def = n;
  return def;
}

void TXmlItem::RemoveLastChild()
{
	if (m_Children != NULL)
	{
    wxNode* n = m_Children->GetLast();
		if (n != NULL)
      m_Children->DeleteNode(n);
	}
}

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

	// Ignora la eventuale riga <?xml version="1.0" encoding="UTF-8" ?>
  if (res == 0 && GetTag()[0] == '?') 
    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[0u] == '/')
					break;
			}
			else
				break;
		}
		RemoveLastChild();                  // Remove dummy child
	}
	return res >= 0;
}

TXmlItem* TXmlItem::ForEach(XmlItemCallback cb, void* 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, void* jolly)
{
  wxString* strText = (wxString*)jolly;
	const wxString& str = item.GetText();
	if (!str.IsEmpty())
	{
		if (!strText->IsEmpty())
			*strText << " ";
	  *strText << str;
	}
	return false;
}

wxString TXmlItem::GetEnclosedText() const
{
	wxString text;
	((TXmlItem*)this)->ForEach(GetEnclosedTextCallback, &text);
	return text;
}

TXmlItem& TXmlItem::AddEnclosedText(const wxChar* str)
{
  TXmlItem* item = FindFirst("");
	if (item == NULL)
		item = &AddChild("");
  item->m_strText += str;
	return *item;
}

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

void TXmlItem::Write(wxOutputStream& outf, int tab) const
{
	if (!GetTag().IsEmpty())
	{
		Spaces(outf, tab);
	  outf << "<" << GetTag();
		if (m_Attributes != NULL)
		{
      m_Attributes->BeginFind();
      for (wxHashTable::Node* pNode = m_Attributes->Next(); pNode; pNode = m_Attributes->Next())
			{
				outf << " " << pNode->GetKeyString() << "=";
				const TXmlAttr* attr = (const TXmlAttr*)pNode->GetData();
				attr->Write(outf);
			}
		}
		if (GetChildren() > 0)
		{
			outf << ">";
			int n;
			
      for (n = 0; ; n++)
			{
        TXmlItem* c = GetChild(n);
				if (c == NULL)
					break;
				c->Write(outf, tab+1);
			}
			if (GetChild(n-1)->GetText().IsEmpty())
			  Spaces(outf, tab);
			outf << "</" << GetTag() << ">";
		}
		else
		  outf << " />";
	}
	else
		outf << GetText();
}

wxString TXmlItem::AsString() const
{
	wxString str;
	for (size_t nSize = 8192; ; nSize *= 2)
	{
	  wxChar* buf = str.GetWriteBuf(nSize); 
		memset(buf, 0, nSize);
		wxMemoryOutputStream outf(buf, nSize);
		Write(outf, 0);
		str.UngetWriteBuf();
		if (buf[nSize-1] == '\0')
			break;
	}

	return str;
}

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

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

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

TXmlItem::TXmlItem()
        : m_Attributes(NULL), m_Children(NULL)
{ }

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