
///////////////////////////////////////////////////////////
//                                                       //
//                         SAGA                          //
//                                                       //
//      System for Automated Geoscientific Analyses      //
//                                                       //
//           Application Programming Interface           //
//                                                       //
//                  Library: SAGA_API                    //
//                                                       //
//-------------------------------------------------------//
//                                                       //
//                     metadata.cpp                      //
//                                                       //
//          Copyright (C) 2009 by Olaf Conrad            //
//                                                       //
//-------------------------------------------------------//
//                                                       //
// This file is part of 'SAGA - System for Automated     //
// Geoscientific Analyses'.                              //
//                                                       //
// This library is free software; you can redistribute   //
// it and/or modify it under the terms of the GNU Lesser //
// General Public License as published by the Free       //
// Software Foundation, either version 2.1 of the        //
// License, or (at your option) any later version.       //
//                                                       //
// This library is distributed in the hope that it will  //
// be useful, but WITHOUT ANY WARRANTY; without even the //
// implied warranty of MERCHANTABILITY or FITNESS FOR A  //
// PARTICULAR PURPOSE. See the GNU Lesser General Public //
// License for more details.                             //
//                                                       //
// You should have received a copy of the GNU Lesser     //
// General Public License along with this program; if    //
// not, see <http://www.gnu.org/licenses/>.              //
//                                                       //
//-------------------------------------------------------//
//                                                       //
//    contact:    Olaf Conrad                            //
//                Institute of Geography                 //
//                University of Hamburg                  //
//                Germany                                //
//                                                       //
//    e-mail:     oconrad@saga-gis.org                   //
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
#include <wx/xml/xml.h>
#include <wx/wfstream.h>
#include <wx/sstream.h>
#include <wx/mstream.h>
#include <wx/protocol/http.h>

#include "metadata.h"
#include "table.h"


///////////////////////////////////////////////////////////
//                                                       //
//                                                       //
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
CSG_MetaData::CSG_MetaData(void)
{
	_On_Construction();
}

bool CSG_MetaData::Create(void)
{
	return( true );
}

//---------------------------------------------------------
CSG_MetaData::CSG_MetaData(const CSG_MetaData &MetaData)
{
	_On_Construction();

	Create(MetaData);
}

bool CSG_MetaData::Create(const CSG_MetaData &MetaData)
{
	return( Assign(MetaData) );
}

//---------------------------------------------------------
CSG_MetaData::CSG_MetaData(const CSG_String &File, const SG_Char *Extension)
{
	_On_Construction();

	Create(File, Extension);
}

bool CSG_MetaData::Create(const CSG_String &File, const SG_Char *Extension)
{
	return( Load(File, Extension) );
}

//---------------------------------------------------------
CSG_MetaData::CSG_MetaData(CSG_File &Stream)
{
	_On_Construction();

	Create(Stream);
}

bool CSG_MetaData::Create(CSG_File &Stream)
{
	return( Load(Stream) );
}

//---------------------------------------------------------
CSG_MetaData::CSG_MetaData(CSG_MetaData *pParent)
{
	_On_Construction();

	m_pParent = pParent;
}

//---------------------------------------------------------
void CSG_MetaData::_On_Construction(void)
{
	m_pParent = NULL;

	m_Children.Create(sizeof(CSG_MetaData **), 0, TSG_Array_Growth::SG_ARRAY_GROWTH_1);
}

//---------------------------------------------------------
CSG_MetaData::~CSG_MetaData(void)
{
	Destroy();
}

