//////////////////////////////////////////////////////////////////////////
//
// pgAdmin III - PostgreSQL Tools
//
// Copyright (C) 2002 - 2016, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// frmReport.cpp - The report file dialogue
//
//////////////////////////////////////////////////////////////////////////



// App headers
#include "pgAdmin3.h"
#include <wx/file.h>
#include <wx/regex.h>

#include "frm/frmMain.h"
#include "frm/frmReport.h"
#include "utils/sysSettings.h"
#include "utils/misc.h"
#include "ctl/ctlListView.h"
#include "schema/pgObject.h"
#include "schema/pgCollection.h"
#include "schema/pgServer.h"
#include "schema/pgDatabase.h"
#include "schema/pgSchema.h"
#include "schema/pgTable.h"
#include "schema/pgColumn.h"
#include "schema/pgConstraints.h"
#include "agent/pgaJob.h"
#include "schema/pgForeignKey.h"
#include "schema/pgIndexConstraint.h"
#include "schema/pgCheck.h"
#include "utils/utffile.h"
#include <wx/stdpaths.h>
#include "../utils/diff_match_patch.h"
// XML2/XSLT headers
#include <libxslt/transform.h>
#include <libxslt/xsltutils.h>

#define txtTitle          CTRL_TEXT("txtTitle")
#define txtNotes          CTRL_TEXT("txtNotes")
#define txtHtmlFile       CTRL_TEXT("txtHtmlFile")
#define txtXmlFile        CTRL_TEXT("txtXmlFile")
#define txtHtmlStylesheet CTRL_TEXT("txtHtmlStylesheet")
#define txtXmlStylesheet  CTRL_TEXT("txtXmlStylesheet")
#define btnOK             CTRL_BUTTON("wxID_OK")
#define btnFile           CTRL_BUTTON("btnFile")
#define btnStylesheet     CTRL_BUTTON("btnStylesheet")
#define rbHtml            CTRL_RADIOBUTTON("rbHtml")
#define rbXml             CTRL_RADIOBUTTON("rbXml")
#define rbHtmlBuiltin     CTRL_RADIOBUTTON("rbHtmlBuiltin")
#define rbHtmlEmbed       CTRL_RADIOBUTTON("rbHtmlEmbed")
#define rbHtmlLink        CTRL_RADIOBUTTON("rbHtmlLink")
#define rbXmlPlain        CTRL_RADIOBUTTON("rbXmlPlain")
#define rbXmlLink         CTRL_RADIOBUTTON("rbXmlLink")
#define rbXmlProcess      CTRL_RADIOBUTTON("rbXmlProcess")
#define chkSql            CTRL_CHECKBOX("chkSql")
#define chkBrowser        CTRL_CHECKBOX("chkBrowser")

BEGIN_EVENT_TABLE(frmReport, pgDialog)
EVT_RADIOBUTTON(XRCID("rbHtml"), frmReport::OnChange)
EVT_RADIOBUTTON(XRCID("rbXml"), frmReport::OnChange)
EVT_RADIOBUTTON(XRCID("rbHtmlBuiltin"), frmReport::OnChange)
EVT_RADIOBUTTON(XRCID("rbHtmlEmbed"), frmReport::OnChange)
EVT_RADIOBUTTON(XRCID("rbHtmlLink"), frmReport::OnChange)
EVT_RADIOBUTTON(XRCID("rbXmlPlain"), frmReport::OnChange)
EVT_RADIOBUTTON(XRCID("rbXmlLink"), frmReport::OnChange)
EVT_RADIOBUTTON(XRCID("rbXmlProcess"), frmReport::OnChange)
EVT_TEXT(XRCID("txtHtmlFile"), frmReport::OnChange)
EVT_TEXT(XRCID("txtXmlFile"), frmReport::OnChange)
EVT_TEXT(XRCID("txtHtmlStylesheet"), frmReport::OnChange)
EVT_TEXT(XRCID("txtXmlStylesheet"), frmReport::OnChange)
EVT_BUTTON(XRCID("btnFile"), frmReport::OnBrowseFile)
EVT_BUTTON(XRCID("btnStylesheet"), frmReport::OnBrowseStylesheet)
EVT_BUTTON(wxID_HELP, frmReport::OnHelp)
EVT_BUTTON(wxID_OK, frmReport::OnOK)
EVT_BUTTON(wxID_CANCEL, frmReport::OnCancel)
END_EVENT_TABLE()

extern wxString dataDir;

typedef std::vector<double> vectord;
template<typename A, typename B>
std::pair<B, A> flip_pair(const std::pair<A, B>& p)
{
	return std::pair<B, A>(p.second, p.first);
}
template<typename A, typename B>
std::multimap<B, A> flip_map(const std::map<A, B>& src)
{
	std::multimap<B, A> dst;
	std::transform(src.begin(), src.end(), std::inserter(dst, dst.begin()),
		flip_pair<A, B>);
	return dst;
}
const static wxString names[] = { "Count","Sum","Min","Max","Avg","Mediana","Moda"};
class Stats
{
	std::vector<vectord> columns;
	std::vector<int> types_col;
	std::map<wxString, double> metrics;
	int rows = 0;
public:
	Stats(int rows, int cols, const std::vector<int> &types) {

		for (int x = 0; x < cols; x++) {
			vectord arrays(rows);
			columns.push_back(arrays);
		}
		types_col = types;
		this->rows = rows;
	}
	void addValue(int row,int col,double d) {
		if (types_col[col] < 1) return;
		if (col == 0) {
			return; // no use 0 column
		}
		columns[col][row] = d;
	}
	int getCountMetrics() {
		return 7;
	}
	void calc() {
		metrics.clear();
		// Count
		{
			//wxString k = names[nm];
			for (int col = 1; col < columns.size(); col++) {
				if (types_col[col] < 1) continue;
				vectord elements=columns[col];
				//elements.reserve(1000);
				double sum = 0.0;
				double avg = 0;
				double med = 0;
				std::size_t n = elements.size();
				std::sort(elements.begin(), elements.end());
				
				//std::cout << "max=" << elements.back() << std::endl;
				double max = elements.back();
				metrics[getKey(names[3], col)] = max; //max
				//std::cout << "min=" << elements.front() << std::endl;
				metrics[getKey(names[2], col)] = elements.front(); //min
				for (auto& x : elements) {
					sum += x;
				}
				if (n == 0) continue;
				//std::cout << "avg=" << sum / n << std::endl;
				metrics[getKey(names[1], col)] = sum;
				avg = sum / n;
				metrics[getKey(names[4], col)] = avg;
				if (n == 1) {
					//std::cout << "med=" << elements[0] << std::endl;
					med = elements[0];
				}
				else if (n % 2 == 0) {
					std::size_t i = n / 2;
					//std::cout << "med=" << (double)(elements[i] + elements[i - 1]) / 2.0 << std::endl;
					med = (elements[i] + elements[i - 1]) / 2.0;
				}
				else {
					//std::cout << "med=" << elements[n / 2] << std::endl;
					med = elements[n / 2];
				}
				metrics[getKey(names[5], col)] = med;
				std::set<double> elements_set;
				for (auto& x : elements) {
					elements_set.insert(x);
				}
				std::map<double, std::size_t> elements_count;
				for (auto& x : elements_set) {
					elements_count.insert(std::pair<int, std::size_t>(x, std::count(elements.begin(), elements.end(), x)));
				}
				std::multimap<std::size_t, double> count_elements;
				count_elements = flip_map(elements_count);
				//std::cout << "mod: ";
				std::size_t count_mod = count_elements.rbegin()->first;
				for (auto& x : count_elements) {
					if (x.first == count_mod) {
						//std::cout << x.second << ", ";
						metrics[getKey(names[6], col)] = x.second;
					}
					else {}
				}
				//std::cout << "(" << count_mod << ")" << std::endl;
				wxString s_count_mod = "(" + NumToStr((long)count_mod) + ")";
			}
		}
	}
	wxArrayString GetRowForTable(int numMetric) {
		wxArrayString footcols;
		wxString name;
		bool isEmpty = true;
		if (numMetric >= getCountMetrics()) return footcols;
		for (int x = 0; x < columns.size();x++) {
			if (x == 0) { 
				// Title
				name = names[numMetric];
				footcols.Add(name);
			}
			else {
				if (numMetric==0) {
					if (x == 1) footcols.Add(NumToStr((long)rows)); 
						else
							 footcols.Add("");
					isEmpty = false;
				}
				else {
					wxString k = getKey(name, x);
					wxString v;
					auto it = metrics.find(k);
					double d;
					if (it != metrics.end()) {
						d = it->second;
						//if 
						wxString suff;

						if (types_col[x] == 2) {
							if (d > 1024) {
								d = d / 1024;
								suff = "GB";
							}
							else suff = "MB";
						}
						
						if (suff.Length() > 0) v.Printf(wxT("%.2lf %s"), d, suff);
							else v.Printf(wxT("%.2lf"), d);
					} else {
						
					}
					footcols.Add(v);
					if (v.Length() > 0) isEmpty = false;
				}
			}

		}
		if (isEmpty) footcols.Clear();
		return footcols;
	}
	wxString getKey(const wxString& name, int col) {
		return wxString::Format("%s_%d", name,col);
	}

};



frmReport::frmReport(wxWindow *p)
{
	parent = p;
	header = wxT("");

	SetFont(settings->GetSystemFont());
	LoadResource(p, wxT("frmReport"));

	// Icon
	appearanceFactory->SetIcons(this);
	RestorePosition();
	btnOK->Disable();

	wxString val;
	bool bVal;

	// Output format
	settings->Read(wxT("Reports/ReportFormat"), &val, wxT("h"));
	if (val == wxT("x"))
	{
		rbHtml->SetValue(false);
		rbXml->SetValue(true);
	}
	else
	{
		rbHtml->SetValue(true);
		rbXml->SetValue(false);
	}

	// HTML Stylesheet
	settings->Read(wxT("Reports/HtmlStylesheetMode"), &val, wxT("b"));
	if (val == wxT("e"))
	{
		rbHtmlBuiltin->SetValue(false);
		rbHtmlEmbed->SetValue(true);
		rbHtmlLink->SetValue(false);
	}
	else if (val == wxT("l"))
	{
		rbHtmlBuiltin->SetValue(false);
		rbHtmlEmbed->SetValue(false);
		rbHtmlLink->SetValue(true);
	}
	else
	{
		rbHtmlBuiltin->SetValue(true);
		rbHtmlEmbed->SetValue(false);
		rbHtmlLink->SetValue(false);
	}

	// XML Stylesheet
	settings->Read(wxT("Reports/XmlStylesheetMode"), &val, wxT("p"));
	if (val == wxT("l"))
	{
		rbXmlPlain->SetValue(false);
		rbXmlLink->SetValue(true);
		rbXmlProcess->SetValue(false);
	}
	else if (val == wxT("r"))
	{
		rbXmlPlain->SetValue(false);
		rbXmlLink->SetValue(false);
		rbXmlProcess->SetValue(true);
	}
	else
	{
		rbXmlPlain->SetValue(true);
		rbXmlLink->SetValue(false);
		rbXmlProcess->SetValue(false);
	}

	// Default values
	settings->Read(wxT("Reports/LastNotes"), &val, wxT(""));
	txtNotes->SetValue(val);

	settings->Read(wxT("Reports/LastHtmlStylesheet"), &val, wxEmptyString);
	txtHtmlStylesheet->SetValue(val);

	settings->Read(wxT("Reports/LastXmlStylesheet"), &val, wxEmptyString);
	txtXmlStylesheet->SetValue(val);

	settings->Read(wxT("Reports/LastHtmlFile"), &val, wxEmptyString);
	txtHtmlFile->SetValue(val);

	settings->Read(wxT("Reports/LastXmlFile"), &val, wxEmptyString);
	txtXmlFile->SetValue(val);

	settings->Read(wxT("Reports/IncludeSQL"), &bVal, true);
	chkSql->SetValue(bVal);
	chkSql->Disable();

	settings->Read(wxT("Reports/OpenInBrowser"), &bVal, true);
	chkBrowser->SetValue(bVal);

	wxCommandEvent ev;
	OnChange(ev);

	txtTitle->SetFocus();
}


