using Microsoft.Reporting.WebForms;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Text.RegularExpressions;
//using System.Web.Mvc;

//using VRH.Log4Pro.MultiLanguageManager;
using VRH.Common;
//using Vrh.Web.Common.Lib;
using Vrh.XmlProcessing;
using System.Data.Entity.Core.Objects;

namespace Vrh.OneReport
{
    /// <summary>
    /// A riport előállítással kapcsolatos szolgáltatások.
    /// </summary>
    public class ReportService
    {
        #region Constants
        /// <summary>
        /// Az osztály egyetlen publikus állandója.
        /// </summary>
        public const string ERR_LCIDSTAR = "\"LCID=*\" parameter is invalid in this context!";

		/// <summary>
		/// Hibaüzenetek
		/// </summary>
        private const string ERR_REPORTISNULL = "The report has not been initialized yet! Run the ReadReport method first!";
		private const string ERR_CONFIGITEMREQUIRED = "OneReport parameter connection string MUST have config item!!!";
		private const string ERR_READREPORTPARAMETERS = "Error when reading/processing report parameters";
		private const string ERR_CHECKREPORT = "Error when check the riports! \"{0}\"";
		private const string ERR_LISTREPORT = "Error when list the {0}!";
		private const string ERR_DISPLAYREPORT = "Error when generating report! Report {0}";
		private const string ERR_EXPORTREPORT = "Error when export report! Report {0}";
		private const string ERR_TOFILEFORMAT = "The correct values of the tofile format parameter in the OneReport.File action: {0}";
		private const string ERR_EXPORTFORMAT = "The correct values of the export format parameter in the OneReport.File action: {0}";
		private const string ERR_EXPORTDATASETMISSING = "DataSet is missing! Report.Id = \"{0}\"";
		private const string SUCCESSMESSAGE_FILEOREXPORT_ONEFILE = "OneReport {0} action is successful. \nReport Id: {1}, \nFormat: {3}, \nFile: {2}";
		private const string SUCCESSMESSAGE_FILEOREXPORT_MANYFILE = "OneReport {0} action is successful. \nReport Id: {1}, \nFormat: {2}";
		#endregion Constants

		#region Built in static classes
		/// <summary>
		/// A List akcióhoz és szolgáltatáshoz kapcsolódó listázási módokat 
		/// jelölő állandó értékek.
		/// </summary>
		public static class ListModes
        {
            /// <summary>
            /// Riportok listája a preset beállításaikkal együtt
            /// + az alapértelmezettpreset "*".
            /// value: reportid/lcid/presetid, text: reportname, presetname (reportid/lcid/presetid) 
            /// </summary>
            public const string PresetYes = "a";            // value: id/lcid/presetid, text: name, presetname (id/lcid/presetid) 

            /// <summary>
            /// Riportok listája a preset beállítások nélkül
            /// Ez az alapértelmezés.
            /// value: reportid/lcid/*, text: reportname, presetname (reportid/lcid) 
            /// </summary>
            public const string PresetNo = "b";

            /// <summary>
            /// Riportok listája a preset beállítások nélkül.
            /// value: reportid, text: name (reportid/lcid)
            /// </summary>
            public const string OnlyId = "c";               

            /// <summary>
            /// Egy megadott riport preseteit tartalmazó lista.
            /// value: preset id, text: presetname (preset id)
            /// </summary>
            public const string OnlyPreset = "d";

            /// <summary>
            /// Olyan, mint a "PresetYes", csak az alapértelmezett presetek nélkül
            /// value: reportid/lcid/presetid, text: reportname, presetname (reportid/lcid/presetid) 
            /// </summary>
            public const string WithoutDefaultPreset = "e";
        }
        #endregion Built in static classes

        #region Private variables

        private readonly string m_BasePath;

        #endregion Private variables

        #region Properties

        #region LogEntries property
        /// <summary>
        /// A végrehajtott metódusok során keletkező naplózási bejegyzések.
        /// Minden metódus indításakor törlődik.
        /// </summary>
        public List<string> LogEntries { get; private set; } = new List<string>();
        #endregion LogEntries property

        /// <summary>
        /// A kapcsolati sztringben megjelölt riport XML-ben
        /// definiált tartalma.
        /// </summary>
        public OneReportXmlParser Report { get; private set; }

        #endregion Properties

        #region Constructor(s)

        /// <summary>
        /// ReportService egy példánya az alkalmazás alap mappájának megadásával.
        /// A metódusokban érkező relatív útvonalak feloldásához.
        /// </summary>
        /// <param name="basePath"></param>
        public ReportService(string basePath)
        {
            m_BasePath = basePath ?? "";
        }

        #endregion

        #region Public methods

        #region Check public methods
        /// <summary>
        /// Riportok létezését ellenőrző akció.
        /// </summary>
        /// <param name="xml">XmlParser kapcsolati sztring, amely a ReportDirectory xml struktúrára mutat.</param>
        /// <param name="lcid">A környezetben érvényes nyelvi kód.</param>
        /// <param name="checklcid">
        /// Azt mutatja meg, hogy olyan riportokat ellenőrizzen, melyek az adott nyelven el tudnak készülni.
        /// Ha "*" akkor az összes report között ellenőriz a nyelvi beállítástól függetlenül.
        /// </param>
        /// <param name="reportNames">Riportazonosítók vesszővel elválasztva.</param>
        /// <param name="otherVars">A szolgáltatásnak átadott egyéb paramétereket tartalmazó szótár.</param>
        /// <returns>Egy lista a riport azonosítókkal, és mellete, hogy létezik-e még.</returns>
        public List<CheckListJSON> Check(
            string xml, 
            string lcid,
            string checklcid, 
            string reportNames,
            Dictionary<string, string> otherVars = null
            )
        {
            try
            {
                var result = new List<CheckListJSON>();

                checklcid = string.IsNullOrWhiteSpace(checklcid) ? lcid : checklcid.Trim();
                if (checklcid == "*") checklcid = "";

				if (string.IsNullOrWhiteSpace(xml)) { xml = $"config={Constants.DEFAULT_CONFIG};"; }
				var xmlcDir = new XmlConnection(xml);
				using (var directory = new DirectoryXmlParser(xmlcDir, m_BasePath, lcid, otherVars))
				{
                    var reportNamesArray = reportNames.Split(',');

                    foreach (var reportName in reportNamesArray)
                    {
                        var rep = directory.Reports.FirstOrDefault(x => x.Id == reportName);
                        result.Add(new CheckListJSON
                        {
                            Value = reportName,
                            Text = "",
                            Checked = rep == null ? false : (string.IsNullOrWhiteSpace(checklcid) || rep.LCIDList.Contains(checklcid))
                        });
                    }
                }
                return result;
            }
            catch (Exception ex) { throw new Exception(string.Format(ERR_CHECKREPORT,reportNames), ex); }
        }
		#endregion Check public methods