//---------------------------------------------------------
void CSG_MetaData::Destroy(void)
{
	CSG_MetaData **m_pChildren = (CSG_MetaData **)m_Children.Get_Array();

	for(int i=0; i<Get_Children_Count(); i++)
	{
		delete(m_pChildren[i]);
	}

	m_Children.Destroy();

//	m_pParent = NULL;

//	m_Name       .Clear();
//	m_Content    .Clear();

	m_Prop_Names .Clear();
	m_Prop_Values.Clear();
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
CSG_MetaData * CSG_MetaData::Add_Child(void)
{
	return( Ins_Child(-1) );
}

CSG_MetaData * CSG_MetaData::Add_Child(const CSG_String &Name)
{
	return( Ins_Child(Name, -1) );
}

CSG_MetaData * CSG_MetaData::Add_Child(const CSG_String &Name, const CSG_String &Content)
{
	return( Ins_Child(Name, Content, -1) );
}

CSG_MetaData * CSG_MetaData::Add_Child(const CSG_String &Name, double Content)
{
	return( Ins_Child(Name, SG_Get_String(Content, -16), -1) );
}

CSG_MetaData * CSG_MetaData::Add_Child(const CSG_String &Name, int Content)
{
	return( Ins_Child(Name, CSG_String::Format(SG_T("%d"), Content), -1) );
}

CSG_MetaData * CSG_MetaData::Add_Child(const CSG_String &Name, sLong Content)
{
	return( Ins_Child(Name, CSG_String::Format(SG_T("%lld"), Content), -1) );
}

CSG_MetaData * CSG_MetaData::Add_Child(const CSG_MetaData &MetaData, bool bAddChildren)
{
	return( Ins_Child(MetaData, -1, bAddChildren) );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
CSG_MetaData * CSG_MetaData::Ins_Child(int Position)
{
	if( !m_Children.Inc_Array() )
	{
		return( NULL );
	}

	CSG_MetaData **pChildren = (CSG_MetaData **)m_Children.Get_Array();

	if( Position < 0 || Position >= Get_Children_Count() )
	{
		Position = Get_Children_Count() - 1;
	}

	for(int i=Get_Children_Count()-1; i>Position; i--)
	{
		pChildren[i] = pChildren[i - 1];
	}

	return( pChildren[Position] = new CSG_MetaData(this) );
}

//---------------------------------------------------------
CSG_MetaData * CSG_MetaData::Ins_Child(const CSG_String &Name, const CSG_String &Content, int Position)
{
	CSG_MetaData *pChild = Ins_Child(Position);

	if( pChild )
	{
		pChild->m_Name    = Name;
		pChild->m_Content = Content;
	}

	return( pChild );
}

CSG_MetaData * CSG_MetaData::Ins_Child(const CSG_String &Name, int Position)
{
	return( Ins_Child(Name, CSG_String(""), Position) );
}

CSG_MetaData * CSG_MetaData::Ins_Child(const CSG_String &Name, double Content, int Position)
{
	return( Ins_Child(Name, SG_Get_String(Content, -16), Position) );
}

CSG_MetaData * CSG_MetaData::Ins_Child(const CSG_String &Name, int Content, int Position)
{
	return( Ins_Child(Name, CSG_String::Format(SG_T("%d"), Content), Position) );
}

CSG_MetaData * CSG_MetaData::Ins_Child(const CSG_String &Name, sLong Content, int Position)
{
	return( Ins_Child(Name, CSG_String::Format(SG_T("%lld"), Content), Position) );
}

CSG_MetaData * CSG_MetaData::Ins_Child(const CSG_MetaData &MetaData, int Position, bool bAddChildren)
{
	CSG_MetaData *pChild = Ins_Child(Position);

	if( pChild )
	{
		pChild->Assign(MetaData, bAddChildren);
	}

	return( pChild );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CSG_MetaData::Mov_Child(int from_Index, int to_Index)
{
	if( from_Index < 0 || from_Index >= Get_Children_Count()
	||    to_Index < 0 ||   to_Index >= Get_Children_Count() )
	{
		return( false );
	}

	if( from_Index != to_Index )
	{
		CSG_MetaData **pChildren = (CSG_MetaData **)m_Children.Get_Array();
		CSG_MetaData  *pChild    = pChildren[from_Index];

		if( from_Index < to_Index )
		{
			for(int i=from_Index; i<to_Index; i++)
			{
				pChildren[i] = pChildren[i + 1];
			}
		}
		else // if( from_Index > to_Index )
		{
			for(int i=from_Index; i>to_Index; i--)
			{
				pChildren[i] = pChildren[i - 1];
			}
		}

		pChildren[to_Index] = pChild;
	}

	return( true );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CSG_MetaData::Del_Child(int Index)
{
	if( Index >= 0 && Index < Get_Children_Count() )
	{
		CSG_MetaData **pChildren = (CSG_MetaData **)m_Children.Get_Array();

		delete(pChildren[Index]);

		for(int i=Index, j=Index+1; j<Get_Children_Count(); i++, j++)
		{
			pChildren[i] = pChildren[j];
		}

		m_Children.Dec_Array();

		return( true );
	}

	return( false );
}

//---------------------------------------------------------
bool CSG_MetaData::Del_Child(const CSG_String &Name)
{
	if( Name.Find('.') > 0 )
	{
		CSG_MetaData *pParent = Get_Child(Name.BeforeLast('.'));

		if( pParent )
		{
			return( pParent->Del_Child(Name.AfterLast('.')) );
		}
	}

	return( Del_Child(_Get_Child(Name)) );
}

//---------------------------------------------------------
bool CSG_MetaData::Add_Children(const CSG_MetaData &MetaData)
{
	if( &MetaData != this )
	{
		for(int i=0; i<MetaData.Get_Children_Count(); i++)
		{
			Add_Child(MetaData[i], true);
		}
	}

	return( true );
}

//---------------------------------------------------------
/**
  * Deletes children if depth level is reached, i.e. if depth level equals zero
  * all children of the node will be deleted, if it is one only the grandchildren
  * will be affected, and so on.
  * If Name is a valid string, only those children are taken into account that
  * have this name.
*/
//---------------------------------------------------------
bool CSG_MetaData::Del_Children(int Depth, const SG_Char *Name)
{
	if( Depth < 0 )
	{
		// nop
	}
	else if( Name && *Name )
	{
		for(int i=Get_Children_Count()-1; i>=0; i--)
		{
			if( Get_Child(i)->Get_Name().CmpNoCase(Name) )
			{
				Get_Child(i)->Del_Children(Depth, Name);
			}
			else if( Depth > 0 )
			{
				Get_Child(i)->Del_Children(Depth - 1, Name);
			}
			else
			{
				Del_Child(i);
			}
		}
	}
	else if( Depth > 0 )
	{
		for(int i=0; i<Get_Children_Count(); i++)
		{
			Get_Child(i)->Del_Children(Depth - 1, Name);
		}
	}
	else
	{
		for(int i=0; i<Get_Children_Count(); i++)
		{
			delete(Get_Child(i));
		}

		m_Children.Destroy();
	}

	return( true );
}

//---------------------------------------------------------
CSG_MetaData * CSG_MetaData::Get_Child(const CSG_String &Name) const
{
	if( Name.Find('.') > 0 )
	{
		CSG_MetaData *pEntry = Get_Child(Name.BeforeFirst('.'));

		if( pEntry )
		{
			return( pEntry->Get_Child(Name.AfterFirst('.')) );
		}
	}

	return( Get_Child(_Get_Child(Name)) );
}

//---------------------------------------------------------
int CSG_MetaData::_Get_Child(const CSG_String &Name) const
{
	for(int i=0; i<Get_Children_Count(); i++)
	{
		if( Name.CmpNoCase(Get_Child(i)->Get_Name()) == 0 )
		{
			return( i );
		}
	}

	return( -1 );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CSG_MetaData::Cmp_Name(const CSG_String &String, bool bNoCase) const
{
	return( bNoCase ? !m_Name.CmpNoCase(String) : !m_Name.Cmp(String) );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
const SG_Char * CSG_MetaData::Get_Content(const CSG_String &Name) const
{
	if( Name.is_Empty() )
	{
		return( m_Content );
	}

	CSG_MetaData *pEntry = Get_Child(Name);

	return( pEntry ? pEntry->Get_Content().c_str() : NULL );
}

//---------------------------------------------------------
bool CSG_MetaData::Get_Content(const CSG_String &Name, CSG_String &Value) const
{
	const SG_Char *cString = Name.is_Empty() ? Get_Content().c_str() : Get_Content(Name);

	if( cString )
	{
		Value = cString;

		return( true );
	}

	return( false );
}

//---------------------------------------------------------
bool CSG_MetaData::Get_Content(const CSG_String &Name, double &Value) const { CSG_String s; return( Get_Content(Name, s) && s.asDouble  (Value) ); }
bool CSG_MetaData::Get_Content(const CSG_String &Name, int    &Value) const { CSG_String s; return( Get_Content(Name, s) && s.asInt     (Value) ); }
bool CSG_MetaData::Get_Content(const CSG_String &Name, sLong  &Value) const { CSG_String s; return( Get_Content(Name, s) && s.asLongLong(Value) ); }


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
void CSG_MetaData::Fmt_Content(const char *Format, ...)
{
	wxString s; va_list argptr;

#ifdef _SAGA_LINUX
	wxString _Format(Format); _Format.Replace("%s", "%ls");	// workaround as we only use wide characters since wx 2.9.4 so interpret strings as multibyte
	va_start(argptr, _Format);
	s.PrintfV(_Format, argptr);
#else
	va_start(argptr, Format);
	s.PrintfV(Format, argptr);
#endif

	m_Content = CSG_String(&s);

	va_end(argptr);
}

//---------------------------------------------------------
void CSG_MetaData::Fmt_Content(const wchar_t *Format, ...)
{
	wxString s; va_list argptr;

#ifdef _SAGA_LINUX
	wxString _Format(Format); _Format.Replace("%s", "%ls");	// workaround as we only use wide characters since wx 2.9.4 so interpret strings as multibyte
	va_start(argptr, _Format);
	s.PrintfV(_Format, argptr);
#else
	va_start(argptr, Format);
	s.PrintfV(Format, argptr);
#endif

	m_Content = CSG_String(&s);

	va_end(argptr);
}

//---------------------------------------------------------
bool CSG_MetaData::Cmp_Content(const CSG_String &String, bool bNoCase) const
{
	return( bNoCase ? !m_Content.CmpNoCase(String) : !m_Content.Cmp(String) );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CSG_MetaData::Add_Property(const CSG_String &Name, const CSG_String &Value)
{
	if( !Value.is_Empty() &&_Get_Property(Name) < 0 )
	{
		m_Prop_Names .Add(Name );
		m_Prop_Values.Add(Value);

		return( true );
	}

	return( false );
}

bool CSG_MetaData::Add_Property(const CSG_String &Name, double Value)
{
	return( Add_Property(Name, CSG_String::Format(SG_T("%f"), Value)) );
}

bool CSG_MetaData::Add_Property(const CSG_String &Name, int Value)
{
	return( Add_Property(Name, CSG_String::Format(SG_T("%d"), Value)) );
}

bool CSG_MetaData::Add_Property(const CSG_String &Name, sLong Value)
{
	return( Add_Property(Name, CSG_String::Format(SG_T("%lld"), Value)) );
}

//---------------------------------------------------------
bool CSG_MetaData::Del_Property(const CSG_String &Name)
{
	for(int i=0; i<Get_Property_Count(); i++)
	{
		if( !Get_Property_Name(i).CmpNoCase(Name) )
		{
			return( Del_Property(i) );
		}
	}

	return( false );
}

bool CSG_MetaData::Del_Property(int i)
{
	if( i >= 0 && i < Get_Property_Count() )
	{
		m_Prop_Names .Del(i);
		m_Prop_Values.Del(i);

		return( true );
	}

	return( false );
}

//---------------------------------------------------------
bool CSG_MetaData::Set_Property(const CSG_String &Name, const CSG_String &Value, bool bAddIfNotExists)
{
	int Index = _Get_Property(Name);

	if( Index >= 0 )
	{
		m_Prop_Values[Index] = Value;

		return( true );
	}
	else if( bAddIfNotExists )
	{
		m_Prop_Names .Add(Name);
		m_Prop_Values.Add(Value);

		return( true );
	}

	return( false );
}

bool CSG_MetaData::Set_Property(const CSG_String &Name, double Value, bool bAddIfNotExists)
{
	return( Set_Property(Name, CSG_String::Format(SG_T("%f"), Value, bAddIfNotExists)) );
}

bool CSG_MetaData::Set_Property(const CSG_String &Name, int Value, bool bAddIfNotExists)
{
	return( Set_Property(Name, CSG_String::Format(SG_T("%d"), Value, bAddIfNotExists)) );
}

bool CSG_MetaData::Set_Property(const CSG_String &Name, sLong Value, bool bAddIfNotExists)
{
	return( Set_Property(Name, CSG_String::Format(SG_T("%lld"), Value, bAddIfNotExists)) );
}

//---------------------------------------------------------
bool CSG_MetaData::Get_Property(const CSG_String &Name, CSG_String &Value)	const
{
	const SG_Char *cString = Get_Property(Name);

	if( cString )
	{
		Value = cString;

		return( true );
	}

	return( false );
}

bool CSG_MetaData::Get_Property(const CSG_String &Name, double &Value) const { CSG_String s; return( Get_Property(Name, s) && s.asDouble  (Value) ); }
bool CSG_MetaData::Get_Property(const CSG_String &Name, int    &Value) const { CSG_String s; return( Get_Property(Name, s) && s.asInt     (Value) ); }
bool CSG_MetaData::Get_Property(const CSG_String &Name, sLong  &Value) const { CSG_String s; return( Get_Property(Name, s) && s.asLongLong(Value) ); }

//---------------------------------------------------------
bool CSG_MetaData::Cmp_Property(const CSG_String &Name, const CSG_String &String, bool bNoCase) const
{
	CSG_String	s;

	return( Get_Property(Name, s) && (bNoCase ? !s.CmpNoCase(String) : !s.Cmp(String)) );
}

//---------------------------------------------------------
int CSG_MetaData::_Get_Property(const CSG_String &Name) const
{
	for(int i=0; i<m_Prop_Names.Get_Count(); i++)
	{
		if( Name.CmpNoCase(m_Prop_Names[i]) == 0 )
		{
			return( i );
		}
	}

	return( -1 );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
CSG_String CSG_MetaData::asText(int Flags) const
{
	CSG_String s;

	if( Flags == 0 )
	{
		for(int i=0; i<Get_Children_Count(); i++)
		{
			s += Get_Child(i)->Get_Name() + ":\t" + Get_Child(i)->Get_Content() + "\n";
		}
	}
	else
	{
		wxXmlDocument XML; wxXmlNode *pRoot = new wxXmlNode(NULL, wxXML_ELEMENT_NODE, Get_Name().c_str());

		XML.SetRoot(pRoot);

		_Save(pRoot);

		wxStringOutputStream Stream;

		XML.Save(Stream);

		s = &Stream.GetString();

		if( Flags == 2 ) // remove <xml>
		{
			s = s.AfterFirst('\n');
		}
	}

	return( s );
}

//---------------------------------------------------------
CSG_Table CSG_MetaData::Get_Table(int Flags) const
{
	CSG_Table t;

	t.Add_Field("NAME" , SG_DATATYPE_String);
	t.Add_Field("VALUE", SG_DATATYPE_String);

	for(int i=0; i<Get_Children_Count(); i++)
	{
		CSG_Table_Record *r = t.Add_Record();

		r->Set_Value(0, Get_Child(i)->Get_Name());
		r->Set_Value(1, Get_Child(i)->Get_Content());
	}

	return( t );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CSG_MetaData::Assign(const CSG_MetaData &MetaData, bool bAddChildren)
{
	if( &MetaData != this )
	{
		Destroy();

		Set_Name   (MetaData.Get_Name   ());
		Set_Content(MetaData.Get_Content());

		for(int i=0; i<MetaData.Get_Property_Count(); i++)
		{
			Add_Property(MetaData.Get_Property_Name(i), MetaData.Get_Property(i));
		}

		if( bAddChildren )
		{
			Add_Children(MetaData);
		}
	}

	return( true );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CSG_MetaData::Load(const CSG_String &File, const SG_Char *Extension)
{
	Destroy();

	//-----------------------------------------------------
	if( File.Find("http://") == 0 )
	{
		CSG_String s(File.Right(File.Length() - CSG_String("http://").Length()));

		return( Load_HTTP(s.BeforeFirst('/'), s.AfterFirst('/')) );
	}

	//-----------------------------------------------------
	CSG_String _File(SG_File_Make_Path("", File, Extension));

	if( !SG_File_Exists(_File) )
	{
		return( false );
	}

	//-----------------------------------------------------
	if( SG_File_Cmp_Extension(_File, "json") )
	{
		return( Load_JSON(_File) );
	}

	//-----------------------------------------------------
	wxXmlDocument XML;

	if( XML.Load(_File.c_str()) )
	{
		_Load(XML.GetRoot());

		return( true );
	}

	//-----------------------------------------------------
	return( false );
}

//---------------------------------------------------------
bool CSG_MetaData::Load(CSG_File &File)
{
	Destroy();

	wxXmlDocument XML;

	if( File.is_Reading() && XML.Load(*((wxInputStream *)File.Get_Stream())) )
	{
		_Load(XML.GetRoot());

		return( true );
	}

	return( false );
}

//---------------------------------------------------------
void CSG_MetaData::_Load(wxXmlNode *pNode)
{
	m_Name    = pNode->GetName       ().wc_str();
	m_Content = pNode->GetNodeContent().wc_str();

	//-----------------------------------------------------
	wxXmlAttribute *pProperty = pNode->GetAttributes();

	while( pProperty )
	{
		Add_Property(&pProperty->GetName(), &pProperty->GetValue());

		pProperty = pProperty->GetNext();
	}

	//-----------------------------------------------------
	wxXmlNode *pChild = pNode->GetChildren();

	while( pChild )
	{
		if( pChild->GetType() != wxXML_TEXT_NODE )
		{
			Add_Child()->_Load(pChild);
		}

		pChild = pChild->GetNext();
	}
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CSG_MetaData::Save(const CSG_String &File, const SG_Char *Extension) const
{
	wxXmlDocument XML; wxXmlNode *pRoot = new wxXmlNode(NULL, wxXML_ELEMENT_NODE, Get_Name().c_str());

	XML.SetRoot(pRoot);

	_Save(pRoot);

	if( XML.Save(SG_File_Make_Path("", File, Extension).c_str()) )
	{
		return( true );
	}

	return( false );
}

//---------------------------------------------------------
bool CSG_MetaData::Save(CSG_File &File) const
{
	wxXmlDocument XML; wxXmlNode *pRoot = new wxXmlNode(NULL, wxXML_ELEMENT_NODE, Get_Name().c_str());

	XML.SetRoot(pRoot);

	_Save(pRoot);

	if( File.is_Writing() && XML.Save(*((wxOutputStream *)File.Get_Stream())) )
	{
		return( true );
	}

	return( false );
}

//---------------------------------------------------------
void CSG_MetaData::_Save(wxXmlNode *pNode) const
{
	CSG_String Name(Get_Name()); if( Name.is_Empty() ) { Name = "NODE"; } else { Name.Replace(" ", "_"); } // name must not contain spaces!

	pNode->SetName(Name.c_str());

	pNode->SetContent(Get_Content().c_str());

	if( Get_Content().Length() > 0 || (Get_Property_Count() == 0 && Get_Children_Count() == 0) )
	{
		wxXmlNode *pChild = new wxXmlNode(pNode, wxXML_TEXT_NODE, SG_T("TEXT"));

		pChild->SetContent(Get_Content().c_str());
	}

	//-----------------------------------------------------
	for(int i=0; i<Get_Property_Count(); i++)
	{
		pNode->AddAttribute(Get_Property_Name(i).c_str(), Get_Property(i));
	}

	//-----------------------------------------------------
	for(int i=Get_Children_Count()-1; i>=0; i--)
	{
		Get_Child(i)->_Save(new wxXmlNode(pNode, wxXML_ELEMENT_NODE, Get_Child(i)->Get_Name().c_str()));
	}
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CSG_MetaData::from_XML(const char *_XML, size_t Length, const char *Encoding)
{
	Destroy();

	wxXmlDocument XML; wxMemoryInputStream Stream((const void *)_XML, Length);

#if wxCHECK_VERSION(3, 3, 0)
	if( XML.Load(Stream) )
#else
	if( XML.Load(Stream, Encoding) )
#endif
	{
		_Load(XML.GetRoot());

		return( true );
	}

	return( false );
}

//---------------------------------------------------------
bool CSG_MetaData::from_XML(const CSG_String &_XML)
{
	Destroy();

	wxXmlDocument XML; wxMemoryInputStream Stream((const void *)_XML.c_str(), sizeof(SG_Char) * _XML.Length());

	if( XML.Load(Stream) )
	{
		_Load(XML.GetRoot());

		return( true );
	}

	return( false );
}

//---------------------------------------------------------
bool CSG_MetaData::to_XML(CSG_String &_XML) const
{
	wxXmlDocument XML; wxXmlNode *pRoot = new wxXmlNode(NULL, wxXML_ELEMENT_NODE, Get_Name().c_str());

	XML.SetRoot(pRoot);

	_Save(pRoot);

	wxMemoryOutputStream Stream;

	if( XML.Save(Stream) )
	{
		CSG_Array s(sizeof(char), Stream.GetSize());

		Stream.CopyTo(s.Get_Array(), s.Get_Size());

		_XML = (const char *)s.Get_Array();

		return( true );
	}

	return( false );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CSG_MetaData::Load_HTTP(const CSG_String &Server, const CSG_String &Path, const SG_Char *Username, const SG_Char *Password)
{
	Destroy();

	//-----------------------------------------------------
	wxHTTP HTTP;

	if( Username && *Username ) { HTTP.SetUser    (Username); }
	if( Password && *Password ) { HTTP.SetPassword(Password); }

	wxString s = Server.c_str();

	if( s.Find("http://") == 0 )
	{
		s = s.Right(s.Length() - wxString("http://").Length());
	}

	if( !HTTP.Connect(s) )
	{
		return( false );
	}

	//-----------------------------------------------------
	s = Path.c_str();

	if( s[0] != '/' )
	{
		s.Prepend("/");
	}

	wxInputStream *pStream = HTTP.GetInputStream(s);

	if( !pStream )
	{
		return( false );
	}

	wxXmlDocument XML;

	if( XML.Load(*pStream) )
	{
		_Load(XML.GetRoot());

		delete(pStream);

		return( true );
	}

	delete(pStream);

	return( false );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CSG_MetaData::Load_JSON(const CSG_String &File)
{
	CSG_File	Stream;	CSG_String	JSON;

	if( Stream.Open(File, SG_FILE_R, false) && Stream.Read(JSON, (size_t)Stream.Length()) > 0 )
	{
		return( from_JSON(JSON) );
	}

	return( false );
}

//---------------------------------------------------------
bool CSG_MetaData::Save_JSON(const CSG_String &File)	const
{
	return( false );
}

//---------------------------------------------------------
bool CSG_MetaData::from_JSON(const CSG_String &JSON)
{
	Destroy();

	Set_Name("root");

	CSG_MetaData *pNode = this; const SG_Char *pc = JSON.c_str();

	while( *pc )
	{
		CSG_String Element;

		for(bool bQuota=false; *pc; )
		{
			SG_Char c = *pc++;
			
			if( c == '\0' || c == ',' )
			{
				break;
			}
			else if( c == '{' || c == '[' )
			{
				Element += c;

				break;
			}
			else if( c == '}' || c == ']' )
			{
				if( Element.is_Empty() )
				{
					Element = c;
				}
				else
				{
					pc--;
				}

				break;
			}
			else if( c == '\"' )
			{
				Element += c; bQuota = !bQuota;
			}
			else if( bQuota || (c != ' ' && c != '\n' && c != '\t') )
			{
				Element += c;
			}
		}

		//-------------------------------------------------
		if( Element.is_Empty() )
		{
			// nop
		}
		else if( Element.Find('[') >= 0 )	// array begins
		{
			pNode = pNode->Add_Child(Element.AfterFirst('\"').BeforeFirst('\"'));

			pNode->Add_Property("array", 1);
		}
		else if( Element.Find(']') >= 0 )	// array ends
		{
			if( pNode != this )
			{
				pNode = pNode->Get_Parent();
			}
		}
		else if( Element.Find('{') >= 0 )	// object begins
		{
			Element = Element.AfterFirst('\"').BeforeFirst('\"');

			if( !Element.is_Empty() )
			{
				pNode = pNode->Add_Child(Element);
			}
			else if( pNode->Get_Property("array") )
			{
				pNode = pNode->Add_Child(CSG_String::Format("%d", pNode->Get_Children_Count()));
			}
		}
		else if( Element.Find('}') >= 0 )	// object ends
		{
			if( pNode != this )
			{
				pNode = pNode->Get_Parent();
			}
		}
		else
		{
			CSG_String key(Element.AfterFirst('\"').BeforeFirst('\"'));
			CSG_String val(Element.AfterFirst(':')); val.Trim();

			if( val.Find('\"') == 0 )
			{
				val = val.AfterFirst('\"').BeforeFirst('\"');
			}

			pNode->Add_Child(key, val);
		}
	}

	return( true );
}

//---------------------------------------------------------
bool CSG_MetaData::to_JSON(CSG_String &JSON) const
{
	if( Get_Parent() )
	{
		JSON += "\"" + Get_Name() + "\": ";
	}

	if( Get_Children_Count() )
	{
		JSON += "{";

		for(int i=0; i<Get_Children_Count(); i++)
		{
			if( i > 0 )
			{
				JSON += ",";
			}

			Get_Child(i)->to_JSON(JSON);
		}

		JSON += "}";
	}
	else
	{
		JSON += " \"" + Get_Content() + "\"";
	}

	return( true );
}


///////////////////////////////////////////////////////////
//                                                       //
//                                                       //
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
CSG_HTTP::CSG_HTTP(void)
{
	m_pHTTP = NULL;
}

bool CSG_HTTP::Create(void)
{
	return( Destroy() );
}

//---------------------------------------------------------
CSG_HTTP::CSG_HTTP(const CSG_String &Server, const CSG_String &User, const CSG_String &Password)
{
	m_pHTTP = NULL;

	Create(Server, User, Password);
}

bool CSG_HTTP::Create(const CSG_String &Server, const CSG_String &User, const CSG_String &Password)
{
	Destroy();

	m_pHTTP = new wxHTTP;

	if( !User    .is_Empty() ) { m_pHTTP->SetUser    (User    .wx_str()); }
	if( !Password.is_Empty() ) { m_pHTTP->SetPassword(Password.wx_str()); }

	wxString Host= Server.wx_str(); unsigned short Port = 80;

	#define SERVER_TRIM(s, p) { wxString sp(p); sp += "://"; if( s.Find(p) == 0 ) { s = s.Right(s.Length() - sp.Length()); } }

	SERVER_TRIM(Host, "https");
	SERVER_TRIM(Host, "http" );

	if( Host.Find(":") >= 0 )
	{
		long _Port;

		if( Host.AfterLast(':').ToLong(&_Port) )
		{
			Port = (unsigned short)_Port;
		}

		Host = Host.BeforeLast(':');
	}

	if( !m_pHTTP->Connect(Host, Port) )
	{
		Destroy();

		return( false );
	}

	return( true );
}

//---------------------------------------------------------
CSG_HTTP::~CSG_HTTP(void)
{
	Destroy();
}

bool CSG_HTTP::Destroy(void)
{
	if( m_pHTTP )
	{
		delete(m_pHTTP);

		m_pHTTP = NULL;
	}

	return( true );
}

//---------------------------------------------------------
wxInputStream * CSG_HTTP::_Request(const CSG_String &Request)
{
	if( !is_Connected() )
	{
		wxString s(Request.c_str()); if( s[0] != '/' ) { s.Prepend("/"); }

		wxInputStream *pStream = m_pHTTP->GetInputStream(s);

		if( pStream )
		{
			if( pStream->CanRead() )
			{
				return( pStream );
			}

			delete(pStream);
		}
	}

	return( NULL );
}

//---------------------------------------------------------
bool CSG_HTTP::Request(const CSG_String &Request, CSG_MetaData &Answer)
{
	wxInputStream *pStream = _Request(Request); if( !pStream ) { return( false ); }

	wxXmlDocument XML;

	if( !XML.Load(*pStream) )
	{
		delete(pStream);

		return( false );
	}

	Answer.Destroy(); Answer._Load(XML.GetRoot());

	delete(pStream);

	return( true );
}

//---------------------------------------------------------
bool CSG_HTTP::Request(const CSG_String &Request, CSG_Bytes &Answer)
{
	wxInputStream *pStream = _Request(Request); if( !pStream ) { return( false ); }

	Answer.Clear();

	while( pStream->CanRead() )
	{
		char Byte; pStream->Read(&Byte, sizeof(Byte)); Answer += Byte;
	}

	delete(pStream);

	return( true );
}

//---------------------------------------------------------
bool CSG_HTTP::Request(const CSG_String &Request, CSG_String &Answer)
{
	wxInputStream *pStream = _Request(Request); if( !pStream ) { return( false ); }

	Answer.Clear();

	while( pStream->CanRead() )
	{
		char Byte; pStream->Read(&Byte, sizeof(Byte)); Answer += Byte;
	}

	delete(pStream);

	return( true );
}

//---------------------------------------------------------
bool CSG_HTTP::Request(const CSG_String &Request, const SG_Char *File)
{
	wxInputStream *pStream = _Request(Request); if( !pStream ) { return( false ); }

	wxFileOutputStream *pFile = new wxFileOutputStream(File);

	if( !pFile )
	{
		delete(pStream);

		return( false );
	}

	pFile->Write(*pStream);

	delete(pFile);

	delete(pStream);

	return( true );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
#include <wx/protocol/ftp.h>

//---------------------------------------------------------
bool	SG_FTP_Download(const CSG_String &Target_Directory, const CSG_String &Source, const CSG_String &User, const CSG_String &Password, unsigned short Port, bool bBinary, bool bVerbose)
{
	CSG_String _Source(Source); _Source.Trim();

	if( _Source.Find("ftp://") == 0 )
	{
		_Source = _Source.Right(_Source.Length() - CSG_String("ftp://").Length());
	}

	CSG_String ftpHost = _Source.BeforeFirst('/');
	CSG_String ftpDir  = _Source.AfterFirst ('/').BeforeLast('/'); // ftpDir.Prepend("/");
	CSG_String ftpFile = _Source.AfterLast  ('/');

	//-----------------------------------------------------
	wxFTP ftp;

	if( !User    .is_Empty() ) { ftp.SetUser    (User    .wx_str()); }
	if( !Password.is_Empty() ) { ftp.SetPassword(Password.wx_str()); }

	if( !ftp.Connect(ftpHost.c_str(), Port) )
	{
		if( bVerbose )
		{
			SG_UI_Msg_Add_Error(_TL("Couldn't connect"));
		}

		return( false );
	}

	//-----------------------------------------------------
	if( !ftpDir.is_Empty() && !ftp.ChDir(ftpDir.c_str()) )
	{
		if( bVerbose )
		{
			SG_UI_Msg_Add_Error(CSG_String::Format("%s [%s]", _TL("Couldn't change to directory"), ftpDir.c_str()));
		}

		return( false );
	}

	if( ftp.GetFileSize(ftpFile.c_str()) == -1 )
	{
		if( bVerbose )
		{
			SG_UI_Msg_Add_Error(CSG_String::Format("%s [%s]", _TL("Couldn't get the file size"), ftpFile.c_str()));
		}
	}

	//-----------------------------------------------------
	wxInputStream *pStream = ftp.GetInputStream(ftpFile.c_str());

	if( !pStream )
	{
		if( bVerbose )
		{
			SG_UI_Msg_Add_Error(CSG_String::Format("%s [%s]", _TL("Couldn't get the file"), ftpFile.c_str()));
		}

		return( false );
	}

	//-----------------------------------------------------
	wxFileOutputStream *pFile = new wxFileOutputStream(SG_File_Make_Path(Target_Directory, ftpFile).c_str());

	if( !pFile )
	{
		if( bVerbose )
		{
			SG_UI_Msg_Add_Error(CSG_String::Format("%s [%s]", _TL("Couldn't create target file"), SG_File_Make_Path(Target_Directory, ftpFile).c_str()));
		}

		delete(pStream);

		return( false );
	}

	//-----------------------------------------------------
	pFile->Write(*pStream);

	delete(pFile);
	delete(pStream);

	return( true );
}


///////////////////////////////////////////////////////////
//                                                       //
//                                                       //
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
CSG_WebRequestSync::CSG_WebRequestSync(void)
{
	Create();
}

bool CSG_WebRequestSync::Create(void)
{
	Destroy();

	return( true );
}

//---------------------------------------------------------
CSG_WebRequestSync::CSG_WebRequestSync(const CSG_String &Server, const CSG_String &User, const CSG_String &Password)
{
	Create(Server, User, Password);
}

bool CSG_WebRequestSync::Create(const CSG_String &Server, const CSG_String &User, const CSG_String &Password)
{
	Destroy();

	m_Server = Server;

	if( !User.is_Empty() || !Password.is_Empty() )
	{
		#if !wxCHECK_VERSION(3, 3, 0)
		m_User = User; m_Password = Password;
		#else
		wxURI URI; URI.SetUserAndPassword(User.wx_str(), Password.wx_str());

		m_User = URI.GetUser().wx_str(); m_Password = URI.GetPassword().wx_str();
		#endif
	}

	return( true );
}

//---------------------------------------------------------
bool CSG_WebRequestSync::Destroy(void)
{
	m_Server.Clear(); m_User.Clear(), m_Password.Clear();

	return( true );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
#if !wxCHECK_VERSION(3, 3, 0)
bool CSG_WebRequestSync::Request(                          const CSG_String &Request, CSG_Bytes    &Answer) { return( false ); }
bool CSG_WebRequestSync::Request(                          const CSG_String &Request, CSG_MetaData &Answer) { return( false ); }
bool CSG_WebRequestSync::Request(                          const CSG_String &Request, CSG_String   &Answer) { return( false ); }
bool CSG_WebRequestSync::Request(                          const CSG_String &Request, const SG_Char *File ) { return( false ); }
bool CSG_WebRequestSync::Request(const CSG_String &Server, const CSG_String &Request, CSG_Bytes    &Answer) { return( false ); }
bool CSG_WebRequestSync::Request(const CSG_String &Server, const CSG_String &Request, CSG_MetaData &Answer) { return( false ); }
bool CSG_WebRequestSync::Request(const CSG_String &Server, const CSG_String &Request, CSG_String   &Answer) { return( false ); }
bool CSG_WebRequestSync::Request(const CSG_String &Server, const CSG_String &Request, const SG_Char *File ) { return( false ); }
#else

//---------------------------------------------------------
#include <wx/webrequest.h>

//---------------------------------------------------------
bool CSG_WebRequestSync::Request(const CSG_String &Request, CSG_Bytes &Answer)
{
	if( !m_Server.is_Empty() ) { wxWebSessionSync::GetDefault().SetBaseURL(m_Server.wx_str()); } auto request = wxWebSessionSync::GetDefault().CreateRequest(Request.c_str());

	#if wxCHECK_VERSION(3, 3, 2)
	if( !m_User.is_Empty() || !m_Password.is_Empty() )
	{
		request.UseBasicAuth(wxWebCredentials(m_User.wx_str(), wxSecretValue(m_Password.wx_str())));
	}
	#endif

	if( !request.Execute() ) { return( false ); }

	//-----------------------------------------------------
	wxInputStream *pStream = request.GetResponse().GetStream();

	if( pStream->IsOk() )
	{
		int Byte = pStream->GetC();

		while( Byte != wxEOF )
		{
			Answer += (BYTE)Byte;

			Byte = pStream->GetC();
		}

		return( true );
	}

	return( false );
}

//---------------------------------------------------------
bool CSG_WebRequestSync::Request(const CSG_String &Request, CSG_MetaData &Answer)
{
	if( !m_Server.is_Empty() ) { wxWebSessionSync::GetDefault().SetBaseURL(m_Server.wx_str()); } auto request = wxWebSessionSync::GetDefault().CreateRequest(Request.c_str());

	#if wxCHECK_VERSION(3, 3, 2)
	if( !m_User.is_Empty() || !m_Password.is_Empty() )
	{
		request.UseBasicAuth(wxWebCredentials(m_User.wx_str(), wxSecretValue(m_Password.wx_str())));
	}
	#endif

	if( !request.Execute() ) { return( false ); }

	//-----------------------------------------------------
	wxXmlDocument XML;

	if( XML.Load(*request.GetResponse().GetStream()) )
	{
		Answer._Load(XML.GetRoot());

		return( true );
	}

	return( false );

//	CSG_Bytes _Answer; return( CSG_WebRequestSync::Request(Request, _Answer) && Answer.from_XML((const char *)_Answer.Get_Bytes(), (size_t)_Answer.Get_Count()) );
}

//---------------------------------------------------------
bool CSG_WebRequestSync::Request(const CSG_String &Request, CSG_String &Answer)
{
	if( !m_Server.is_Empty() ) { wxWebSessionSync::GetDefault().SetBaseURL(m_Server.wx_str()); } auto request = wxWebSessionSync::GetDefault().CreateRequest(Request.c_str());

	#if wxCHECK_VERSION(3, 3, 2)
	if( !m_User.is_Empty() || !m_Password.is_Empty() )
	{
		request.UseBasicAuth(wxWebCredentials(m_User.wx_str(), wxSecretValue(m_Password.wx_str())));
	}
	#endif

	if( !request.Execute() ) { return( false ); }

	//-----------------------------------------------------
	Answer = request.GetResponse().AsString().wx_str();

	return( true );
}

//---------------------------------------------------------
bool CSG_WebRequestSync::Request(const CSG_String &Request, const SG_Char *File)
{
	if( !m_Server.is_Empty() ) { wxWebSessionSync::GetDefault().SetBaseURL(m_Server.wx_str()); } auto request = wxWebSessionSync::GetDefault().CreateRequest(Request.c_str());

	#if wxCHECK_VERSION(3, 3, 2)
	if( !m_User.is_Empty() || !m_Password.is_Empty() )
	{
		request.UseBasicAuth(wxWebCredentials(m_User.wx_str(), wxSecretValue(m_Password.wx_str())));
	}
	#endif

	if( !request.Execute() ) { return( false ); }

	//-----------------------------------------------------
	wxFileOutputStream Output(File);

	if( Output.IsOk() )
	{
		Output.Write(*request.GetResponse().GetStream());

		return( true );
	}

	return( false );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CSG_WebRequestSync::Request(const CSG_String &Server, const CSG_String &Request, CSG_Bytes &Answer)
{
	if( !Server.is_Empty() ) { wxWebSessionSync::GetDefault().SetBaseURL(Server.wx_str()); } auto request = wxWebSessionSync::GetDefault().CreateRequest(Request.c_str()); if( !request.Execute() ) { return( false ); }

	wxInputStream *pStream = request.GetResponse().GetStream();

	if( pStream->IsOk() )
	{
		int Byte = pStream->GetC();

		while( Byte != wxEOF )
		{
			Answer += (BYTE)Byte;

			Byte = pStream->GetC();
		}

		return( true );
	}

	return( false );
}

//---------------------------------------------------------
bool CSG_WebRequestSync::Request(const CSG_String &Server, const CSG_String &Request, CSG_MetaData &Answer)
{
	if( !Server.is_Empty() ) { wxWebSessionSync::GetDefault().SetBaseURL(Server.wx_str()); } auto request = wxWebSessionSync::GetDefault().CreateRequest(Request.c_str()); if( !request.Execute() ) { return( false ); }

	wxXmlDocument XML;

	if( XML.Load(*request.GetResponse().GetStream()) )
	{
		Answer._Load(XML.GetRoot());

		return( true );
	}

	return( false );

//	CSG_Bytes _Answer; return( CSG_WebRequestSync::Request(Request, _Answer) && Answer.from_XML((const char *)_Answer.Get_Bytes(), (size_t)_Answer.Get_Count()) );
}

//---------------------------------------------------------
bool CSG_WebRequestSync::Request(const CSG_String &Server, const CSG_String &Request, CSG_String &Answer)
{
	if( !Server.is_Empty() ) { wxWebSessionSync::GetDefault().SetBaseURL(Server.wx_str()); } auto request = wxWebSessionSync::GetDefault().CreateRequest(Request.c_str()); if( !request.Execute() ) { return( false ); }

	Answer = request.GetResponse().AsString().wx_str();

	return( true );
}

//---------------------------------------------------------
bool CSG_WebRequestSync::Request(const CSG_String &Server, const CSG_String &Request, const SG_Char *File)
{
	if( !Server.is_Empty() ) { wxWebSessionSync::GetDefault().SetBaseURL(Server.wx_str()); } auto request = wxWebSessionSync::GetDefault().CreateRequest(Request.c_str()); if( !request.Execute() ) { return( false ); }

	wxFileOutputStream Output(File);

	if( Output.IsOk() )
	{
		Output.Write(*request.GetResponse().GetStream());

		return( true );
	}

	return( false );
}

//---------------------------------------------------------
#endif // #if wxCHECK_VERSION(3, 3, 0)


///////////////////////////////////////////////////////////
//                                                       //
//                                                       //
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