frmReport::~frmReport()
{
	SavePosition();
}


void frmReport::OnHelp(wxCommandEvent &ev)
{
	DisplayHelp(wxT("reports"), HELP_PGADMIN);
}


void frmReport::OnChange(wxCommandEvent &ev)
{
	bool enable = true;

	if (rbHtml->GetValue())
	{
		// Show/hide the appropriate controls
		rbHtmlBuiltin->Show(true);
		rbHtmlEmbed->Show(true);
		rbHtmlLink->Show(true);
		txtHtmlStylesheet->Show(true);
		txtHtmlFile->Show(true);

		rbXmlPlain->Show(false);
		rbXmlLink->Show(false);
		rbXmlProcess->Show(false);
		txtXmlStylesheet->Show(false);
		txtXmlFile->Show(false);

		// Enable/disable as appropriate
		if (txtHtmlFile->GetValue().IsEmpty())
			enable = false;

		if (rbHtmlBuiltin->GetValue())
		{
			txtHtmlStylesheet->Disable();
			btnStylesheet->Disable();
		}
		else
		{
			txtHtmlStylesheet->Enable();
			btnStylesheet->Enable();
		}

		if (rbHtmlEmbed->GetValue())
		{
			if (!wxFile::Exists(txtHtmlStylesheet->GetValue()))
				enable = false;
		}
	}
	else
	{
		// Show/hide the appropriate controls
		rbHtmlBuiltin->Show(false);
		rbHtmlEmbed->Show(false);
		rbHtmlLink->Show(false);
		txtHtmlStylesheet->Show(false);
		txtHtmlFile->Show(false);

		rbXmlPlain->Show(true);
		rbXmlLink->Show(true);
		rbXmlProcess->Show(true);
		txtXmlStylesheet->Show(true);
		txtXmlFile->Show(true);

		// Enable/disable as appropriate
		if (txtXmlFile->GetValue().IsEmpty())
			enable = false;

		if (rbXmlPlain->GetValue())
		{
			txtXmlStylesheet->Disable();
			btnStylesheet->Disable();
		}
		else
		{
			txtXmlStylesheet->Enable();
			btnStylesheet->Enable();
		}

		if (rbXmlProcess->GetValue())
		{
			if (!wxFile::Exists(txtXmlStylesheet->GetValue()))
				enable = false;
		}
	}

	btnOK->Enable(enable);
}


void frmReport::OnOK(wxCommandEvent &ev)
{
	wxString filename;
	if (rbHtml->GetValue())
		filename = txtHtmlFile->GetValue();
	else
		filename = txtXmlFile->GetValue();

	wxFileName fn(filename);
	fn.MakeAbsolute();

	// Check if the file exsits, and if so, whether to overwrite it
	if (wxFileExists(fn.GetFullPath()))
	{
		wxString msg;
		msg.Printf(_("The file: \n\n%s\n\nalready exists. Do you want to overwrite it?"), fn.GetFullPath().c_str());

		if (wxMessageBox(msg, _("Overwrite file?"), wxYES_NO | wxICON_QUESTION) != wxYES)
		{
			if (rbHtml->GetValue())
				txtHtmlFile->SetFocus();
			else
				txtXmlFile->SetFocus();
			return;
		}
	}

	// Yield, to allow the messagebox to go
	wxTheApp->Yield(true);

	// Add the title, notes and SQL
	if (txtTitle->GetValue() != wxT(""))
		XmlAddHeaderValue(wxT("title"), txtTitle->GetValue());

	if (txtNotes->GetValue() != wxT(""))
		XmlAddHeaderValue(wxT("notes"), txtNotes->GetValue());

	// Generate the report data
	wxString report;

	if (rbHtml->GetValue())
	{
		if (rbHtmlBuiltin->GetValue())
		{
			wxString xml = GetXmlReport(wxEmptyString);
			wxString xsl = GetDefaultXsl(GetEmbeddedCss(GetDefaultCss()));
			report = XslProcessReport(xml, xsl);
		}
		else if (rbHtmlEmbed->GetValue())
		{
			wxString xml = GetXmlReport(wxEmptyString);
			wxString css = FileRead(txtHtmlStylesheet->GetValue());
			if (css.IsEmpty())
			{
				wxLogError(_("No stylesheet data could be read from the file %s: Errcode=%d."), txtHtmlStylesheet->GetValue().c_str(), wxSysErrorCode());
				return;
			}
			wxString xsl = GetDefaultXsl(GetEmbeddedCss(css));
			report = XslProcessReport(xml, xsl);
		}
		else
		{
			wxString xml = GetXmlReport(wxEmptyString);
			wxString xsl = GetDefaultXsl(GetCssLink(txtHtmlStylesheet->GetValue()));
			report = XslProcessReport(xml, xsl);
		}
	}
	else
	{
		if (rbXmlPlain->GetValue())
		{
			report = GetXmlReport(wxEmptyString);
		}
		else if (rbXmlLink->GetValue())
		{
			report = GetXmlReport(txtXmlStylesheet->GetValue());
		}
		else
		{
			wxString xml = GetXmlReport(wxEmptyString);
			wxString xsl = FileRead(txtXmlStylesheet->GetValue());
			if (xsl.IsEmpty())
			{
				wxLogError(_("No stylesheet data could be read from the file %s: Errcode=%d."), txtXmlStylesheet->GetValue().c_str(), wxSysErrorCode());
				return;
			}
			report = XslProcessReport(xml, xsl);
		}
	}

	// If report is empty, an error must have occurred
	if (report.IsEmpty())
		return;

	// Save it to disk
	wxFile file(fn.GetFullPath(), wxFile::write);
	if (!file.IsOpened())
	{
		wxLogError(_("Failed to open file %s."), fn.GetFullPath().c_str());
		return;
	}
	file.Write(report, wxConvUTF8);
	file.Close();

	// Open the file in the default browser if required
	if (chkBrowser->GetValue())
#ifdef __WXMSW__
		wxLaunchDefaultBrowser(fn.GetFullPath());
#else
		wxLaunchDefaultBrowser(wxT("file://") + fn.GetFullPath());
#endif

	// Save the settings for next time round
	settings->Write(wxT("Reports/LastNotes"), txtNotes->GetValue());

	if (rbHtml->GetValue())
		settings->Write(wxT("Reports/ReportFormat"), wxT("h"));
	else
		settings->Write(wxT("Reports/ReportFormat"), wxT("x"));

	if (rbHtmlBuiltin->GetValue())
		settings->Write(wxT("Reports/HtmlStylesheetMode"), wxT("b"));
	else if (rbHtmlEmbed->GetValue())
		settings->Write(wxT("Reports/HtmlStylesheetMode"), wxT("e"));
	else
		settings->Write(wxT("Reports/HtmlStylesheetMode"), wxT("l"));

	if (rbXmlPlain->GetValue())
		settings->Write(wxT("Reports/XmlStylesheetMode"), wxT("p"));
	else if (rbXmlLink->GetValue())
		settings->Write(wxT("Reports/XmlStylesheetMode"), wxT("l"));
	else
		settings->Write(wxT("Reports/XmlStylesheetMode"), wxT("r"));

	settings->Write(wxT("Reports/LastHtmlStylesheet"), txtHtmlStylesheet->GetValue());
	settings->Write(wxT("Reports/LastXmlStylesheet"), txtXmlStylesheet->GetValue());


	settings->Write(wxT("Reports/LastHtmlFile"), txtHtmlFile->GetValue());
	settings->Write(wxT("Reports/LastXmlFile"), txtXmlFile->GetValue());

	settings->WriteBool(wxT("Reports/IncludeSQL"), chkSql->GetValue());

	settings->WriteBool(wxT("Reports/OpenInBrowser"), chkBrowser->GetValue());

	// Now go away
	if (IsModal())
		EndModal(wxID_OK);
	else
		Destroy();
}

void frmReport::OnCancel(wxCommandEvent &ev)
{
	if (IsModal())
		EndModal(wxID_CANCEL);
	else
		Destroy();
}

void frmReport::OnBrowseStylesheet(wxCommandEvent &ev)
{
	if (rbHtml->GetValue())
	{
		wxString def = txtHtmlStylesheet->GetValue();

		if (!wxFile::Exists(def))
			def.Empty();

#ifdef __WXMSW__
		wxFileDialog file(this, _("Select stylesheet filename"), wxGetHomeDir(), def, _("HTML Stylesheet files (*.css)|*.css|All files (*.*)|*.*"), wxFD_OPEN);
#else
		wxFileDialog file(this, _("Select stylesheet filename"), wxGetHomeDir(), def, _("HTML Stylesheet files (*.css)|*.css|All files (*)|*"), wxFD_OPEN);
#endif

		if (file.ShowModal() == wxID_OK)
		{
			txtHtmlStylesheet->SetValue(file.GetPath());
			OnChange(ev);
		}
	}
	else
	{
		wxString def = txtXmlStylesheet->GetValue();

		if (!wxFile::Exists(def))
			def.Empty();

#ifdef __WXMSW__
		wxFileDialog file(this, _("Select stylesheet filename"), wxGetHomeDir(), def, _("XML Stylesheet files (*.xsl)|*.xsl|All files (*.*)|*.*"), wxFD_OPEN);
#else
		wxFileDialog file(this, _("Select stylesheet filename"), wxGetHomeDir(), def, _("XML Stylesheet files (*.xsl)|*.xsl|All files (*)|*"), wxFD_OPEN);
#endif

		if (file.ShowModal() == wxID_OK)
		{
			txtXmlStylesheet->SetValue(file.GetPath());
			OnChange(ev);
		}
	}
}