		#region List public method
		/// <summary>
		/// Riport listát visszaadó metódus.
		/// </summary>
		/// <param name="xml">XmlParser kapcsolati sztring, amely a ReportDirectory xml struktúrára mutat.</param>
		/// <param name="lcid">A lista ilyen nyelven fog elkészülni</param>
		/// <param name="listmode">
		/// A listázás módja, mely meghatározza a megjelenítést és az azonosító összetételét.
		/// </param>
		/// <param name="listlcid">
		/// Azt mutatja meg, hogy olyan riportok legyenek a listában, melyek az adott nyelven el tudnak készülni.
		/// Ha "*" akkor az összes report bekerül a listába a nyelvi beállítástól függetlenül.</param>
		/// <param name="reportId"></param>
		/// <param name="otherVars">A szolgáltatásnak átadott egyéb paramétereket tartalmazó szótár.</param>
		/// <returns>Egy riport lista, ha nincs elem, akkor üres lista nem null.</returns>
		public List<SelectListJSON> List(
            string xml, 
            string lcid,
            string listmode = ListModes.PresetNo,
            string listlcid = null,
            string reportId = null,
            Dictionary<string, string> otherVars = null
            )
        {
            try
            {
                if (String.IsNullOrEmpty(listmode)) listmode = ListModes.PresetNo;
                else
                {   //ha érvénytelen a listmode, akkor a PresetNo lesz, és akkor is, ha OnlyPreset-nél nem ad meg ReportId-t.
                    listmode = listmode.ToLower();
                    if (listmode != ListModes.PresetYes
                        && listmode != ListModes.PresetNo
                        && listmode != ListModes.OnlyId
                        && listmode != ListModes.OnlyPreset
                        && listmode != ListModes.WithoutDefaultPreset) listmode = ListModes.PresetNo;
                    //if (listmode == ListModes.OnlyPreset && String.IsNullOrWhiteSpace(reportId)) listmode = ListModes.PresetNo;
                }
                listlcid = string.IsNullOrWhiteSpace(listlcid) ? lcid : listlcid.Trim();
                if (listlcid == "*") listlcid = null;

				if (string.IsNullOrWhiteSpace(xml)) { xml = $"config={Constants.DEFAULT_CONFIG};"; }
				var xmlcDir = new XmlConnection(xml);
				using (var directory = new DirectoryXmlParser(xmlcDir, m_BasePath, lcid, otherVars))
                {
                    return directory.List(listmode, listlcid, reportId);
                }
            }
            catch (Exception ex) {   throw new Exception(string.Format(ERR_LISTREPORT,(listmode.ToLower() == ListModes.OnlyPreset ? "presets" : "reports")), ex);   }

        }
        #endregion List public method

        #region ListFormats public method
        /// <summary>
        /// Riport formátumok listája.
        /// </summary>
        /// <returns>Riport formátumok listája.</returns>
        public List<SelectListJSON> ListFormats()
        {
			return ListFormatsGeneric<ToFileFormats>();
        }
		#endregion ListFormats public method

		#region ListAllFormats public method
		/// <summary>
		/// Riport formátumok listája.
		/// </summary>
		/// <returns>Riport formátumok listája.</returns>
		public List<SelectListJSON> ListAllFormats()
		{
			return ListFormatsGeneric<AllFormats>();
		}
		#endregion ListAllFormats public method

		#region ListExportFormats public method
		/// <summary>
		/// Riport formátumok listája.
		/// </summary>
		/// <returns>Riport formátumok listája.</returns>
		public List<SelectListJSON> ListExportFormats()
		{
			return ListFormatsGeneric<ExportFormats>();
		}
		#endregion ListExportFormats public method

		#region ReadReport method
		/// <summary>
		/// OneReport xml beolvasása.
		/// </summary>
		/// <param name="xmlConnectionString">XmlParser kapcsolati sztring. Egyből egy riportra mutat.</param>
		/// <param name="lcid">A környezetben érvényes vagy a kért nyelvi kód.</param>
		/// <param name="otherVars">A szolgáltatásnak átadott egyéb paramétereket tartalmazó szótár.</param>
		/// <returns></returns>
		public void ReadReport(string xmlConnectionString, string lcid, Dictionary<string, string> otherVars)
        {
            try
            {
				if (string.IsNullOrWhiteSpace(xmlConnectionString)) { xmlConnectionString = $"config={Constants.DEFAULT_CONFIG};"; }
				var xmlc = new XmlConnection(xmlConnectionString);
				if (string.IsNullOrWhiteSpace(xmlc.ConfigurationName)) { throw new Exception(ERR_CONFIGITEMREQUIRED); }
				this.Report = new OneReportXmlParser(xmlc, m_BasePath, lcid, otherVars);
            }
            catch (Exception ex) {   throw new ApplicationException(ERR_READREPORTPARAMETERS, ex);   }
        }
        #endregion ReadReport method

        #region ReportToReportViewer
        /// <summary>
        /// A beolvasott riport alapján visszaad egy ReportViewer típust.
        /// </summary>
        /// <returns></returns>
        public ReportViewer ReportToReportViewer()
        {
            try
            {
                if (this.Report == null) { throw new ApplicationException(ERR_REPORTISNULL); }

                var reportViewer = new ReportViewer
                {
                    ProcessingMode = ProcessingMode.Local,
                    SizeToReportContent = true,
                };

				reportViewer.LocalReport.ReportPath = PrepareRdl(this.Report,autopurgeparameters: this.Report.AutoPurge);

				foreach (var dataset in this.Report.Datasets) { reportViewer.LocalReport.DataSources.Add(CreateOneDataSource(dataset,this.Report.Parameters)); }
				reportViewer.LocalReport.SetParameters(GetReportParameters());
				return reportViewer;
            }
            catch (Exception ex) { throw new ApplicationException(string.Format(ERR_DISPLAYREPORT,(this.Report == null ? "Report is empty!" : "Id=" + this.Report.Id)),ex ); }
        }
		#endregion ReportToReportViewer

		#region FileOrExport
		/// <summary>
		/// Egy riport adatainak exportálása egy fájlba.
		/// </summary>
		/// <param name="xmlConnectionString">XmlParser kapcsolati sztring.</param>
		/// <param name="format">Milyen formátumban történjen az export.</param>
		/// <param name="file">A riport ebbe a fájlba fog elkészülni.</param>
		/// <param name="lcid">A környezetben érvényes nyelvi kód.</param>
		/// <param name="otherVars">Az XmlParser egyéb változói.</param>
		/// <returns>success message</returns>
		public ReturnInfoJSON FileOrExport(
			string xmlConnectionString,
			string format,
			string file,
			string lcid,
			Dictionary<string, string> otherVars)
		{
			string thisfn = string.Concat(nameof(ReportService), ".", nameof(FileOrExport), ": ");
			#region DEBUG
#if DEBUG
			System.Diagnostics.Debug.WriteLine($"{thisfn}START");
			System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
			sw.Start();
#endif
			#endregion DEBUG
			this.LogEntries.Clear();
			this.LogEntries.Add($"*** Logs of the {thisfn} ***");
			this.LogEntries.Add($"Parameters: xmlConnectionString={xmlConnectionString}, format={format}, file={file}, lcid={lcid}");
			#region DEBUG
#if DEBUG
			System.Diagnostics.Debug.WriteLine($"{thisfn}{this.LogEntries.Last()}");
#endif
			#endregion DEBUG

			#region Paraméterek ellenőrzése

			#region lcid ellenőrzése
			lcid = lcid?.Trim();
			if (lcid == "*") throw new ApplicationException(ERR_LCIDSTAR);
			// ha LCID üres, akkor majd a ReadReport beállítja az XML-ben lévő alapértelmezett LCID-et.
			#endregion lcid ellenőrzése

			#region DEBUG
#if DEBUG
			sw.Stop();
			System.Diagnostics.Debug.WriteLine($"{thisfn}Parameters check elapsed time: {sw.ElapsedMilliseconds}ms");
			sw.Start();
#endif
			#endregion DEBUG

			#endregion Paraméterek ellenőrzése

			#region Riport xml feldolgozása
			this.LogEntries.Add($"ReadReport START!");
			this.ReadReport(xmlConnectionString, lcid, otherVars);
			if (this.Report.ErrorMessages.Any()) { throw new ApplicationException(string.Join("\n", this.Report.ErrorMessages)); }
			this.LogEntries.Add($"ReadReport OK!");
			#region DEBUG
#if DEBUG
			sw.Stop();
			System.Diagnostics.Debug.WriteLine($"{thisfn}ReadReport elapsed time: {sw.ElapsedMilliseconds}ms");
			sw.Start();
#endif
			#endregion DEBUG
			#endregion Riport xml feldolgozása

			#region Formátum beállítása, ellenőrzése
			if (!string.IsNullOrWhiteSpace(format))
			{
				if (Enum.TryParse<AllFormats>(format, true, out AllFormats frm)) { this.Report.Formats.AllFormats = frm; }
				else { throw new ApplicationException(string.Format(ERR_EXPORTFORMAT, string.Join(",", Enum.GetNames(typeof(AllFormats))))); }
			}
			#endregion Formátum beállítása, ellenőrzése

			#region Fájl nevének és elérésének beállítása
			if (string.IsNullOrWhiteSpace(file))
			{
				#region Extension beállítása
				string formatExtension;
				switch (this.Report.Formats.AllFormats)
				{
					case AllFormats.XML: formatExtension = "xml"; break;
					case AllFormats.CSV: formatExtension = "csv"; break;
					case AllFormats.XLS: formatExtension = "xls"; break;
					case AllFormats.DOC: formatExtension = "doc"; break;
					case AllFormats.PDF:
					default:
						formatExtension = "pdf"; break;
				}
				this.LogEntries.Add($"Format OK! Extension=\"{formatExtension}\"");
				#endregion Extension beállítása

				string fileName = $"{Path.GetFileNameWithoutExtension(this.Report.ReportFile)}.{formatExtension}";

				string folderPath = this.Report.Folders.Export;
				if (string.IsNullOrWhiteSpace(folderPath))
				{
					folderPath = Path.GetDirectoryName(this.Report.ReportFile);
				}
				if (folderPath.StartsWith(VrhCommon.SIGN_RELATIVE)) folderPath = VrhCommon.RealPath(m_BasePath, folderPath);
				if (!Directory.Exists(folderPath)) folderPath = Path.GetDirectoryName(this.Report.ReportFile);
				file = Path.Combine(folderPath, fileName);
			}
			else
			{
				if (file.StartsWith(VrhCommon.SIGN_RELATIVE)) file = VrhCommon.RealPath(m_BasePath, file);
			}
			#endregion Fájl nevének és elérésének beállítása

			#region processing
			ReturnInfoJSON rij = new ReturnInfoJSON();
			switch (this.Report.Formats.AllFormats)
			{
				case AllFormats.XML:
				case AllFormats.CSV:
					var fdir = Path.GetDirectoryName(file);
					var fnam = Path.GetFileNameWithoutExtension(file);
					var fext = Path.GetExtension(file);
					this.Report.TargetFile = Path.Combine(fdir, string.Concat(fnam, Constants.DATASETNAMEMASK, fext));
					ExportToFile();

					rij.ReturnMessage = String.Format(SUCCESSMESSAGE_FILEOREXPORT_MANYFILE, nameof(FileOrExport), this.Report.Id, this.Report.Formats.AllFormats);
					foreach (var dset in this.Report.Datasets) { rij.ReturnMessage = String.Concat(rij.ReturnMessage, "\nFile: ", dset.ExportFile); }
					break;
				case AllFormats.XLS:
				case AllFormats.DOC:
				case AllFormats.PDF:
				default:
					this.Report.TargetFile = file;

					#region DEBUG
#if DEBUG
					sw.Stop();
					sw.Start();
#endif
					#endregion DEBUG
					byte[] readyReport = ReportToByteArray();
					this.LogEntries.Add($"Reporting OK!");
					#region DEBUG
#if DEBUG
					sw.Stop();
					System.Diagnostics.Debug.WriteLine($"{thisfn}Reporting elapsed time: {sw.ElapsedMilliseconds}ms");
					sw.Start();
#endif
					#endregion DEBUG

					this.LogEntries.Add($"TargetFile = '{this.Report.TargetFile}'");
					var saveFiledirectory = Path.GetDirectoryName(this.Report.TargetFile);
					if (!Directory.Exists(saveFiledirectory)) { Directory.CreateDirectory(saveFiledirectory); }
					File.WriteAllBytes(this.Report.TargetFile, readyReport);
					this.LogEntries.Add($"Report to file OK!");
					#region DEBUG
#if DEBUG
					sw.Stop();
					System.Diagnostics.Debug.WriteLine($"{thisfn}Report to file elapsed time: {sw.ElapsedMilliseconds}ms");
#endif
					#endregion DEBUG

					rij.ReturnMessage = String.Format(SUCCESSMESSAGE_FILEOREXPORT_ONEFILE, nameof(FileOrExport), this.Report.Id, this.Report.Formats.AllFormats, this.Report.TargetFile);
					break;
			}
			return rij;
			#endregion processing
		}
		#endregion FileOrExport