void frmReport::OnBrowseFile(wxCommandEvent &ev)
{
	if (rbHtml->GetValue())
	{
#ifdef __WXMSW__
		wxFileDialog file(this, _("Select output filename"), wxGetHomeDir(), txtHtmlFile->GetValue(),
		                  _("HTML files (*.html)|*.html|All files (*.*)|*.*"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
#else
		wxFileDialog file(this, _("Select output filename"), wxGetHomeDir(), txtHtmlFile->GetValue(),
		                  _("HTML files (*.html)|*.html|All files (*)|*"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
#endif

		if (file.ShowModal() == wxID_OK)
		{
			txtHtmlFile->SetValue(file.GetPath());
			OnChange(ev);
		}
	}
	else
	{
#ifdef __WXMSW__
		wxFileDialog file(this, _("Select output filename"), wxGetHomeDir(), txtXmlFile->GetValue(),
		                  _("XML files (*.xml)|*.xml|All files (*.*)|*.*"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
#else
		wxFileDialog file(this, _("Select output filename"), wxGetHomeDir(), txtXmlFile->GetValue(),
		                  _("XML files (*.xml)|*.xml|All files (*)|*"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
#endif

		if (file.ShowModal() == wxID_OK)
		{
			txtXmlFile->SetValue(file.GetPath());
			OnChange(ev);
		}
	}
}

void frmReport::SetReportTitle(const wxString &t)
{
	txtTitle->SetValue(t);
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// START STYLESHEET FUNCTIONS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

wxString frmReport::GetCssLink(const wxString &file)
{
	wxString data;

	data = wxT("    <link rel=\"stylesheet\" href=\"") + file + wxT("\" type=\"text/css\" />\n");

	return data;
}

wxString frmReport::GetEmbeddedCss(const wxString &css)
{
	wxString data;

	data  = wxT("    <style type=\"text/css\">\n");
	data += css;
	data += wxT("    </style>\n");

	return data;
}

const wxString frmReport::GetDefaultCss()
{
	wxString data;

	data = wxT("      body {  font-family: verdana, helvetica, sans-serif; margin: 0px; padding: 0; }\n")
	       wxT("      h1 { font-weight: bold; font-size: 150%; border-bottom-style: solid; border-bottom-width: 2px; margin-top: 0px; padding-bottom: 0.5ex; color: #eeeeee; overflow: hidden; text-overflow: ellipsis; }\n")
	       wxT("      h2 { font-size: 130%; padding-bottom: 0.5ex; color: ") + appearanceFactory->GetReportKeyColour().GetAsString(wxC2S_HTML_SYNTAX) + wxT("; border-bottom-style: solid; border-bottom-width: 2px; }\n")
	       wxT("      h3 { font-size: 110%; padding-bottom: 0.5ex; color: #000000; }\n")
	       wxT("      th { text-align: left; background-color: ") + appearanceFactory->GetReportKeyColour().GetAsString(wxC2S_HTML_SYNTAX) + wxT("; color: #eeeeee; }\n")
		   wxT("      .tableFixHead {\n")
		   wxT("        overflow-y: auto; /* make the table scrollable if height is more than 200 px  */\n")
           wxT("        height: 300px; /* gives an initial height of 200px to the table */\n")
		   wxT("      }\n")
		   wxT("      .tableFixHead thead th {\n")
           wxT("        position: sticky; /* make the table heads sticky */\n")
           wxT("        top: 0px; /* table head will be placed from the top of the table and sticks to it */\n")
           wxT("      }\n")
		wxT("      #ReportHeader { padding: 10px; background-color: ") + appearanceFactory->GetReportKeyColour().GetAsString(wxC2S_HTML_SYNTAX) + wxT("; color: #eeeeee; border-bottom-style: solid; border-bottom-width: 2px; border-color: #999999; }\n")
	       wxT("      #ReportHeader th { width: 25%; white-space: nowrap; vertical-align: top; }\n")
	       wxT("      #ReportHeader td { vertical-align: top; color: #eeeeee; }\n")
	       wxT("      #ReportNotes { padding: 10px; background-color: #eeeeee; font-size: 80%; border-bottom-style: solid; border-bottom-width: 2px; border-color: #999999; }\n")
	       wxT("      .ReportSQL { margin-bottom: 10px; padding: 10px; display: block; background-color: #eeeeee; font-family: monospace; }\n")
	       wxT("      #ReportDetails { margin-left: 10px; margin-right: 10px; margin-bottom: 10px; }\n")
	       wxT("      #ReportDetails td, th { font-size: 80%; margin-left: 2px; margin-right: 2px; }\n")
	       wxT("      #ReportDetails th { border-bottom-color: #777777; border-bottom-style: solid; border-bottom-width: 2px; }\n")
	       wxT("      .ReportDetailsOddDataRow { background-color: #dddddd; }\n")
	       wxT("      .ReportDetailsEvenDataRow { background-color: #eeeeee; }\n")
		   wxT("      .ReportDetailsFootDataRow { font-weight: bold; background-color: #cccccc; }\n")
	       wxT("      .ReportTableHeaderCell { background-color: #dddddd; color: ") + appearanceFactory->GetReportKeyColour().GetAsString(wxC2S_HTML_SYNTAX) + wxT("; vertical-align: top; font-size: 80%; white-space: nowrap; }\n")
	       wxT("      .ReportTableValueCell { vertical-align: top; font-size: 80%; white-space: nowrap; padding: 2px; }\n")
	       wxT("      .ReportTableInfo { font-size: 80%; font-style: italic; }\n")
	       wxT("      #ReportFooter { font-weight: bold; font-size: 80%; text-align: right; background-color: ") + appearanceFactory->GetReportKeyColour().GetAsString(wxC2S_HTML_SYNTAX) + wxT("; color: #eeeeee; margin-top: 10px; padding: 2px; border-bottom-style: solid; border-bottom-width: 2px; border-top-style: solid; border-top-width: 2px; border-color: #999999; }\n")
	       wxT("      #ReportFooter a { color: #ffffff; text-decoration: none; }\n");

	return data;
}

wxString frmReport::GetDefaultXsl(const wxString &css)
{
	wxString data;

	data = wxT("<?xml version=\"1.0\"?>\n")
	       wxT("<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n")
	       wxT("<xsl:output method=\"xml\" doctype-system=\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\" doctype-public=\"-//W3C//DTD XHTML 1.0 Transitional//EN\" indent=\"yes\" encoding=\"utf-8\" />\n")
	       wxT("<xsl:template match=\"/report\">\n")
	       wxT("\n")
	       wxT("<html>\n")
	       wxT("  <head>\n")
	       wxT("    <xsl:if test=\"header/title != ''\">\n")
	       wxT("      <title><xsl:value-of select=\"header/title\" /></title>\n")
	       wxT("    </xsl:if>\n")
	       wxT("    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n");
	data += css;
	data += wxT("  </head>\n")
	        wxT("\n")
	        wxT("  <body>\n")
	        wxT("    <div id=\"ReportHeader\">\n")
	        wxT("\n")
	        wxT("    <xsl:if test=\"header/title != ''\">\n")
	        wxT("      <h1><xsl:value-of select=\"header/title\" /></h1>\n")
	        wxT("    </xsl:if>\n")
	        wxT("\n")
	        wxT("    <xsl:if test=\"header/generated != ''\">\n")
	        wxT("      <b>");
	data +=   _("Generated");
	data += wxT(": </b><xsl:value-of select=\"header/generated\" /><br />\n")
	        wxT("    </xsl:if>\n")
	        wxT("\n")
	        wxT("    <xsl:if test=\"header/server != ''\">\n")
	        wxT("      <b>");
	data +=   _("Server");
	data += wxT(": </b><xsl:value-of select=\"header/server\" /><br />\n")
	        wxT("    </xsl:if>\n")
	        wxT("\n")
	        wxT("    <xsl:if test=\"header/database != ''\">\n")
	        wxT("      <b>");
	data +=   _("Database");
	data += wxT(": </b><xsl:value-of select=\"header/database\" /><br />\n")
	        wxT("    </xsl:if>\n")
	        wxT("\n")
	        wxT("    <xsl:if test=\"header/catalog != ''\">\n")
	        wxT("      <b>");
	data +=   _("Catalog");
	data += wxT(": </b><xsl:value-of select=\"header/catalog\" /><br />\n")
	        wxT("    </xsl:if>\n")
	        wxT("\n")
	        wxT("    <xsl:if test=\"header/schema != ''\">\n")
	        wxT("      <b>");
	data +=   _("Schema");
	data += wxT(": </b><xsl:value-of select=\"header/schema\" /><br />\n")
	        wxT("    </xsl:if>\n")
	        wxT("\n")
	        wxT("    <xsl:if test=\"header/table != ''\">\n")
	        wxT("      <b>");
	data +=   _("Table");
	data += wxT(": </b><xsl:value-of select=\"header/table\" /><br />\n")
	        wxT("    </xsl:if>\n")
	        wxT("\n")
	        wxT("    <xsl:if test=\"header/job != ''\">\n")
	        wxT("      <b>");
	data +=   _("Job");
	data += wxT(": </b><xsl:value-of select=\"header/job\" /><br />\n")
	        wxT("    </xsl:if>\n")
	        wxT("\n")
	        wxT("    </div>\n")
	        wxT("\n")
	        wxT("    <xsl:if test=\"header/notes != ''\">\n")
	        wxT("      <div id=\"ReportNotes\">\n")
	        wxT("      <b>");
	data +=   _("Notes");
	data += wxT(": </b><br /><br />\n")
	        wxT("      <xsl:call-template name=\"substitute\">\n")
	        wxT("         <xsl:with-param name=\"string\" select=\"header/notes\" />\n")
	        wxT("      </xsl:call-template>\n")
	        wxT("      </div>\n")
	        wxT("    </xsl:if>\n")
	        wxT("\n")
	        wxT("    <div id=\"ReportDetails\">\n")
	        wxT("      <xsl:apply-templates select=\"section\" >\n")
	        wxT("        <xsl:sort select=\"@number\" data-type=\"number\" order=\"ascending\" />\n")
	        wxT("      </xsl:apply-templates>\n")
	        wxT("    </div>\n")
	        wxT("\n")
	        wxT("    <div id=\"ReportFooter\">\n");
	data +=   _("Report generated by");
	data += wxT(" <a href=\"");
	data += HtmlEntities(appearanceFactory->GetWebsiteUrl());
	data += wxT("\">");
	data += HtmlEntities(appearanceFactory->GetLongAppName());
	data += wxT("</a>\n")
	        wxT("    </div>\n")
	        wxT("\n")
	        wxT("    <br />\n")
	        wxT("  </body>\n")
		wxT("<script>\n")
		wxT("<xsl:comment><xsl:text disable-output-escaping=\"yes\"> <![CDATA[\n")
		wxT("function adjustTableBodyHeight() {\n")
		wxT("  var cnt=document.querySelectorAll('.tableFixHead').length;\n")
		wxT("  if (cnt!=1) {\n")
		wxT("    [...document.querySelectorAll('.tableFixHead')].forEach(el => { el.classList.remove('tableFixHead'); });\n")
		wxT("  } else {\n")
		wxT("   \n")
		wxT("   var tableBody = document.querySelector('.tableFixHead');\n")
		wxT("   var rf=document.getElementById('ReportFooter');\n")
		wxT("   var wh=window.innerHeight;\n")
		wxT("   var availableHeight = wh - tableBody.getBoundingClientRect().top - rf.getBoundingClientRect().height;\n")
		wxT("   tableBody.style.height = Math.max(availableHeight, 100) + 'px';\n")
		wxT("  }\n")
		wxT("}\n")
		wxT("window.onload = adjustTableBodyHeight;\n")
		wxT("window.onresize = adjustTableBodyHeight;\n")
		wxT("]]></xsl:text></xsl:comment>\n")
		wxT("</script>\n")
	        wxT("</html>\n")
	        wxT("\n")
	        wxT("</xsl:template>\n")
	        wxT("\n")
	        wxT("<xsl:template match=\"section\">\n")
	        wxT("  <xsl:if test=\"../section[@id = current()/@id]/@name != ''\">\n")
	        wxT("    <h2><xsl:value-of select=\"../section[@id = current()/@id]/@name\" /></h2>\n")
	        wxT("  </xsl:if>\n")
	        wxT("\n")
	        wxT("  <xsl:if test=\"count(../section[@id = current()/@id]/table/columns/column) > 0\">\n")
	        wxT("    <div class=\"tableFixHead\" >\n")
	        wxT("      <table>\n")
	        wxT("        <thead><tr>\n")
	        wxT("          <xsl:apply-templates select=\"../section[@id = current()/@id]/table/columns/column\">\n")
	        wxT("            <xsl:sort select=\"@number\" data-type=\"number\" order=\"ascending\" />\n")
	        wxT("            <xsl:with-param name=\"count\" select=\"count(../section[@id = current()/@id]/table/columns/column)\" />\n")
	        wxT("          </xsl:apply-templates>\n")
	        wxT("        </tr></thead><tbody>\n")
	        wxT("        <xsl:apply-templates select=\"../section[@id = current()/@id]/table/rows/*\" mode=\"rows\">\n")
	        wxT("          <xsl:sort select=\"@number\" data-type=\"number\" order=\"ascending\" />\n")
	        wxT("          <xsl:with-param name=\"column-meta\" select=\"../section[@id = current()/@id]/table/columns/column\" />\n")
	        wxT("        </xsl:apply-templates>\n")
	        wxT("      </tbody></table>\n")
	        wxT("    </div>\n")
	        wxT("    <br />\n")
	        wxT("    <xsl:if test=\"../section[@id = current()/@id]/table/info != ''\">\n")
	        wxT("      <p class=\"ReportTableInfo\"><xsl:value-of select=\"../section[@id = current()/@id]/table/info\" /></p>\n")
	        wxT("    </xsl:if>\n")
	        wxT("  </xsl:if>\n")
	        wxT("\n")
	        wxT("  <xsl:if test=\"../section[@id = current()/@id]/sql != ''\">\n")
	        wxT("    <pre class=\"ReportSQL\">\n")
	        wxT("      <xsl:call-template name=\"substitute\">\n")
	        wxT("         <xsl:with-param name=\"string\" select=\"../section[@id = current()/@id]/sql\" />\n")
	        wxT("      </xsl:call-template>\n")
	        wxT("    </pre>\n")
	        wxT("  </xsl:if>\n")
	        wxT("</xsl:template>\n")
	        wxT("\n")
	        wxT("<xsl:template match=\"column\">\n")
	        wxT("  <xsl:param name=\"count\" />\n")
	        wxT("  <th class=\"ReportTableHeaderCell\">\n")
	        wxT("  <xsl:attribute name=\"width\"><xsl:value-of select=\"100 div $count\" />%</xsl:attribute>\n")
	        wxT("    <xsl:call-template name=\"substitute\">\n")
	        wxT("       <xsl:with-param name=\"string\" select=\"@name\" />\n")
	        wxT("    </xsl:call-template>\n")
	        wxT("  </th>\n")
	        wxT("</xsl:template>\n")
	        wxT("\n")
	        wxT("<xsl:template match=\"*\" mode=\"rows\">\n")
	        wxT("  <xsl:param name=\"column-meta\" />\n")
	        wxT("  <tr>\n")
	        wxT("  <xsl:choose>\n")
	        wxT("  <xsl:when test=\"(@foot)\">\n")
	        wxT("    <xsl:attribute name=\"class\">ReportDetailsFootDataRow</xsl:attribute>\n")
		    wxT("  </xsl:when>\n")
		    wxT("  <xsl:when test=\"position() mod 2 != 1\">\n")
		    wxT("    <xsl:attribute name=\"class\">ReportDetailsOddDataRow</xsl:attribute>\n")
		    wxT("  </xsl:when>\n")
	        wxT("  <xsl:otherwise>\n")
	        wxT("    <xsl:attribute name=\"class\">ReportDetailsEvenDataRow</xsl:attribute>\n")
	        wxT("  </xsl:otherwise>\n")
	        wxT("  </xsl:choose>\n")
	        wxT("    <xsl:apply-templates select=\"$column-meta\" mode=\"cells\">\n")
	        wxT("      <xsl:with-param name=\"row\" select=\".\" />\n")
	        wxT("    </xsl:apply-templates>\n")
	        wxT("  </tr>\n")
	        wxT("</xsl:template>\n")
	        wxT("\n")
	        wxT("<xsl:template match=\"*\" mode=\"cells\">\n")
	        wxT("  <xsl:param name=\"row\" />\n")
	        wxT("    <td class=\"ReportTableValueCell\">\n")
	        wxT("    <xsl:choose>\n")
	        wxT("      <xsl:when test=\"$row/@*[name() = current()/@id]|$row/*[name() = current()/@id] != ''\">\n")
	        wxT("        <xsl:call-template name=\"substitute\">\n")
	        wxT("          <xsl:with-param name=\"string\" select=\"$row/@*[name() = current()/@id]|$row/*[name() = current()/@id]\" />\n")
	        wxT("        </xsl:call-template>\n")
	        wxT("      </xsl:when>\n")
	        wxT("      <xsl:otherwise>\n")
	        wxT("        <xsl:text> </xsl:text>\n")
	        wxT("      </xsl:otherwise>\n")
	        wxT("    </xsl:choose>\n")
	        wxT("  </td>\n")
	        wxT("</xsl:template>\n")
	        wxT("\n")
	        wxT("<xsl:template name=\"substitute\">\n")
	        wxT("   <xsl:param name=\"string\" />\n")
	        wxT("   <xsl:param name=\"from\" select=\"'&#xA;'\" />\n")
	        wxT("   <xsl:param name=\"to\">\n")
	        wxT("      <br />\n")
	        wxT("   </xsl:param>\n")
	        wxT("   <xsl:choose>\n")
	        wxT("      <xsl:when test=\"contains($string, $from)\">\n")
	        wxT("         <xsl:value-of select=\"substring-before($string, $from)\" />\n")
	        wxT("         <xsl:copy-of select=\"$to\" />\n")
	        wxT("         <xsl:call-template name=\"substitute\">\n")
	        wxT("            <xsl:with-param name=\"string\" select=\"substring-after($string, $from)\" />\n")
	        wxT("            <xsl:with-param name=\"from\" select=\"$from\" />\n")
	        wxT("            <xsl:with-param name=\"to\" select=\"$to\" />\n")
	        wxT("         </xsl:call-template>\n")
	        wxT("      </xsl:when>\n")
	        wxT("      <xsl:otherwise>\n")
	        wxT("         <xsl:value-of select=\"$string\" />\n")
	        wxT("      </xsl:otherwise>\n")
	        wxT("   </xsl:choose>\n")
	        wxT("</xsl:template>\n")
	        wxT("\n")
	        wxT("</xsl:stylesheet>\n");

	return data;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// END STYLESHEET FUNCTIONS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// START XML FUNCTIONS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void frmReport::XmlAddHeaderValue(const wxString &name, const wxString &value)
{
	header += wxT("    <") + HtmlEntities(name) + wxT(">") + HtmlEntities(value) + wxT("</") + HtmlEntities(name) + wxT(">\n");
}

int frmReport::XmlCreateSection(const wxString &name)
{
	int ind = sectionName.Add(HtmlEntities(name));
	sectionData.Add(wxT(""));
	sectionTableHeader.Add(wxT(""));
	sectionTableRows.Add(wxT(""));
	sectionTableInfo.Add(wxT(""));
	sectionSql.Add(wxT(""));
	return ind + 1;
}

void frmReport::XmlSetSectionTableHeader(const int section, int columns, const wxChar *name, ...)
{
	va_list ap;
	const wxChar *p = name;
	wxString data;

	va_start(ap, name);

	for (int x = 0; x < columns; x++)
	{
		data += wxT("        <column id=\"c");
		data += NumToStr((long)(x + 1));
		data += wxT("\" number=\"");
		data += NumToStr((long)(x + 1));
		data += wxT("\" name=\"");
		data += HtmlEntities(p);
		data += wxT("\"/>\n");
		p = va_arg(ap, wxChar *);
	}

	va_end(ap);

	sectionTableHeader[section - 1] = data;
}

void frmReport::XmlAddSectionTableRow(const int section, int number, int columns, const wxChar *value, ...)
{
	va_list ap;
	const wxChar *p = value;
	wxString data;

	va_start(ap, value);

	data = wxT("        <row id=\"r");
	data += NumToStr((long)number);
	data += wxT("\" number=\"");
	data += NumToStr((long)number);
	data += wxT("\"");

	for (int x = 0; x < columns; x++)
	{
		data += wxT(" c");
		data += NumToStr((long)(x + 1));
		data += wxT("=\"");
		data += HtmlEntities(p);
		data += wxT("\"");
		p = va_arg(ap, wxChar *);
	}


	data += wxT(" />\n");

	va_end(ap);

	sectionTableRows[section - 1] += data;
}

void frmReport::XmlAddSectionTableFromListView(const int section, ctlListView *list)
{
	// Get the column headers
	int cols = list->GetColumnCount();
	wxString data;
	wxListItem itm;
	//double sum = 0;
	//double max = -DBL_MAX;
	//double min = -DBL_MAX;
	std::vector<int> isTypeNumber;
	// Build the columns
	for (int x = 0; x < cols; x++)
	{
		itm.SetMask(wxLIST_MASK_TEXT);
		list->GetColumn(x, itm);
		
		wxString label = itm.GetText();
		if (label == _("Size")) isTypeNumber.push_back(2);
			else if (list->IsNumberColumn(label)) isTypeNumber.push_back(1);
			else
				isTypeNumber.push_back(0); // string
		data += wxT("        <column id=\"c");
		data += NumToStr((long)(x + 1));
		data += wxT("\" number=\"");
		data += NumToStr((long)(x + 1));
		data += wxT("\" name=\"");
		data += HtmlEntities(label);
		data += wxT("\" />\n");
	}
	sectionTableHeader[section - 1] = data;
	// Build the rows
	int rows = list->GetItemCount();
	wxArrayString footcols;
	Stats st(rows, cols, isTypeNumber);
	int footrows = st.getCountMetrics();
	
	for (int y = 0; y < rows + footrows; y++)
	{
		if (y >= rows) {
			if (y == rows) st.calc();
			// foot rows
			footcols = st.GetRowForTable(y - rows);
			if (footcols.Count() == 0) continue;
		}
		data = wxT("        <row id=\"r");
		data += NumToStr((long)(y + 1));
		data += wxT("\" number=\"");
		data += NumToStr((long)(y + 1));
		data += wxT("\"");
			for (int x = 0; x < cols; x++)
			{
				data += wxT(" c");
				data += NumToStr((long)(x + 1));
				data += wxT("=\"");
				if (y >= rows)
					data += HtmlEntities(footcols[x]);
				else {
					wxString val = list->GetText(y, x);
					if (val == "NaN") val = "0";
					double d = 0;
					int t = isTypeNumber[x];
					if (t == 2) { d = ConvertSizeToMB(val); }
					else
					{
						if (val.ToCDouble(&d)) {
						}
					}
					st.addValue(y, x, d);
					data += HtmlEntities(val);
				}
				data += wxT("\"");
			}
		if (y >= rows) data = data + " foot=\"yes\"";
		data += wxT(" />\n");
		sectionTableRows[section - 1] += data;
	}
}

void frmReport::XmlAddSectionTableFromGrid(const int section, ctlSQLResult *grid)
{
	// Get the column headers
	int cols = grid->GetNumberCols();
	int shift = 0;

	wxString data;
	wxListItem itm;

	if (grid->GetRowCountSuppressed())
		shift = 1;

	for (int x = 1; x <= cols; x++)
	{
		wxString label = grid->OnGetItemText(-1, x - shift);
		data += wxT("        <column id=\"c");
		data += NumToStr((long)(x));
		data += wxT("\" number=\"");
		data += NumToStr((long)(x));
		data += wxT("\" name=\"");
		data += HtmlEntities(label);
		data += wxT("\" />\n");
	}
	sectionTableHeader[section - 1] = data;

	// Build the rows
	int rows = grid->NumRows();

	for (int y = 0; y < rows; y++)
	{
		data = wxT("        <row id=\"r");
		data += NumToStr((long)(y + 1));
		data += wxT("\" number=\"");
		data += NumToStr((long)(y + 1));
		data += wxT("\"");

		for (int x = 1; x <= cols; x++)
		{
			data += wxT(" c");
			data += NumToStr((long)(x));
			data += wxT("=\"");
			data += HtmlEntities(grid->OnGetItemText(y, x - shift));
			data += wxT("\"");
		}
		data += wxT(" />\n");
		sectionTableRows[section - 1] += data;
	}
}

void frmReport::XmlSetSectionSql(int section, const wxString &sql)
{
	sectionSql[section - 1] = HtmlEntities(sql);

	if (!sectionSql[section - 1].IsEmpty())
		chkSql->Enable();
	else
		chkSql->Disable();
}

void frmReport::XmlAddSectionValue(const int section, const wxString &name, const wxString &value)
{
	sectionData[section - 1] += wxT("    <") + HtmlEntities(name) + wxT(">") + HtmlEntities(value) + wxT("</") + HtmlEntities(name) + wxT(">\n");
}

wxString frmReport::GetSectionTableColumns(const int section)
{
	wxString data;

	data  = wxT("      <columns>\n");
	data += sectionTableHeader[section - 1];
	data += wxT("      </columns>\n");

	return data;
}

wxString frmReport::GetSectionTableRows(const int section)
{
	wxString data;

	data  = wxT("      <rows>\n");
	data += sectionTableRows[section - 1];
	data += wxT("      </rows>\n");

	return data;
}

wxString frmReport::GetSectionTable(const int section)
{
	wxString data;

	data  = wxT("    <table>\n");
	data += GetSectionTableColumns(section);
	data += GetSectionTableRows(section);

	if (!sectionTableInfo[section - 1].IsEmpty())
	{
		data += wxT("      <info>");
		data += sectionTableInfo[section - 1];
		data += wxT("</info>\n");
	}

	data += wxT("    </table>\n");

	return data;
}

wxString frmReport::GetSection(const int section)
{
	wxString data;

	data  = wxT("  <section id=\"s");
	data += NumToStr((long)section);
	data += wxT("\" number=\"");
	data += NumToStr((long)section);
	data += wxT("\" name=\"");
	data += sectionName[section - 1];
	data += wxT("\">\n");
	data += GetSectionTable(section);


	if (chkSql->GetValue() && !sectionSql[section - 1].IsEmpty())
	{
		data += wxT("    <sql>");
		data += sectionSql[section - 1];
		data += wxT("</sql>\n");
	}

	data += sectionData[section - 1];
	data += wxT("  </section>\n");

	return data;
}

wxString frmReport::GetXmlReport(const wxString &stylesheet = wxT(""))
{
	wxString data;

	data  = wxT("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");

	if (!stylesheet.IsEmpty())
	{
		data += wxT("<?xml-stylesheet type=\"text/xsl\" href=\"");
		data += stylesheet;
		data += wxT("\" ?>\n");
	}

	data += wxT("\n");
	data += wxT("<report>\n\n");

	data += wxT("  <header>\n");
	data += header;
	data += wxT("  </header>\n\n");

	for (unsigned int x = 1; x <= sectionName.GetCount(); x++ )
	{
		data += GetSection(x);
		data += wxT("\n");
	}

	data += wxT("</report>\n");

	return data;
}

//
// libxml convenience macros
//
#define XML_FROM_WXSTRING(s) ((xmlChar *)(const char *)s.mb_str(wxConvUTF8))
#define WXSTRING_FROM_XML(s) wxString((char *)s, wxConvUTF8)

wxString frmReport::XslProcessReport(const wxString &xml, const wxString &xsl)
{
	xmlChar *output = 0;
	xmlDocPtr ssDoc = 0, xmlDoc = 0, resDoc = 0;
	xsltStylesheetPtr ssPtr = 0;
	int length;

	wxBeginBusyCursor();

	// Apply the stylesheet
	xmlSubstituteEntitiesDefault (1); // Substitute entities
	xmlLoadExtDtdDefaultValue = 1; // Load external entities

	// Parse the stylesheet
	ssDoc = xmlParseDoc(XML_FROM_WXSTRING(xsl));
	if (!ssDoc)
	{
		wxEndBusyCursor();
		wxLogError(_("Failed to parse the XML stylesheet!"));
		goto cleanup;
	}

	ssPtr = xsltParseStylesheetDoc(ssDoc);
	if (!ssPtr)
	{
		wxEndBusyCursor();
		wxLogError(_("Failed to parse the XSL stylesheet!"));
		goto cleanup;
	}

	// Parse the data
	xmlDoc = xmlParseDoc(XML_FROM_WXSTRING(xml));
	if (!xmlDoc)
	{
		wxEndBusyCursor();
		wxLogError(_("Failed to parse the XML document!"));
		goto cleanup;
	}

	// Apply the stylesheet
	resDoc = xsltApplyStylesheet(ssPtr, xmlDoc, NULL);
	if (!resDoc)
	{
		wxEndBusyCursor();
		wxLogError(_("Failed to apply the XSL stylesheet to the XML document!"));
		goto cleanup;
	}

	// Get the result
	xsltSaveResultToString (&output, &length, resDoc, ssPtr);
	if (!resDoc)
	{
		wxEndBusyCursor();
		wxLogError(_("Failed to read the processed document!"));
		goto cleanup;
	}

cleanup:

	// Cleanup
	if (resDoc)
		xmlFreeDoc(resDoc);

	if (xmlDoc)
		xmlFreeDoc(xmlDoc);

	if (ssPtr)
		xsltFreeStylesheet(ssPtr);

	// This crashes - dunno why :-(
	// if (ssDoc)
	//  xmlFreeDoc(ssDoc);

	xsltCleanupGlobals();

	wxEndBusyCursor();

	if (output)
		return WXSTRING_FROM_XML(output);
	else
		return wxEmptyString;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// END XML FUNCTIONS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
SQL::SQL(const wxString &_sql, const wxString &_pathtree) {
  // Construct a SQL with the specified operation and text.
	sql=wxString(_sql);
	pathtree=wxString(_pathtree) ;
	countchild=0;
	mode=0;
}

SQL::SQL() {
}

/**
 * Display a human-readable version of this SQL.
 * @return text version
 */
wxString SQL::toString() const {
  wxString prettyText = sql;
  // Replace linebreaks with Pilcrow signs.
  //prettyText.replace('\n', L'\u00b6');
  	wxString c=wxEmptyString;
	wxString cmp=wxEmptyString;
	if (countchild>0) c.Printf(wxT("(%d)"), countchild);
	bool r=false;
	if (sql.Cmp(sql2)!=0) {
		cmp="ne EQ ";
		cmp.Printf(wxT("(ne EQ (%d, %d))"), sql.Len(),sql2.Len());
	}
	if (mode==__Remove) cmp=cmp+" - ";
	if (mode==__Insert) cmp=cmp+" + ";
	if (mode==__Equal) cmp=cmp+" = ";
  return wxString(pathtree+c+"\n"+cmp+"SQL(" + wxString("\""))
      + prettyText + wxString("\")");
}

/**
 * Is this SQL equivalent to another SQL?
 * @param d Another SQL to compare against
 * @return true or false
 */
bool SQL::operator==(const SQL &d) const {
  return (d.sql == this->sql);
}

bool SQL::operator!=(const SQL &d) const {
  return !(operator == (d));
}
	#include <wx/listimpl.cpp>
	WX_DEFINE_LIST(MyListSql);
#include "wx/arrimpl.cpp"
WX_DEFINE_OBJARRAY(ArraySQL)
wxString reportCompareFactory::GetNodePath(wxTreeItemId node) {
	wxString path;
	path = parent->GetBrowser()->GetItemText(node).Trim();

	wxTreeItemId parent_id = parent->GetBrowser()->GetItemParent(node);
	while (parent_id.IsOk())
	{
		path = parent->GetBrowser()->GetItemText(parent_id).BeforeFirst('(').Trim() + wxT("/") + path;
		parent_id = parent->GetBrowser()->GetItemParent(parent_id);
	}

	return path;

}
wxString reportCompareFactory::ApplyCompareOpts(wxString sql,int metatype) {
	if (sql.IsEmpty()) return "";
	bool no_comment = parent->getChoiceSelect(1);
	bool no_priv = parent->getChoiceSelect(0);
	if (no_comment) {
		int last = 0;
		while (last!=-1) {
			last = sql.Find("\nCOMMENT ON");
			int start = last;
			int end = -1;
			while (last!=-1) {
				last++;
				if (sql.Length() == last) { last = -1; continue; }
				wxChar c = sql.GetChar(last);
				if (c == '\'') {
					last++;
					if (sql.GetChar(last) == '\'') { continue; }
					if (sql.GetChar(last) == ';') { end = last; last = -1; continue; }
					
				}
			};
			if (end != -1) {
				sql=sql.Remove(start, end - start+1);
				last = 0;
			}
		}
		
	}
	if (no_priv &&(metatype != PGM_ROLE)) {
		int last = 0;
		while (last != -1) {
			last = sql.Find("\nGRANT ");
			if (last==-1) last = sql.Find("\nREVOKE ");
			int start = last;
			int end = -1;
			while (last != -1) {
				last++;
				if (sql.Length() == last) { last = -1; continue; }
				wxChar c = sql.GetChar(last);
				if (c == ';') {
					 end = last; last = -1;
				}
			};
			if (end != -1) {
				sql = sql.Remove(start, end - start + 1);
				last = 0;
			}
		}

	}
	return sql;

}
void reportCompareFactory::GetExpandedChildNodes(wxTreeItemId node, wxArrayString &expandedNodes, ArraySQL &list, time_t *t, wxBusyInfo *w, MyHashSQL &h_path,int lvl)
{
	wxTreeItemIdValue cookie;
	ctlTree *browser=parent->GetBrowser();
	wxTreeItemId child;
	if (lvl==0) child = node;
		else child = browser->GetFirstChild(node, cookie);
	pgObject *obj;
	wxString path;
	int size=expandedNodes.Count();
	while (child.IsOk())
	{
		obj=browser->GetObject(child);
		if (obj && obj->GetMetaType()==PGM_FUNCTION && !browser->HasChildren(child))
			path=GetNodePath(child);
		else
											  		path=parent->GetNodePath(child);
		
	//	tmp=wxDateTime::GetTimeNow();
	//	if (difftime(tmp,*t)>2.0) {
			//w->~wxBusyInfo();
			//wxSafeYield();
			//wxMilliSleep(50);
			//wxSafeYield();
			//w = new wxBusyInfo(wxString::Format("Path = %s ,GetName() = %s, isCollection = %d",
			//                                    path.c_str(), obj->GetName().c_str(), obj->IsCollection()),parent);
			//wxSafeYield();
			//wxMilliSleep(50);
			//wxSafeYield();
	//		*t=tmp;
	//	}
		if (obj) {
//		  OutputDebugString(wxString::Format("Path = %s ,GetName() = %s, isCollection = %d\n",
//			                                    path.c_str(), obj->GetTypeName().c_str(), obj->IsCollection()));
		  if (obj->GetMetaType()==PGM_CATALOG
			  ||obj->GetMetaType()==PGM_COLUMN
			  ||obj->GetMetaType()==PGM_RULE
			  ||obj->GetMetaType()==PGM_CATALOG
			  ) {
				  child = browser->GetNextChild(node, cookie);
				  continue;
		  }
		  //obj->ShowTreeDetail(browser);
		  //obj->ShowTree(parent,browser);
		  // если надо объекто сложный и сас состоит из коллекций
		if ((obj->GetMetaType()==PGM_SCHEMA
			||obj->GetMetaType()==PGM_DATABASE
			||obj->GetMetaType()==PGM_TABLE
			||obj->GetMetaType()==PGM_FOREIGNTABLE
			)&&!obj->IsCollection()) {
						obj->ShowTreeDetail(browser);
						//obj->ShowTree(parent,browser);
		} else
		{
			if (obj->GetMetaType() == PGM_VIEW) obj->ShowTreeDetail(browser); // только для того чтобы получить инфу о триггерах
			if (obj->GetMetaType() == PGM_EVENTTRIGGER)  // получаем инфу о тригеррах по событиям
				obj->ShowTreeDetail(browser);
		}
		}
		
		//if (browser->IsExpanded(child))
		if (browser->HasChildren(child))
		{
			bool rec=true;
		  if (obj && (obj->GetMetaType()==PGM_TABLE
			  //||obj->GetMetaType()==PGM_VIEW
			  )) {
				  wxTreeItemId Item = browser->GetItemParent(child);
				  obj=browser->GetObject(Item); // Tables
				  wxTreeItemId Item2 = browser->GetItemParent(obj->GetId());
				  obj=browser->GetObject(Item2); // Schemes
				  if (obj && obj->GetMetaType()==PGM_SCHEMA&& !obj->IsCollection()) {
					  rec=false; // не собираем инфу по сек. таблицам и секциям, и во внутрь не заходим
					  obj=browser->GetObject(child);
					  obj->ShowTreeDetail(browser);
				  } else obj=browser->GetObject(child);
		   }
		  if (obj && (obj->GetMetaType()==PGM_VIEW && !obj->IsCollection())) rec=false;
		  if (rec) {
			GetExpandedChildNodes(child, expandedNodes,list,t,w,h_path,lvl+1);
			//expandedNodes.Add(parent->GetNodePath(child));
		    obj=browser->GetObject(child);
		  }
		  
		}
		if (obj ) {
				wxString s=obj->GetSql(browser);
				if (obj->GetMetaType()==PGM_SEQUENCE) s="";
				s = ApplyCompareOpts(s, obj->GetMetaType());
				int c=browser->GetChildrenCount(child,false);
				if (size>0) {
					wxString srcpath(path);
					SQL *sq;
					srcpath.Replace(expandedNodes[0],expandedNodes[2],false);
					srcpath.Replace(expandedNodes[1],expandedNodes[3],false);
					MyHashSQL::iterator it=h_path.find(srcpath);
					if (h_path.end()==it) {
						// не найдено в первой БД

						if (s!=wxEmptyString) {
							sq =new SQL(wxEmptyString,srcpath);
							sq->sql2=s;
							sq->mode=__Remove;
							list.Add(sq);

						} else
						{
							sq =new SQL(wxEmptyString,srcpath);
							sq->sql2=wxEmptyString;
							sq->countchild=c;
							sq->mode=__Remove;
							list.Add(sq);
							
						}
						
						h_path[srcpath]=list.GetCount()-1;
					}
					else {
						int i=it->second;
						SQL& sql=list.Item(i);
						sql.mode=__Equal;
						sql.sql2=s;
					}
				} else {
					if (s!=wxEmptyString) {
						SQL *sq =new SQL(s,path);
						sq->mode=__Insert;
						list.Add(sq);
					} else
					{
						SQL *sq =new SQL(s,path);
						sq->countchild=c;
						sq->mode=__Insert;
						list.Add(sq);
					}
					h_path[path]=list.GetCount()-1;
				}
		}
		
		child = browser->GetNextChild(node, cookie);
	}

}

wxWindow *reportCompareFactory::StartDialog(frmMain *form, pgObject *obj)
{
	parent = form;
//	std::wstring r;
//	std::wstring str1=L"";
//	std::wstring str2=L"line#\nline2\nADD line 3\nline4\n";
//	r=printdiff(str1,str2);
//	return 0;

//	wxBeginBusyCursor();
	//frmReport *report = new frmReport(GetFrmMain());
	//wxBusyInfo *waiting;
	wxString msg;
	// Generate the report header
	wxDateTime now = wxDateTime::Now();
	
		ctlTree *browser=form->GetBrowser();
	wxTreeItemIdValue foldercookie;
	wxTreeItemId folderitem = browser->GetFirstChild(browser->GetRootItem(), foldercookie);
	wxString path(form->GetNodePath(obj->GetId()));
	wxString src_serverGroupname=path.AfterFirst('/').BeforeFirst('/');
	wxString trg_serverGroupname;
	// ������ ��������/�������/serverN/Datebases/dbname
	//                p1      p2      p3
	wxString p_db;
	int p1=path.Find('/');
	if (p1<0) return 0;
	int p2=path.find('/',p1+1);
	if (p2<0) return 0;
	startpathpos=p2;
	int p3=path.find('/',p2+1);
	wxString p_pref=path.substr(0,p2);
	wxString p_server_obj;
	if (!obj->GetConnection()) return 0;
	if (obj && obj->GetDatabase() && obj->GetDatabase()->GetConnected()) 
			p_db=obj->GetDatabase()->GetName();
	else
			p_db=obj->GetServer()->GetDatabaseName();

	if (p3<0) {
		// select server
		//if (wxMessageBox(wxString::Format("Path = %s ,GetName() = %s, isCollection = %d",
		//	                                    path.c_str(), obj->GetTypeName().c_str(), obj->IsCollection()), _("Close"), wxYES_NO | wxICON_QUESTION) != wxYES)
		//{
		//	return 0;
		//}
//Группы серверов/Серверы/PostgreSQL 9.6
// используем первую попавшуюся открытую БД
		p_server_obj = path.substr(p2); // с /серверN/

	}
	else
	{
		//	Группы серверов/Серверы/PostgreSQL 9.6/Базы данных/postgres
		p_server_obj=path.substr(p2,p3-p2); // � /������N/
	}
	wxString p_db_replace=_("Databases")+"/"+p_db+"/";
	wxString p_server_replace=src_serverGroupname+p_server_obj;
	pgServer *server;
	pgDatabase *db=NULL,*lastdb=NULL;
wxString trg_db_replace;
wxString trg_server_replace;

	wxTreeItemId srvitem = obj->GetServer()->GetId();

	while (folderitem)
	{
		if (browser->ItemHasChildren(folderitem))
		{
			wxTreeItemIdValue servercookie;
			wxTreeItemId serveritem = browser->GetFirstChild(folderitem, servercookie);
			while (serveritem)
			{
				server = (pgServer *)browser->GetItemData(serveritem);
				if (server != NULL && server->IsCreatedBy(serverFactory))
				{
					trg_server_replace=browser->GetItemText(server->GetId()).BeforeFirst('(').Trim();
					if (srvitem!=server->GetId() && server->GetConnected()) {
						// наше соединение не нужно нужно другое и активное
						pgCollection *coll = browser->FindCollection(databaseFactory, server->GetId());
						if (coll)
						{
							treeObjectIterator dbs(browser, coll);
							while ((db = (pgDatabase *)dbs.GetNextObject()) != 0)
							{
								wxString path(form->GetNodePath(server->GetId()));
								trg_serverGroupname=path.AfterFirst('/').BeforeFirst('/');
								// есть открытая БД
								lastdb=db;
								if (db->GetConnected()) {

									//if (db->GetName()!=wxT("postgres")) 	goto ex_out;
									goto ex_out;
								}
							}
							
						}
					}
				}
				serveritem = browser->GetNextChild(folderitem, servercookie);
			}
		}
		folderitem = browser->GetNextChild(browser->GetRootItem(), foldercookie);
	}
ex_out:
pgObject *trgobj;
wxString newpath;
if (lastdb!=NULL) {
			p_db=browser->GetItemText(lastdb->GetId()).BeforeFirst('(').Trim();
			trg_db_replace=_("Databases")+"/"+p_db+"/";
			trg_server_replace=trg_serverGroupname+'/'+trg_server_replace;

			newpath=path;
			
			//if (newpath.Replace(p_server_replace,trg_server_replace,false)==0) 
			newpath.Replace(p_server_replace,trg_server_replace,false);
			newpath.Replace(p_db_replace,trg_db_replace,false);
			if (!parent->SetCurrentNode(parent->GetBrowser()->GetRootItem(),newpath)) {
				msg.Printf("Not found object %s in other DB.",newpath);
				wxMessageBox(msg, _("Error"), wxOK | wxICON_INFORMATION);
				return 0;
			}
			trgobj=browser->GetObject(browser->GetSelection());
} else
{
		msg="Not open connecting, compare stop.";
		//		msg.Printf("В установленном соединении %s, нет подходящих БД.",browser->GetItemText(lastdb->GetServer()->GetId()).BeforeFirst('(').Trim());
	wxMessageBox(msg, _("Error"), wxOK | wxICON_INFORMATION);
	return 0;
}
if (!parent->StartChoiceDialog()) return 0;
time_t timer=wxDateTime::GetTimeNow();
	wxArrayString expandedNodes;
	ArraySQL list;
	MyHashSQL h_path;

wxWindowDisabler disableAll;
{
#ifndef DEBUG
			wxBusyInfo waiting(wxString::Format(" Reading source DB Path = %s ,Start object = %s",
			                                    browser->GetItemText(obj->GetServer()->GetId()).c_str(), obj->GetName().c_str(),parent));
			// Give the UI a chance to redraw
			wxSafeYield();
			wxMilliSleep(50);
			wxSafeYield();
#endif
//			waiting->~wxBusyInfo();
	GetExpandedChildNodes(obj->GetId(),expandedNodes,list,&timer, NULL,h_path,0);
}
//waiting->~wxBusyInfo();

	wxFileName fn("");
	fn.MakeAbsolute();
wxSafeYield();
wxMilliSleep(50);
wxSafeYield();
//	return 0;

{
			wxBusyInfo waiting(wxString::Format(" Reading target DB Path = %s\n, Start object = %s",
			                                    browser->GetItemText(trgobj->GetId()).c_str(), trgobj->GetName().c_str(),parent));
			// Give the UI a chance to redraw
			wxSafeYield();
			wxMilliSleep(50);
			wxSafeYield();
timer=wxDateTime::GetTimeNow();
	wxArrayString expandedNodes2;
	expandedNodes2.Add(trg_server_replace);
	expandedNodes2.Add(trg_db_replace);
	expandedNodes2.Add(p_server_replace);
	expandedNodes2.Add(p_db_replace);
	GetExpandedChildNodes(trgobj->GetId(),expandedNodes2,list,&timer, NULL,h_path,0);
}
//waiting->~wxBusyInfo();

int e_count=list.GetCount();
int linecount=0;
int nstart=0,lastpos=0;
int c,minlen=100000,root=0;
wxArrayInt* child;
wxArrayPtrVoid level(15);
wxHashTable htab(wxKEY_STRING);
wxString key;
int npos=p2;
    for (int i = 0; i < e_count; ++i)
    {
        SQL& sq = list.Item(i);
		c=0;
		nstart=0;
		if ((nstart=sq.pathtree.rfind('/'))!=wxNOT_FOUND ) { 
			key=sq.pathtree.SubString(npos,nstart-1);
			if (minlen>key.Len()) {minlen=key.Len(); root=i;}
		}
		child=(wxArrayInt *)htab.Get(key);
		if (child==NULL) {
			child=new wxArrayInt;
			htab.Put(key,(wxObject *)child);
		}
		child->Add(i);
	
		linecount++;
	}
	
MyListSql::iterator iter2;
//e_count=list.GetCount();
//for (int i = 0; i < e_count; ++i)
//{
//		SQL& sq = list.Item(i);
//}
//	file2.Write(report, wxConvUTF8);
//	file2.Close();


	//return 0;
	titleline=wxEmptyString;
	list_head=wxEmptyString;
	rowlist=wxEmptyString;
	list_end=wxEmptyString;
	tableheader=wxEmptyString;
	head=wxEmptyString;
	tableheader2=wxEmptyString;
	tableshtml=wxEmptyString;

// �������� �������
#ifndef _DEBUG
	wxString fDir=wxStandardPaths::Get().GetExecutablePath().BeforeLast('\\')+wxT("\\");
#else
	wxString fDir=wxStandardPaths::Get().GetExecutablePath().BeforeLast('\\')+wxT("\\");
#endif
	
	wxString f=fDir+"textcompare_report.template";
	wxString buffer;
	if (!wxFileExists(f)) {
		f=dataDir+wxFileName::GetPathSeparator()+"textcompare_report.template";
	}
	wxUtfFile file3(f, wxFile::read, wxFONTENCODING_UTF8);
	if (file3.IsOpened())
	{
		file3.Read(buffer);
		file3.Close();
	} else
	{
		wxLogError(_("Failed to open file %s."), f.c_str());
		return 0;
	}
	buffer.Replace('\r',wxEmptyString,true);
	wxStringTokenizer lines(buffer, wxT("\n"));
	Diff_EditCost=4;
	Match_Threshold=0.5;
	Match_Distance=1000;
	
	bool mline=false;
	while (lines.HasMoreTokens())
	{
		wxString tmp = lines.GetNextToken();
		
		//if (tmp.length()>0 && tmp[tmp.length()-1]=='\r') tmp.RemoveLast();
		wxString line = tmp.Strip(wxString::both);
		if (tmp.StartsWith("@end@")) break;
		int l=wxString("@titleline").Len();
		if (tmp.StartsWith("@titleline")) {titleline=tmp.Mid(l)+"\n"; continue;}
		l=wxString("@[list").Len();
		if (tmp.StartsWith("@[list")) {list_head=tmp.Mid(l)+"\n"; continue;}
		l=wxString("@]list").Len();
		if (tmp.StartsWith("@]list")) {list_end=tmp.Mid(l)+"\n"; continue;}
		l=wxString("@rowlist").Len();
		if (tmp.StartsWith("@rowlist")) {rowlist=tmp.Mid(l)+"\n"; continue;}
		l=wxString("@tableheader2").Len();
		if (tmp.StartsWith("@tableheader2")) {tableheader2=tmp.Mid(l)+"\n"; continue;}
		
		if (tmp==wxString("@tableheader@")) {mline=!mline; continue;}
		if (mline) { tableheader+=tmp+"\n";continue;}
		// diff config
		if (tmp.StartsWith("@Match_Distance=")) {Match_Distance=(int)StrToDouble(tmp.After('=')); continue;}
		if (tmp.StartsWith("@Diff_EditCost=")) {Diff_EditCost=StrToDouble(tmp.After('=')); continue;}
		if (tmp.StartsWith("@Match_Threshold=")) {Match_Threshold=(float)StrToDouble(tmp.After('=')); continue;}
		
		head+=tmp+"\n";
	}
	//
{
			wxBusyInfo waiting(wxString::Format(" Search for differences ...",0));
			// Give the UI a chance to redraw
			wxSafeYield();
			wxMilliSleep(50);
			wxSafeYield();

	wxString content=printlvl(root,0,list,htab);
	wxString tmp(titleline);
	tmp.Replace("$titleline$","Left  : "+path);
	wxString tmp2(titleline);
	tmp2.Replace("$titleline$","Right: "+newpath);
	head.Replace("$titleline$",tmp+tmp2);

	head.Replace("$list$",content);
	head.Replace("$tables$",tableshtml);
	tableshtml=wxEmptyString;
	content=wxEmptyString;
}

	//head+="</div></body></html>";
    fDir=wxStandardPaths::Get().GetTempDir()+wxFileName::GetPathSeparator()+wxT("cmp.html");
	//fn="D:\\PostgreSQL\\cmp.html";
	fn=fDir;
	fn.MakeAbsolute();

	wxString html(head);
	wxFile file4(fn.GetFullPath(), wxFile::write);
	if (!file4.IsOpened())
	{
		wxLogError(_("Failed to open file %s."), fn.GetFullPath().c_str());
		return 0;
	}
	file4.Write(html, wxConvUTF8);
	file4.Close();
	head=wxEmptyString;

list.RemoveAt(0,list.GetCount());
e_count=list.GetCount();
for (int i = 0; i < e_count; ++i)
{
		//SQL *p=&sq;
	//list.RemoveAt(0,list.GetCount());
//		SQL *p = list.Detach(i);
//		delete p;
}
//htab.DeleteContents(true);
h_path.clear();
//MyHashSQL::iterator it, en;
//for( it = h_path.begin(), en = h_path.end(); it != en; ++it ) 
//{
//    h_path.erase(it);
//}
//htab.Clear();
//list.Empty();

#ifdef __WXMSW__
		wxLaunchDefaultBrowser(fn.GetFullPath());
#else
		wxLaunchDefaultBrowser(wxT("file://") + fn.GetFullPath());
#endif

	return 0;
}
#if defined(DELETE)
#undef DELETE
#endif // DUMMYSTRUCTNAME

std::wstring reportCompareFactory::printdiff(std::wstring str1, std::wstring str2 )
{
	Diff_EditCost = 4;
	Match_Threshold = 0.5;
	Match_Distance = 1000;
	  diff_match_patch dmp(Diff_EditCost,Match_Threshold,Match_Distance);
	  std::list<Diff> diffs;
	  if (str1==str2) {
		  return L"";
	  }
	  diffs=dmp.diff_main(str1,str2,true);
	  int nstart=0;
	  int pos=0;
	  countdiffline=0;
	  std::wstring cur_l;
	  std::wstring ncur_l; std::wstring p_ncur_l;std::wstring p_ncur_r;
	  std::wstring cur_r;
	  std::wstring ncur_r;
	  std::wstring tex;
	  std::wstring t;
	  std::wstring tableline;
	  int rline=1,lline=1;
	  std::list<Diff>::const_iterator it; // объявляем итератор
	  it = diffs.begin(); // присваиваем ему начало списка
	  Diff aDiff;
	  bool modify = false;
	  bool oneline = false;
	  nstart = 0;
	  while (it != diffs.end()) // пока итератор не достигнет конца
	  {
		aDiff=*it;
	    tex=aDiff.text;
		nstart=0;
	    while (nstart<tex.length()) {
			pos=tex.find('\n',nstart);
			if (pos==-1) {t.assign(tex,nstart,tex.length());nstart=tex.length();} else {t.assign(tex,nstart,pos-nstart);nstart=pos;}
			if (t.length()>0) {
				// это всё ещё одна строка
				if (aDiff.operation == Operation::INSERT) cur_r += L"<span class=\"differencei\">" + t + L"</span>";
				if (aDiff.operation == Operation::DELETE) cur_l += L"<span class=\"differenced\">" + t + L"</span>";
				if (aDiff.operation == Operation::EQUAL) {
					cur_r += t;
					cur_l += t;
				}
				else modify = true;
				// пока не встретим перевод строки считаем что это всё одна строка
				oneline = true;
			}
			else
			{
				// дошли до перевода \n
				nstart=pos+1;
				ncur_l=std::to_wstring(lline);
				ncur_l=L""; ncur_r=L"";
//				if (p_ncur_r==ncur_r) { ncur_r=L""; modify=true;}
//				if (p_ncur_l==ncur_l) { ncur_l=L""; modify=true;}
				
				std::wstring t_cur_l=cur_l;
				std::wstring t_cur_r=cur_r;
				if (aDiff.operation==Operation::DELETE) {
					t_cur_r=L"";
					ncur_l=std::to_wstring(lline);
					modify=true;
					lline++;
				} else  if (aDiff.operation==Operation::INSERT) {
					t_cur_l=L"";
					ncur_r=std::to_wstring(rline);
					modify=true;
					rline++;
				} else if (aDiff.operation==Operation::EQUAL) {
					ncur_r=std::to_wstring(rline);
					ncur_l=std::to_wstring(lline);
					rline++; lline++;
				}
				if (modify) countdiffline++;
//				if (( (ncur_r.empty()&&(!ncur_l.empty()))
//					||(ncur_l.empty()&&(!ncur_r.empty()))
//					)&&(!modify)) modify=true;
				// create columns
				//left
					tableline+=L"<tr><td class=\"diff_next\" onclick=\"c(this)\"></td>";
					 tableline+= modify ? L"<td class=\"has_difference\" onclick=\"d(this)\">"+ncur_l+"</td>" : L"<td class=\"diff_header\" onclick=\"d(this)\">"+ncur_l+"</td>";
					tableline+=L"<td class=\"lineContent\"><pre>"+t_cur_l+"</pre></td>";
				// right
				tableline+=L"<td class=\"diff_next\" onclick=\"c(this)\"></td>";
				tableline+= modify ? L"<td class=\"has_difference\" onclick=\"d(this)\">"+ncur_r+"</td>" : L"<td class=\"diff_header\" onclick=\"d(this)\">"+ncur_r+"</td>";
				tableline+=L"<td class=\"lineContent\"><pre>"+t_cur_r+"</pre></td>";
				tableline+=L"</tr>";



				if (ncur_r.length()>0) p_ncur_r=ncur_r;
				if (ncur_l.length()>0) p_ncur_l=ncur_l;
				
				if (aDiff.operation==Operation::DELETE) {
					cur_l=L"";
				} else  if (aDiff.operation==Operation::INSERT) {
					cur_r=L"";
				} else if (aDiff.operation==Operation::EQUAL) {
					cur_r=L"";cur_l=L"";
				}
				
				//
				modify=false;
				oneline=false;
			}
		} // цикл по строкам внутри одного Diff
	  ++it;
	  }
#ifdef _DEBUG
	wxFileName fn("cmp_debug.txt");
	fn.MakeAbsolute();

	wxFile file(fn.GetFullPath(), wxFile::write);
	if (!file.IsOpened())
	{
		wxLogError(_("Failed to open file %s."), fn.GetFullPath().c_str());
	}

wxString report;
	report.Append(wxString::Format(" Table\n%s\n",tableline));
	file.Write(report, wxConvUTF8);
	file.Close();

#endif

return tableline;
}
wxString reportCompareFactory::printlvl(int element,int lvl,ArraySQL &list, wxHashTable &htab)
{
	wxString l(list_head);

	wxString r=wxEmptyString;
	wxArrayInt* child;
	wxString key,name;
	int e=element;
	int nstart;
		SQL& sq=list.Item(e);
		if ((nstart=sq.pathtree.rfind('/'))!=wxNOT_FOUND ) { 
			name=sq.pathtree.AfterLast('/');
			key=sq.pathtree.Mid(startpathpos);
			
		}
		wxString tid=wxString::Format("id%d",e);
		wxString rlist=HtmlEntities(name);
		wxString cdiff=wxEmptyString;
		// таблица различий
		countdiffline = 0;
		if (sq.sql.length() + sq.sql2.length() > 0) {
			// для одинаковых не будем таблицу формировать
			std::wstring t1=sq.sql.wc_str();
			std::wstring t2(sq.sql2.wc_str());
			t1.erase(std::remove(t1.begin(), t1.end(), '\r'), t1.end());
			t2.erase(std::remove(t2.begin(), t2.end(), '\r'), t2.end());
			std::wstring rez=printdiff(t1,t2);
			if (rez.length()>0) {
				//L"</tbody></table>"
				//tableshtml
				wxString tmp(tableheader);
				tmp.Replace("@idtablecmp@",tid);
				tmp.Replace("$rowlist$",rlist);
				tableshtml+=tmp+"\n";
				tmp=tableheader2;
				tmp.Replace("@idtablecmp@",tid);
				tmp.Replace("$rowlist$",rlist);
				tableshtml+=tmp;
				tableshtml+=rez;
				tableshtml+="</tbody></table>\n";
				cdiff=wxString::Format(" ( %d )",countdiffline);
			}
		}

		// список объектов
		child=(wxArrayInt *)htab.Get(key);
		r=rowlist;
		r.Replace("$rowlist$",rlist+cdiff);
		if (sq.mode==__Insert) r.Replace("@color@","insert");
				else if (sq.mode==__Remove) r.Replace("@color@","remove");
				else if (countdiffline>0) r.Replace("@color@","ne"); else {
					if (child!=NULL) r.Replace("@color@","eq"); else r.Replace("@color@","eqhidden");
				}
		r.Replace("@idtablecmp@",tid);	
		l+=r;
		if (child!=NULL) {
			for (int j=0;j<child->GetCount();j++) {
				l+=printlvl(child->Item(j),lvl+1,list,htab);
			}
		}
	l+=list_end;
	return l;
}

///////////////////////////////////////////////////////
// Report base
///////////////////////////////////////////////////////
wxWindow *reportBaseFactory::StartDialog(frmMain *form, pgObject *obj)
{
	parent = form;

	wxBeginBusyCursor();
	frmReport *report = new frmReport(GetFrmMain());

	// Generate the report header
	wxDateTime now = wxDateTime::Now();
	report->XmlAddHeaderValue(wxT("generated"), now.Format(wxT("%c")));
	if (obj->GetServer())
		report->XmlAddHeaderValue(wxT("server"), obj->GetServer()->GetFullIdentifier());
	if (obj->GetDatabase())
		report->XmlAddHeaderValue(wxT("database"), obj->GetDatabase()->GetName());
	if (obj->GetSchema())
	{
		if (obj->GetSchema()->GetMetaType() == PGM_CATALOG)
			report->XmlAddHeaderValue(wxT("catalog"), obj->GetSchema()->GetDisplayName());
		else
			report->XmlAddHeaderValue(wxT("schema"), obj->GetSchema()->GetName());
	}
	if (obj->GetJob())
		report->XmlAddHeaderValue(wxT("job"), obj->GetJob()->GetName());
	if (obj->GetTable())
		report->XmlAddHeaderValue(wxT("table"), obj->GetTable()->GetName());

	GenerateReport(report, obj);
	wxEndBusyCursor();

	report->ShowModal();
	return 0;
}

///////////////////////////////////////////////////////
// Properties report
///////////////////////////////////////////////////////
reportObjectPropertiesFactory::reportObjectPropertiesFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar)
	: reportBaseFactory(list)
{
	mnu->Append(id, _("&Properties Report"), _("Generate a Properties report for this object."));
}

bool reportObjectPropertiesFactory::CheckEnable(pgObject *obj)
{
	if (obj)
	{
		if (obj->IsCollection())
			return false;
		else
			return true;
	}
	return false;
}

void reportObjectPropertiesFactory::GenerateReport(frmReport *report, pgObject *object)
{
	report->SetReportTitle(object->GetTranslatedMessage(PROPERTIESREPORT));

	int section = report->XmlCreateSection(object->GetTranslatedMessage(PROPERTIES));

	ctlListView *list = GetFrmMain()->GetProperties();
	object->ShowProperties();

	report->XmlAddSectionTableFromListView(section, list);

	report->XmlSetSectionSql(section, object->GetSql(NULL));
}

///////////////////////////////////////////////////////
// DDL report
///////////////////////////////////////////////////////
reportObjectDdlFactory::reportObjectDdlFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar)
	: reportBaseFactory(list)
{
	mnu->Append(id, _("&DDL Report"), _("Generate a DDL report for this object."));
}

bool reportObjectDdlFactory::CheckEnable(pgObject *obj)
{
	if (obj)
	{
		if (obj->GetMetaType() == PGM_SERVER || obj->GetMetaType() == PGM_CATALOGOBJECT || obj->IsCollection())
			return false;
		else
			return true;
	}
	return false;
}

void reportObjectDdlFactory::GenerateReport(frmReport *report, pgObject *object)
{
	report->SetReportTitle(object->GetTranslatedMessage(DDLREPORT));

	int section = report->XmlCreateSection(object->GetTranslatedMessage(DDL));

	report->XmlSetSectionSql(section, object->GetSql(NULL));
}

///////////////////////////////////////////////////////
// Data dictionary report
///////////////////////////////////////////////////////
reportObjectDataDictionaryFactory::reportObjectDataDictionaryFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar)
	: reportBaseFactory(list)
{
	mnu->Append(id, _("&Data Dictionary Report"), _("Generate a Data Dictionary report for this object."));
}

bool reportObjectDataDictionaryFactory::CheckEnable(pgObject *obj)
{
	if (obj)
	{
		if ((obj->GetMetaType() == PGM_TABLE || obj->GetMetaType() == GP_PARTITION) && !obj->IsCollection())
			return true;
		else
			return false;
	}
	return false;
}

void reportObjectDataDictionaryFactory::GenerateReport(frmReport *report, pgObject *object)
{

	pgTable *table = (pgTable *)object;

	report->SetReportTitle(object->GetTranslatedMessage(DATADICTIONNARYREPORT));

	// Columns
	int section = report->XmlCreateSection(_("Columns"));

	report->XmlSetSectionTableHeader(section, 6, (const wxChar *) _("Name"), (const wxChar *) _("Data type"), (const wxChar *) _("Not Null?"), (const wxChar *) _("Primary key?"), (const wxChar *) _("Default"), (const wxChar *) _("Comment"));

	ctlTree *browser = GetFrmMain()->GetBrowser();
	pgCollection *columns = table->GetColumnCollection(browser);

	treeObjectIterator colIt(browser, columns);

	pgColumn *column;
	bool haveInherit = false;
	wxString colName;
	while ((column = (pgColumn *)colIt.GetNextObject()) != 0)
	{
		column->ShowTreeDetail(browser);
		if (column->GetColNumber() > 0)
		{
			colName = column->GetName();
			if (column->GetInheritedCount() > 0)
			{
				colName += _("*");
				haveInherit = true;
			}

			report->XmlAddSectionTableRow(section,
			                              column->GetColNumber(),
			                              6,
			                              (const wxChar *) colName,
			                              (const wxChar *) column->GetVarTypename(),
			                              (const wxChar *) BoolToYesNo(column->GetNotNull()),
			                              (const wxChar *) BoolToYesNo(column->GetIsPK()),
			                              (const wxChar *) column->GetDefault(),
			                              (const wxChar *) column->GetComment());
		}
	}
	if (haveInherit)
	{
		wxString info;
		info.Printf(_("* Inherited columns from %s."), table->GetInheritedTables().c_str());
		report->XmlSetSectionTableInfo(section, info);
	}

	// Constraints
	pgCollection *constraints = table->GetConstraintCollection(browser);

	treeObjectIterator conIt(browser, constraints);

	pgObject *constraint;
	long x = 1;
	wxString definition, type;
	while ((constraint = (pgObject *)conIt.GetNextObject()) != 0)
	{
		if (x == 1)
		{
			section = report->XmlCreateSection(_("Constraints"));
			report->XmlSetSectionTableHeader(section, 4, (const wxChar *) _("Name"), (const wxChar *) _("Type"), (const wxChar *) _("Definition"), (const wxChar *) _("Comment"));
		}

		constraint->ShowTreeDetail(browser);

		switch (constraint->GetMetaType())
		{
			case PGM_PRIMARYKEY:
				type = _("Primary key");
				definition = ((pgIndexConstraint *)constraint)->GetDefinition();
				break;
			case PGM_UNIQUE:
				type = _("Unique");
				definition = ((pgIndexConstraint *)constraint)->GetDefinition();
				break;
			case PGM_FOREIGNKEY:
				type = _("Foreign key");
				definition = ((pgForeignKey *)constraint)->GetDefinition(PGM_FOREIGNKEY);
				break;
			case PGM_EXCLUDE:
				type = _("Exclude");
				definition = ((pgIndexConstraint *)constraint)->GetDefinition();
				break;
			case PGM_CHECK:
				type = _("Check");
				definition = wxT("(") + ((pgCheck *)constraint)->GetDefinition() + wxT(")");
				break;
		}

		report->XmlAddSectionTableRow(section,
		                              x,
		                              4,
		                              (const wxChar *) constraint->GetName(),
		                              (const wxChar *) type,
		                              (const wxChar *) definition,
		                              (const wxChar *) constraint->GetComment());
		x++;
	}
}

///////////////////////////////////////////////////////
// Statistics report
///////////////////////////////////////////////////////
reportObjectStatisticsFactory::reportObjectStatisticsFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar)
	: reportBaseFactory(list)
{
	mnu->Append(id, _("&Statistics Report"), _("Generate a Statistics report for this object."));
}

bool reportObjectStatisticsFactory::CheckEnable(pgObject *obj)
{
	if (obj)
	{
		if (!obj->HasStats())
		{
			if (obj->IsCollection())
			{
				pgaFactory *f = obj->GetFactory();

				if (f)
				{
					if (f->GetMetaType() == PGM_TABLE ||
					        f->GetMetaType() == GP_PARTITION ||
					        f->GetMetaType() == PGM_TABLESPACE ||
					        f->GetMetaType() == PGM_DATABASE)
						return true;
				}
				else
				{
					return false;
				}
			}
			else
			{
				return false;
			}

		}
		else
		{
			return true;
		}
	}

	return false;
}

void reportObjectStatisticsFactory::GenerateReport(frmReport *report, pgObject *object)
{
	report->SetReportTitle(object->GetTranslatedMessage(STATISTICSREPORT));

	int section = report->XmlCreateSection(object->GetTranslatedMessage(OBJSTATISTICS));

	ctlListView *list = GetFrmMain()->GetStatistics();
	object->ShowStatistics(GetFrmMain(), list);

	report->XmlAddSectionTableFromListView(section, list);
}

///////////////////////////////////////////////////////
// Dependencies report
///////////////////////////////////////////////////////
reportObjectDependenciesFactory::reportObjectDependenciesFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar)
	: reportBaseFactory(list)
{
	mnu->Append(id, _("&Dependencies Report"), _("Generate a Dependencies report for this object."));
}

bool reportObjectDependenciesFactory::CheckEnable(pgObject *obj)
{
	if (obj)
	{
		if (!obj->HasDepends())
			return false;
		else
			return true;
	}
	return false;
}

void reportObjectDependenciesFactory::GenerateReport(frmReport *report, pgObject *object)
{
	report->SetReportTitle(object->GetTranslatedMessage(DEPENDENCIESREPORT));

	int section = report->XmlCreateSection(object->GetTranslatedMessage(DEPENDENCIES));

	ctlListView *list = GetFrmMain()->GetDependencies();
	object->ShowDependencies(parent, list);

	report->XmlAddSectionTableFromListView(section, list);
}

///////////////////////////////////////////////////////
// Dependents report
///////////////////////////////////////////////////////
reportObjectDependentsFactory::reportObjectDependentsFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar)
	: reportBaseFactory(list)
{
	mnu->Append(id, _("&Dependents Report"), _("Generate a Dependents report for this object."));
}

bool reportObjectDependentsFactory::CheckEnable(pgObject *obj)
{
	if (obj)
	{
		if (!obj->HasReferences())
			return false;
		else
			return true;
	}
	return false;
}

void reportObjectDependentsFactory::GenerateReport(frmReport *report, pgObject *object)
{
	report->SetReportTitle(object->GetTranslatedMessage(DEPENDENTSREPORT));

	int section = report->XmlCreateSection(object->GetTranslatedMessage(DEPENDENTS));

	ctlListView *list = GetFrmMain()->GetReferencedBy();
	object->ShowDependents(parent, list);

	report->XmlAddSectionTableFromListView(section, list);
}

///////////////////////////////////////////////////////
// Object list report
///////////////////////////////////////////////////////
reportObjectListFactory::reportObjectListFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar)
	: reportBaseFactory(list)
{
	mnu->Append(id, _("&Object List Report"), _("Generate an Object List report for this collection."));
}

bool reportObjectListFactory::CheckEnable(pgObject *obj)
{
	if (obj)
	{
		if (!obj->IsCollection())
		{
			return false;
		}
		else
		{
			pgaFactory *f = obj->GetFactory();
			if (obj)
			{
				if (f->GetMetaType() == PGM_SERVER)
					return false;
			}
			else
				return false;

			return true;
		}
	}
	return false;
}

void reportObjectListFactory::GenerateReport(frmReport *report, pgObject *object)
{
	report->SetReportTitle(object->GetTranslatedMessage(OBJECTSLISTREPORT));

	int section = report->XmlCreateSection(object->GetFullIdentifier());

	ctlListView *list = GetFrmMain()->GetProperties();
	object->ShowProperties();

	report->XmlAddSectionTableFromListView(section, list);
}