		#region Export public method
		/// <summary>
		/// Egy riport adatainak exportálása egy fájlba.
		/// </summary>
		/// <param name="xmlConnectionString">XmlParser kapcsolati sztring.</param>
		/// <param name="format">Milyen formátumban történjen az export.</param>
		/// <param name="file">A riport ebbe a fájlba fog elkészülni.</param>
		/// <param name="lcid">A környezetben érvényes nyelvi kód.</param>
		/// <param name="otherVars">Az XmlParser egyéb változói.</param>
		/// <returns></returns>
		public void Export(
			string xmlConnectionString,
			string format,
			string file,
			string lcid,
			Dictionary<string, string> otherVars)
		{
			string thisfn = string.Concat(nameof(ReportService), ".", nameof(Export), ": ");
#region DEBUG
#if DEBUG
            System.Diagnostics.Debug.WriteLine($"{thisfn}START");
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();
#endif
#endregion DEBUG
			this.LogEntries.Clear();
			this.LogEntries.Add($"*** Logs of the {thisfn} ***");
			this.LogEntries.Add($"Parameters: xmlConnectionString={xmlConnectionString}, format={format}, file={file}, lcid={lcid}");
#region DEBUG
#if DEBUG
            System.Diagnostics.Debug.WriteLine($"{thisfn}{this.LogEntries.Last()}");
#endif
#endregion DEBUG

			#region Paraméterek ellenőrzése

			#region lcid ellenőrzése
			lcid = lcid?.Trim();
			if (lcid == "*") throw new ApplicationException(ERR_LCIDSTAR);
			// ha LCID üres, akkor majd a ReadReport beállítja az XML-ben lévő alapértelmezett LCID-et.
			#endregion lcid ellenőrzése

#region DEBUG
#if DEBUG
            sw.Stop();
            System.Diagnostics.Debug.WriteLine($"{thisfn}Parameters check elapsed time: {sw.ElapsedMilliseconds}ms");
            sw.Start();
#endif
#endregion DEBUG

			#endregion Paraméterek ellenőrzése

			#region Riport xml feldolgozása
			this.LogEntries.Add($"ReadReport START!");
			this.ReadReport(xmlConnectionString, lcid, otherVars);
			if (this.Report.ErrorMessages.Any()) {  throw new ApplicationException(string.Join("\n", this.Report.ErrorMessages));  }
			this.LogEntries.Add($"ReadReport OK!");
#region DEBUG
#if DEBUG
            sw.Stop();
            System.Diagnostics.Debug.WriteLine($"{thisfn}ReadReport elapsed time: {sw.ElapsedMilliseconds}ms");
            sw.Start();
#endif
#endregion DEBUG
			#endregion Riport xml feldolgozása

			#region Formátum beállítása, ellenőrzése
			if (!string.IsNullOrWhiteSpace(format))
			{
				if (Enum.TryParse<AllFormats>(format, true, out AllFormats frm)) { this.Report.Formats.AllFormats = frm; }
				else { throw new ApplicationException(string.Format(ERR_EXPORTFORMAT, string.Join(",", Enum.GetNames(typeof(AllFormats))))); }
			}
			#endregion Formátum beállítása, ellenőrzése

			#region Fájl nevének és elérésének beállítása
			if (string.IsNullOrWhiteSpace(file))
			{
				#region Extension beállítása
				string formatExtension;
				switch (this.Report.Formats.AllFormats)
				{
					case AllFormats.XML: formatExtension = "xml"; break;
					case AllFormats.CSV:
					default: formatExtension = "csv"; break;
				}
				this.LogEntries.Add($"Format OK! Extension=\"{formatExtension}\"");
				#endregion Extension beállítása

				string fileName = $"{Path.GetFileNameWithoutExtension(this.Report.ReportFile)}.{formatExtension}";

				string folderPath = this.Report.Folders.Export;
				if (string.IsNullOrWhiteSpace(folderPath))
				{
					folderPath = Path.GetDirectoryName(this.Report.ReportFile);
				}
				if (folderPath.StartsWith(VrhCommon.SIGN_RELATIVE)) folderPath = VrhCommon.RealPath(m_BasePath, folderPath);
				if (!Directory.Exists(folderPath)) folderPath = Path.GetDirectoryName(this.Report.ReportFile);
				file = Path.Combine(folderPath, fileName);
			}
			else
			{
				if (file.StartsWith(VrhCommon.SIGN_RELATIVE)) { file = VrhCommon.RealPath(m_BasePath, file); }
			}
			var fdir = Path.GetDirectoryName(file);
			var fnam = Path.GetFileNameWithoutExtension(file);
			var fext = Path.GetExtension(file);

			#endregion Fájl nevének és elérésének beállítása

			this.Report.TargetFile = Path.Combine(fdir, string.Concat(fnam, Constants.DATASETNAMEMASK, fext));
			ExportToFile();
		}

		#endregion Export public method

		#region ToFile public method
		/// <summary>
		/// Egy riport elkészítése egy fájlba.
		/// </summary>
		/// <param name="xmlConnectionString">XmlParser kapcsolati sztring.</param>
		/// <param name="format">Milyen formátumban történjen az export.</param>
		/// <param name="file">A riport ebbe a fájlba fog elkészülni.</param>
		/// <param name="lcid">A környezetben érvényes nyelvi kód.</param>
		/// <param name="otherVars">Az XmlParser egyéb változói.</param>
		/// <returns></returns>
		public void ToFile(
            string xmlConnectionString,
            string format,
            string file,
            string lcid,
            Dictionary<string,string> otherVars)
        {
            string thisfn = string.Concat(nameof(ReportService), ".", nameof(ToFile), ": ");
            #region DEBUG
#if DEBUG
            System.Diagnostics.Debug.WriteLine($"{thisfn}START");
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();
#endif
            #endregion DEBUG
            this.LogEntries.Clear();
            this.LogEntries.Add($"*** Logs of the {thisfn} ***");
            this.LogEntries.Add($"Parameters: xmlConnectionString={xmlConnectionString}, format={format}, file={file}, lcid={lcid}");
			#region DEBUG
#if DEBUG
            System.Diagnostics.Debug.WriteLine($"{thisfn}{this.LogEntries.Last()}");
#endif
			#endregion DEBUG

			#region Paraméterek ellenőrzése

			#region lcid ellenőrzése
			lcid = lcid?.Trim();
            if (lcid == "*") throw new ApplicationException(ERR_LCIDSTAR);
            // ha LCID üres, akkor majd a ReadReport beállítja az XML-ben lévő alapértelmezett LCID-et.
            #endregion lcid ellenőrzése

            #region DEBUG
#if DEBUG
            sw.Stop();
            System.Diagnostics.Debug.WriteLine($"{thisfn}Parameters check elapsed time: {sw.ElapsedMilliseconds}ms");
            sw.Start();
#endif
            #endregion DEBUG

            #endregion Paraméterek ellenőrzése

            #region Riport xml feldolgozása
            this.LogEntries.Add($"ReadReport START!");
            this.ReadReport(xmlConnectionString, lcid, otherVars);
            if (this.Report.ErrorMessages.Any())
            {
                throw new ApplicationException(string.Join("\n", this.Report.ErrorMessages));
            }
            this.LogEntries.Add($"ReadReport OK!");
            #region DEBUG
#if DEBUG
            sw.Stop();
            System.Diagnostics.Debug.WriteLine($"{thisfn}ReadReport elapsed time: {sw.ElapsedMilliseconds}ms");
            sw.Start();
#endif
            #endregion DEBUG
            #endregion Riport xml feldolgozása

            #region Formátum beállítása, ellenőrzése
            if (!string.IsNullOrWhiteSpace(format))
            {
				if (Enum.TryParse<AllFormats>(format, true, out AllFormats frm)) { this.Report.Formats.AllFormats = frm; }
				else { throw new ApplicationException(string.Format(ERR_TOFILEFORMAT,string.Join(",", Enum.GetNames(typeof(AllFormats))))); }
			}
            this.LogEntries.Add($"Format OK! Extension=\"{this.Report.Formats.AllFormats}\"");
			#endregion Formátum beállítása, ellenőrzése

			#region Fájl nevének és elérésének beállítása
			if (string.IsNullOrWhiteSpace(file))
			{
				string fileName = $"{Path.GetFileNameWithoutExtension(this.Report.ReportFile)}.{this.Report.Formats.AllFormats}";

				string folderPath = this.Report.Folders.File;
				if (string.IsNullOrWhiteSpace(folderPath))
				{
					folderPath = Path.GetDirectoryName(this.Report.ReportFile);
				}
				if (folderPath.StartsWith(VrhCommon.SIGN_RELATIVE)) folderPath = VrhCommon.RealPath(m_BasePath, folderPath);
				if (!Directory.Exists(folderPath)) folderPath = Path.GetDirectoryName(this.Report.ReportFile);
				this.Report.TargetFile = Path.Combine(folderPath, fileName);
			}
			else
			{
				this.Report.TargetFile = file.StartsWith(VrhCommon.SIGN_RELATIVE) ? VrhCommon.RealPath(m_BasePath, file) : file;
			}
			#endregion Fájl nevének és elérésének beállítása

			#region Reporting
			#region DEBUG
#if DEBUG
			sw.Stop();
            sw.Start();
#endif
            #endregion DEBUG
            byte[] readyReport = ReportToByteArray();
            this.LogEntries.Add($"Reporting OK!");
            #region DEBUG
#if DEBUG
            sw.Stop();
            System.Diagnostics.Debug.WriteLine($"{thisfn}Reporting elapsed time: {sw.ElapsedMilliseconds}ms");
            sw.Start();
#endif
			#endregion DEBUG
			#endregion Reporting

			#region Riport kiírása a fájlba
			this.LogEntries.Add($"TargetFile = '{this.Report.TargetFile}'");
            File.WriteAllBytes(this.Report.TargetFile, readyReport);
            this.LogEntries.Add($"Report to file OK!");
            #region DEBUG
#if DEBUG
            sw.Stop();
            System.Diagnostics.Debug.WriteLine($"{thisfn}Report to file elapsed time: {sw.ElapsedMilliseconds}ms");
#endif
            #endregion DEBUG
            #endregion Riport kiírása a fájlba
        }
        #endregion ToFile public method

        #endregion Public methods

        #region Private methods

        #region GetReportParameters private method    
        /// <summary>
        /// A Report tulajdonságban NameValuCollection-ben található paraméterlista
        /// átrámolása egy ReportParameter típusú listába.
        /// </summary>
        /// <returns></returns>
        private List<ReportParameter> GetReportParameters()
        {
            var result = new List<ReportParameter>();
            try
            {
                foreach (var inputKey in this.Report.Parameters.AllKeys)
                {
                    result.Add(new ReportParameter(inputKey, this.Report.Parameters[inputKey]));
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Error when set report parameters!", ex);
            }
            return result;
        }
		#endregion GetReportParameters private method    

		#region CreateOneDataSource private method
		private ReportDataSource CreateOneDataSource(Dataset dataset,NameValueCollection parameters)
		{
			try
			{
				ReportDataSource newReportDataSource = null;
				switch (dataset.Type)
				{
					case DatasetTypes.SQL:
						newReportDataSource = new ReportDataSource(dataset.Name, LoadDataTable(dataset.Type, dataset.ConnectionString, dataset.Command));
						if (parameters != null && !parameters.AllKeys.Contains("SQLTEXT")) { parameters.Add("SQLTEXT", dataset.Command);}
						break;
					case DatasetTypes.SQLTextPlugin:
						if (dataset.Plugin == null || dataset.GetMethod == null) { throw new Exception((dataset.Plugin == null?"Plugin object is null! ":"") + (dataset.GetMethod == null ? "Method object is null!" : "")); }
						dataset.Command = (string)dataset.GetMethod.Invoke(dataset.Plugin, new object[] { dataset.PluginViewName, dataset.ConnectionString, dataset.PluginAssemblyXml, dataset.DatasetXml, parameters });
						if (parameters == null) { }
						else if (parameters.AllKeys.Contains("SQLTEXT")) { parameters["SQLTEXT"]=dataset.Command; }
						else { parameters.Add("SQLTEXT", dataset.Command); }
						newReportDataSource = new ReportDataSource(dataset.Name, LoadDataTable(dataset.Type, dataset.ConnectionString, dataset.Command));
						break;
					case DatasetTypes.DataPlugin:
						if (dataset.Plugin == null || dataset.GetMethod == null) { throw new Exception((dataset.Plugin == null ? "Plugin object is null! " : "") + (dataset.GetMethod == null ? "Method object is null!" : "")); }
						var returndataList = ((IEnumerable<object>)dataset.GetMethod.Invoke(dataset.Plugin, new object[] { dataset.PluginViewName, dataset.ConnectionString, dataset.PluginAssemblyXml, dataset.DatasetXml, parameters })); //.ToList();
						newReportDataSource = new ReportDataSource(dataset.Name, returndataList);
						break;
					default: break;//ide nem jöhet, mert már korábban kibukna
				}
				return newReportDataSource;
			}
			catch (Exception ex)
			{
				string msg = $"Error to create datasource for report! Type: \"{dataset.Type}\"";
				msg += dataset.Type==DatasetTypes.SQL
					? $"; Command: \"{dataset.Command}\"; ConnectionString=\"{dataset.ConnectionString}\""
					: $"; Plugin: \"{dataset.PluginAssemblyPath}(type:{dataset.PluginTypeFullName},method:{dataset.PluginMethodName},view:{dataset.PluginViewName})\"; ConnectionString=\"{dataset.ConnectionString}\"; Command: \"{dataset.Command}\";";
				throw new Exception(msg, ex);
			}
		}
		#region LoadDataTable private method
		private DataTable LoadDataTable(DatasetTypes dstype, string connectionString, string command)
		{
			var result = new DataTable();
			using (var cnn = new SqlConnection(connectionString))
			{
				cnn.Open();
				using (var cmd = new SqlCommand(command, cnn))
				{
					cmd.CommandTimeout = this.Report.Timeout.SqlQuery;
					var dataReader = cmd.ExecuteReader();
					result.Load(dataReader);
				}
			}
			return result;
		}
		#endregion LoadDataTable private method
		#endregion CreateOneDataSource private method

		#region ExportToFile private method
		private void ExportToFile()
		{
			if (this.Report.Datasets == null || !this.Report.Datasets.Any()) { throw new ApplicationException(string.Format(ERR_EXPORTDATASETMISSING, this.Report.Id)); }
			bool singledataset = this.Report.Datasets.Count() == 1;
			foreach (var dataset in this.Report.Datasets)
			{
				dataset.ExportFile = this.Report.TargetFile.Replace(Constants.DATASETNAMEMASK, singledataset ? "" : $"_{dataset.Name}");
                switch (dataset.Type)
                {
					case DatasetTypes.SQL:
					case DatasetTypes.SQLTextPlugin:
						ReportService.ExportSQLText(dataset.ConnectionString, dataset.Command, dataset.ExportFile, this.Report.Timeout.SqlQuery, allformat: this.Report.Formats.AllFormats);
						break;
					case DatasetTypes.DataPlugin:
						var resultdata = (IEnumerable<object>)dataset.GetMethod.Invoke(dataset.Plugin, new object[] { dataset.PluginViewName, dataset.ConnectionString, dataset.PluginAssemblyXml, dataset.DatasetXml, this.Report.Parameters });
						//TODO: ezt meg kell csinálni!!!!
						ReportService.ExportSQLData(dataset.ConnectionString, resultdata, dataset.ExportFile, this.Report.Timeout.SqlQuery, allformat: this.Report.Formats.AllFormats);
						break;
					default:break;//ide nem jöhet, mert már korábban kibukna
				}
			}
		}
		#endregion ExportToFile private method

		#region ReportToByteArray private method
		/// <summary>
		/// Egy byte tömböt ad vissza, amelyben a legenerált report található.
		/// A formátum a Report.Formats.File tulajdonságban.
		/// </summary>
		/// <returns>Egy byte tömb a kész riporttal.</returns>
		private byte[] ReportToByteArray()
        {
			byte[] rr = null;
			try
			{
				if (this.Report == null) { throw new ApplicationException(ERR_REPORTISNULL); }

				using (var reportViewer = new ReportViewer { ProcessingMode = ProcessingMode.Local })
				{
					reportViewer.LocalReport.ReportPath = PrepareRdl(this.Report, autopurgeparameters: this.Report.AutoPurge);
					SetHttpContextCurrent(1);
					foreach (var dataset in this.Report.Datasets) 
					{
						if (dataset.Type == DatasetTypes.DataPlugin || dataset.Type == DatasetTypes.SQLTextPlugin) SetHttpContextCurrent(2);
						var ds = CreateOneDataSource(dataset, this.Report.Parameters);
						reportViewer.LocalReport.DataSources.Add(ds); 
					}
					reportViewer.LocalReport.SetParameters(GetReportParameters());

					string format;
					switch (this.Report.Formats.AllFormats)
					{
						case AllFormats.DOC: format = "Word"; break;
						case AllFormats.XLS: format = "Excel"; break;
						default: format = "PDF"; break;
					}
					rr = reportViewer.LocalReport.Render(format);
					return rr;
				}
			}
			catch (Exception ex)
			{
				if (rr != null) { return rr; } // ez a fenti reportViewer.Dispose()-ból jövő exception megkerülésére van itt...
				throw new ApplicationException(
                    $"Error when process report to file! Report {(this.Report == null ? "Report is empty!" : "Id=" + this.Report.Id)}",
                    ex
                );
            }
        }

		/// <summary>
		/// Web-es és nem web-es környezetben való futtatás esetére az exception-ök elkerülése érdekében szükséges hívások.
		/// NEM TELJESEN TISZTA A FUNKCIÓJUK!!!!
		/// </summary>
		/// <param name="mode">
		/// mode==1
		///		Ez a rész azért kell ide, mert egyébként nem web-es környezetben (az iSchedulerMonitor-ból való hívás esetén)
		///		itt exception jön a reportViewer.Dispose()-ból annak ellenére, hogy az rr tartalmazza a byte sorozatot?!
		///		A reportViewer HttpContext-et igényel, és ezzel a kódrésszel kell ezt megteremteni.
		/// mode==2
		///		Ez a rész azért kell ide, mert egyébként a Hangfire-ral együttműködve hibára megy a plugin hívás a dbcontext használatakor
		///		"The virtual path '/' maps to another application, which is not allowed."
		///		exception-nal
		/// </param>
		private static void SetHttpContextCurrent(int mode)
		{
			if (mode==1)
			{
				if (System.Web.HttpContext.Current == null)
				{
					System.Web.HttpContext.Current = new System.Web.HttpContext(
						new System.Web.HttpRequest(System.IO.Path.GetRandomFileName(), "https://www.stackoverflow.com", string.Empty),
						new System.Web.HttpResponse(System.IO.TextWriter.Null)
						);
				}
			}
			if (mode == 2)
			{
				string urlroot = null;
				if (System.Web.HttpContext.Current == null) { urlroot = "http://localhost"; }
				else
				{
					var rq = System.Web.HttpContext.Current.Request;
					urlroot = rq.Url.GetLeftPart(UriPartial.Authority) + rq.ApplicationPath;
				}
				System.Web.HttpContext.Current = new System.Web.HttpContext(
					new System.Web.HttpRequest("", urlroot, string.Empty),
					new System.Web.HttpResponse(System.IO.TextWriter.Null)
					);
			}
		}

		#endregion ReportToByteArray private method

		#region PrepareRdl
		/// <summary>
		/// Az rdl file-t preparálja a futtatás előtt (változók cseréje, ...)
		/// </summary>
		/// <param name="reportdef">az rdl file-ra mutató teljes elérési út</param>
		/// <param name="autopurgeparameters">az ennyi másodpercnél régebbi ideiglenes file-okat törölni kell a temp folderből (auto purge funkció)</param>
		/// <returns>a preparált ideiglenes rdl file-ra mutató teljes elérési út</returns>
		private string PrepareRdl(OneReportXmlParser reportdef, AutoPurgeElement autopurgeparameters)
		{
			string rdlpath = reportdef.ReportFile;
			var dir = string.IsNullOrEmpty(autopurgeparameters.TmpFolder) ? System.IO.Path.GetDirectoryName(rdlpath) : autopurgeparameters.TmpFolder;
			if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); }
			var nam = System.IO.Path.GetFileNameWithoutExtension(rdlpath);
			var ext = ".rdltmp"; //  System.IO.Path.GetExtension(rdlpath);
			var temprdlpath = System.IO.Path.Combine(dir, nam + DateTime.Now.ToString("'_TMP_'yyyy'_'MM'_'dd'_'HH'_'mm'_'ss'_'ff") + ext);
			try
			{
				System.IO.File.Delete(temprdlpath);
				// Remove "OLD" temporary files from the temp directory
				string searchPattern = "*_TMP_????_??_??_??_??_??_??.rdltmp";
				string[] files = Directory.GetFiles(dir, searchPattern, SearchOption.TopDirectoryOnly);
				DateTime borderdate;
				foreach (string file in files)
				{
					FileInfo fi = new FileInfo(file);
					//borderdate = DateTime.Now.AddYears(-1);
					//borderdate = DateTime.Now.AddMonths(-3);
					//borderdate = DateTime.Now.AddDays(-3);
					//borderdate = DateTime.Now.AddHours(-3);
					//borderdate = DateTime.Now.AddMinutes(-3);
					borderdate = DateTime.Now.AddSeconds(-1 * System.Math.Abs(autopurgeparameters.Timeout));
					//borderdate = DateTime.Now.AddMilliseconds(-3);
					if (fi.LastAccessTime < borderdate) fi.Delete();
				}
			}
			catch { }



			var rdlXmlString = System.IO.File.ReadAllText(rdlpath);
			System.IO.File.WriteAllText(temprdlpath, reportdef.XmlVars.Substitution(rdlXmlString));
			return temprdlpath;
		}
		#endregion PrepareRdl

		#region ExportSQLText private method
		/// <summary>
		/// SQL típusú datasetek exportjának végrehajtása
		/// </summary>
		/// <param name="datasetCommand">az táblát előállító SQL script (SELECT).</param>
		/// <param name="datasetConnectionString">connections tring a db-re</param>
		/// <param name="datasetExportFile">az export file elérési útja</param>
		/// <param name="sqlquerytimeout">a lekérdezés időigény korlátja másodpercben</param>
		/// <param name="allformat">az export formátuma</param>
		private static void ExportSQLText(string datasetConnectionString, string datasetCommand,  string datasetExportFile, int sqlquerytimeout, AllFormats? allformat=null)
		{
			using (SqlConnection cnn = new SqlConnection(datasetConnectionString))
			{
				cnn.Open();
				using (SqlCommand cmd = new SqlCommand(datasetCommand, cnn))
				{
					cmd.CommandTimeout = sqlquerytimeout;
					if (allformat == AllFormats.XML)
					{
						using (var da = new SqlDataAdapter(cmd))
						{
							DataSet ds = new DataSet();
							da.Fill(ds);
							ds.Tables[0].WriteXml(datasetExportFile);
						}
					}
					else if (allformat == AllFormats.CSV)
					{
						SqlDataReader dr = cmd.ExecuteReader();
						StringBuilder sb = new StringBuilder();
						string sep = ",";   // az elválasztó
						string dq = "\"";   // dupla idézőjel
						var colnames = Enumerable.Range(0, dr.FieldCount).Select(dr.GetName).ToList(); // összes oszlopnév begyűjtése 
						sb.Append(string.Join(sep, colnames)).AppendLine();
						while (dr.Read())
						{
							for (int i = 0; i < dr.FieldCount; i++)
							{
								string fieldval = dr[i].ToString();
								// ha van benne idézőjel, akkor azokat lecseréljük 2 db idézőjelre
								if (fieldval.Contains(sep)) fieldval = fieldval.Replace(dq, string.Concat(dq, dq));
								// ha van benne elválasztó, akkor dupla idéző jelek közé tesszük
								if (fieldval.Contains(sep)) fieldval = string.Concat(dq, fieldval, dq);
								// ha van benne soremelés, akkor azt lecseréjük szóközre
								sb.Append(string.Concat(fieldval.Replace(Environment.NewLine, " "), sep));
							}
							sb.Length--; // Töröljük az utsó separator-t
							sb.AppendLine();
						}
						var datasetExportFiledirectory = Path.GetDirectoryName(datasetExportFile);
						if (!Directory.Exists(datasetExportFiledirectory)) { Directory.CreateDirectory(datasetExportFiledirectory); }
						using (StreamWriter sw = new StreamWriter(datasetExportFile))
						{
							sw.Write(sb.ToString());
							sw.Close();
						}
					}
					else { throw new Exception($"{nameof(ExportSQLText)} hívási hiba!"); }

					cnn.Close();
				}
			}
		}
		#endregion ExportSQLText private method
		#region ExportSQLData private method
		/// <summary>
		/// SQL típusú datasetek exportjának végrehajtása
		/// </summary>
		/// <param name="datasetdata">a táblát előállító IEnumerable struktúra</param>
		/// <param name="datasetConnectionString">connections tring a db-re</param>
		/// <param name="datasetExportFile">az export file elérési útja</param>
		/// <param name="sqlquerytimeout">a lekérdezés időigény korlátja másodpercben</param>
		/// <param name="allformat">az export formátuma</param>
		private static void ExportSQLData(string datasetConnectionString, IEnumerable<object> datasetdata, string datasetExportFile, int sqlquerytimeout, AllFormats? allformat = null)
		{
			if (datasetdata == null || !datasetdata.Any()) return;
			StringBuilder sb = new StringBuilder();
			var columns = datasetdata.First().GetType().GetProperties();
			if (allformat == AllFormats.XML)
			{
				sb.Append(@"<?xml version=""1.0"" standalone=""yes""?>").AppendLine();
				sb.Append($"<NewDataSet>").AppendLine();
				foreach (var row in datasetdata)
				{
					sb.Append($"\t<Table>").AppendLine();
					foreach (var column in columns)
					{
						string fieldval = ConvertFieldValueForXML(column.GetValue(row));
						string fieldxml = string.IsNullOrEmpty(fieldval)
							? $"\t\t<{column.Name} />"
							: $"\t\t<{column.Name}>{fieldval}</{column.Name}>";
						sb.Append(fieldxml).AppendLine();
					}
					sb.Append($"\t</Table>").AppendLine();
				}
				sb.Append($"</NewDataSet>").AppendLine();
			}
			else if (allformat == AllFormats.CSV)
			{
				var colnames = columns.Select(f => f.Name).ToList();
				string sep = ",";   // az elválasztó
				sb.Append(string.Join(sep, colnames)).AppendLine();
				foreach (var row in datasetdata)
				{
					foreach (var column in columns)
                    {
						sb.Append(ConvertFieldValueForCSV(column.GetValue(row), sep) + sep);
					}
					sb.Length--; // Töröljük az utsó separator-t
					sb.AppendLine();
				}
			}
			else { throw new Exception($"{nameof(ExportSQLData)}  hívási hiba!"); }
			var datasetExportFiledirectory = Path.GetDirectoryName(datasetExportFile);
			if (!Directory.Exists(datasetExportFiledirectory)) { Directory.CreateDirectory(datasetExportFiledirectory); }
			using (StreamWriter sw = new StreamWriter(datasetExportFile))
			{
				sw.Write(sb.ToString());
				sw.Close();
			}
		}
		private static string ConvertFieldValueForCSV(object fieldvalobject,string sep) 
		{
			if (fieldvalobject == null) return "";
			string fieldval = fieldvalobject.ToString();
			if (fieldval.Contains("\"")) fieldval = fieldval.Replace("\"", "\"" + "\"");// ha van benne idézőjel, akkor azokat lecseréljük 2 db idézőjelre
			if (fieldval.Contains(sep)) fieldval = "\"" + fieldval+ "\"";// ha van benne elválasztó, akkor dupla idéző jelek közé tesszük
			fieldval = fieldval.Replace(Environment.NewLine, " ");// ha van benne soremelés, akkor azt lecseréjük szóközre
			return fieldval;
		}
		private static string ConvertFieldValueForXML(object fieldvalobject)
		{
			if (fieldvalobject == null) return null;
			string fieldval = fieldvalobject.ToString();
			fieldval = fieldval.Replace("<", "&lt;");
			fieldval = fieldval.Replace(">", "&gt;");
			return fieldval;
		}
		#endregion ExportSQLData private method

		#region ListFormatsGeneric
		private List<SelectListJSON> ListFormatsGeneric<FormatEnum>()
			where FormatEnum : struct
		{
			var result = new List<SelectListJSON>();
			foreach (var format in Enum.GetNames(typeof(FormatEnum)))
			{
				result.Add(new SelectListJSON
				{
					Value = format,
					Text = format,
					Disabled = false,
					Group = null,
					Selected = false
				});
			}

			return result;
		}
		#endregion ListFormatsGeneric

		#endregion Private methods
	}
}
