using Microsoft.Reporting.WinForms;
using NKC_SDK;
using SteamWare;
using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing.Imaging;
using System.Drawing.Printing;
using System.IO;
using System.Text;

namespace AppData
{
    /// <summary>
    /// Classe che si occupa di stampare report da reportViewer via printer remota
    /// </summary>
    public class reportPrinter
    {
        #region Private Fields

        private int m_currentPageIndex;

        /// <summary>
        /// stream del report...
        /// </summary>
        private IList<Stream> m_streams;

        #endregion Private Fields

        #region Protected Fields

        /// <summary>
        /// oggetto static/singleton per fare chiamate sul datalayer
        /// </summary>
        protected DataLayer DLMan = new DataLayer();

        protected bool doPdfCopy = false;
        protected int logLevel = 0;

        #endregion Protected Fields

        #region Public Fields

        /// <summary>
        /// singleton pubblico
        /// </summary>
        public static reportPrinter obj = new reportPrinter();

        #endregion Public Fields

        #region Protected Constructors

        /// <summary>
        /// oggetto protected
        /// </summary>
        /// <param name="args"></param>
        protected reportPrinter()
        {
            logLevel = memLayer.ML.CRI("_logLevel");
            doPdfCopy = memLayer.ML.CRB("doPdfCopy");
        }

        #endregion Protected Constructors

        #region Private Methods

        /// <summary>
        /// carica i dati richiesti dal report dalla StoredProcedure (filtrando quindi...)
        /// </summary>
        /// <param name="tipoReport"></param>
        /// <param name="keyParam">cod UDC</param>
        /// <returns>tabella dati</returns>
        private DataTable caricaDati(reportRichiesto tipoReport, string keyParam)
        {
            int intIdx = 0;
            DataTable tab = new DataTable();
            // FIXME TODO...
            string qrCodeBaseUrl = "";
            string imagePath = "";
            switch (tipoReport)
            {
                case reportRichiesto.binPost:
                    int.TryParse(keyParam, out intIdx);
                    tab = (DataTable)DLMan.taRepBin.GetData(intIdx, true, imagePath, qrCodeBaseUrl);
                    break;

                case reportRichiesto.binPre:
                    int.TryParse(keyParam, out intIdx);
                    tab = (DataTable)DLMan.taRepBin.GetData(intIdx, false, imagePath, qrCodeBaseUrl);
                    break;

                case reportRichiesto.bunkGroup:
                    int.TryParse(keyParam, out intIdx);
                    tab = (DataTable)DLMan.taRepBunkGroup.GetData(intIdx, qrCodeBaseUrl);
                    break;

                case reportRichiesto.bunkList:
                    int.TryParse(keyParam, out intIdx);
                    tab = (DataTable)DLMan.taRepBunkList.GetData(intIdx, qrCodeBaseUrl);
                    break;

                case reportRichiesto.cart:
                    int.TryParse(keyParam, out intIdx);
                    tab = (DataTable)DLMan.taRepCart.GetData(intIdx, qrCodeBaseUrl);
                    break;

                case reportRichiesto.offline:
                    int.TryParse(keyParam, out intIdx);
                    tab = (DataTable)DLMan.taRepPart.GetData(intIdx, qrCodeBaseUrl);
                    break;

                case reportRichiesto.part:
                    int.TryParse(keyParam, out intIdx);
                    tab = (DataTable)DLMan.taRepPart.GetData(intIdx, qrCodeBaseUrl);
                    break;

                case reportRichiesto.cartIRKGroup:
                    int.TryParse(keyParam, out intIdx);
                    tab = (DataTable)DLMan.taRepIRKSum.GetData(intIdx, qrCodeBaseUrl);
                    break;

                case reportRichiesto.cartIRKList:
                    int.TryParse(keyParam, out intIdx);
                    tab = (DataTable)DLMan.taRepIRK.GetData(intIdx, qrCodeBaseUrl);
                    break;

                default:
                    break;
            }
            return tab;
        }

        /// <summary>
        /// ciclo da fornire al renderizzatore dei report, per salvare 1 immagine da ogni pagina del report
        /// </summary>
        /// <param name="name"></param>
        /// <param name="fileNameExtension"></param>
        /// <param name="encoding"></param>
        /// <param name="mimeType"></param>
        /// <param name="willSeek"></param>
        /// <returns></returns>
        private Stream CreateStream(string name, string fileNameExtension, Encoding encoding, string mimeType, bool willSeek)
        {
            // creo files con nomi univoci...
            string filePathName = string.Format(@"~\temp\{0}_{1:HHmmss}_{1:ffff}.{2}", name, DateTime.Now, fileNameExtension);
            Stream stream = new FileStream(SteamWare.fileMover.getFilePath(filePathName), FileMode.Create);
            //Stream stream = new FileStream(SteamWare.SteamwareStrings.getFilePath(@"~\temp\" + name + "." + fileNameExtension), FileMode.Create);
            m_streams.Add(stream);
            return stream;
        }

        /// <summary>
        /// ciclo da fornire al renderizzatore dei report, per salvare 1 pdf da ogni pagina del report
        /// </summary>
        /// <param name="name"></param>
        /// <param name="fileNameExtension"></param>
        /// <param name="encoding"></param>
        /// <param name="mimeType"></param>
        /// <param name="willSeek"></param>
        /// <returns></returns>
        private Stream CreateStreamPdf(string name, string fileNameExtension, Encoding encoding, string mimeType, bool willSeek)
        {
            // creo Directory se non c'è
            SteamWare.fileMover fm = new fileMover(string.Format(@"{0}\{1:yyyy}\{1:MM}\{1:dd}\", memLayer.ML.confReadString("PdfFolder"), DateTime.Now), "");
            fm.checkDir();
            string pdfPathName = string.Format(@"{0}\{1:yyyy}\{1:MM}\{1:dd}\{2}_{1:HHmmss}_{1:ffff}.{3}", memLayer.ML.confReadString("PdfFolder"), DateTime.Now, name, fileNameExtension);
            //Stream stream = new FileStream(SteamWare.SteamwareStrings.getFilePath(@"~\temp\" + name + "." + fileNameExtension), FileMode.Create);
            Stream stream = new FileStream(SteamWare.fileMover.getFilePath(pdfPathName), FileMode.Create);
            m_streams.Add(stream);
            return stream;
        }

        /// <summary>
        /// esegue print vero e proprio
        /// </summary>
        /// <param name="printerName"></param>
        /// <param name="report"></param>
        /// <param name="deviceInfo"></param>
        private void doEmfPrint(string printerName, LocalReport report, string deviceInfo)
        {
            // export in EMF
            Export(report, deviceInfo);
            m_currentPageIndex = 0;
            // stampo
            Print(printerName);
            // do dispose?
            Dispose();
        }

        /// <summary>
        /// effettua stampa in PDF dei vari report in una cartella Anno/Mese/Giorno
        /// </summary>
        /// <param name="report"></param>
        /// <param name="deviceInfo"></param>
        private void doLocalPdfPrint(LocalReport report, string deviceInfo)
        {
            // export in PDF
            ExportPDF(report, deviceInfo);
            m_currentPageIndex = 0;
            // do dispose?
            Dispose();
        }

        /// <summary>
        /// Export del report come EMF (Enhanced Metafile) file.
        /// </summary>
        /// <param name="report"></param>
        private void Export(LocalReport report, string deviceInfo)
        {
            Warning[] warnings;
            m_streams = new List<Stream>();
            report.Render("Image", deviceInfo, CreateStream, out warnings);
            foreach (Stream stream in m_streams) stream.Position = 0;
        }

        /// <summary>
        /// Export del report come PDF file.
        /// </summary>
        /// <param name="report"></param>
        private void ExportPDF(LocalReport report, string deviceInfo)
        {
            Warning[] warnings;
            m_streams = new List<Stream>();
            report.Render("PDF", deviceInfo, CreateStreamPdf, out warnings);
            foreach (Stream stream in m_streams) stream.Position = 0;
        }

        /// <summary>
        /// funzione di stampa...
        /// </summary>
        private void Print(string printerName)
        {
            //const string printerName = "Microsoft Office Document Image Writer";
            //const string printerName = "Brother HL-2170W series";
            if (m_streams == null || m_streams.Count == 0)
                return;
            PrintDocument printDoc = new PrintDocument();
            printDoc.PrinterSettings.PrinterName = printerName;
            if (!printDoc.PrinterSettings.IsValid)
            {
                logger.lg.scriviLog(String.Format("Impostazioni non valide per la stampante \"{0}\".", printerName), tipoLog.ERROR);
                return;
            }
            printDoc.PrintPage += new PrintPageEventHandler(PrintPage);
            printDoc.Print();
        }

        /// <summary>
        /// effettua la vera e propria fase di stampa
        /// </summary>
        /// <param name="keyParam"></param>
        /// <param name="printerName"></param>
        /// <param name="answ"></param>
        /// <param name="report"></param>
        /// <param name="deviceInfo"></param>
        /// <returns></returns>
        private bool printAndLog(string keyParam, string printerName, bool answ, reportRichiesto report, devInfoParam deviceInfo)
        {
            try
            {
                if (logLevel > 5) logger.lg.scriviLog(String.Format("Sto per inviare un report alla stampante {0}", printerName), tipoLog.INFO);
                reportPrinter.obj.printReport(report, printerName, keyParam, deviceInfo);
                if (logLevel > 5) logger.lg.scriviLog(String.Format("inviato comando print alla stampante {0}", printerName), tipoLog.INFO);
                answ = true;
            }
            catch (Exception e)
            {
                logger.lg.scriviLog(String.Format("Errore in fase di creazione e stampa report: stampante {0}, errore riscontrato {1}", printerName, e), tipoLog.EXCEPTION);
            }
            return answ;
        }

        /// <summary>
        /// Handler per PrintPageEvents
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="ev"></param>
        private void PrintPage(object sender, PrintPageEventArgs ev)
        {
            Metafile pageImage = new Metafile(m_streams[m_currentPageIndex]);
            ev.Graphics.DrawImage(pageImage, ev.PageBounds);
            m_currentPageIndex++;
            ev.HasMorePages = (m_currentPageIndex < m_streams.Count);
        }

        #endregion Private Methods

        #region Public Methods

        /// <summary>
        /// dispone l'applicazione e rilascia le risorse
        /// </summary>
        public void Dispose()
        {
            if (m_streams != null)
            {
                foreach (Stream stream in m_streams)
                    stream.Close();
                m_streams = null;
            }
        }

        /// <summary>
        /// Crea un report locale da file rdlc, carica i dati, esporta report come EMF file e quindi lo invia alla stampante
        /// </summary>
        /// <param name="tipoReport">report ammessi: ElencoMacchine / RichiestaIntervento</param>
        /// <param name="printerName">nome completo stampante (rispetto al server)</param>
        /// <param name="keyParam">cod UDC</param>
        /// <param name="deviceInfoParam">parametri "device input"</param>
        public void printReport(reportRichiesto tipoReport, string printerName, string keyParam, devInfoParam deviceInfoParam)
        {
            LocalReport report = new LocalReport();
            report.EnableExternalImages = true;
            string deviceInfo = "";
            string repoBasePath = utils.getPath(memLayer.ML.cdv("ReportBasePath"));
            string imagePath = memLayer.ML.cdv("ReportImgBasePath"); //"http://seriate.steamware.net:8083/NKC/PartsImg/";
            repoBasePath = repoBasePath.Replace("\\site", "");
            switch (tipoReport)
            {
                case reportRichiesto.binPost:
                    report.ReportPath = string.Format(@"{0}\Bin.rdlc", repoBasePath);
                    report.DataSources.Add(new ReportDataSource(memLayer.ML.cdv("ReportDS_DocBin"), caricaDati(tipoReport, keyParam)));
                    break;

                case reportRichiesto.binPre:
                    report.ReportPath = string.Format(@"{0}\Bin.rdlc", repoBasePath);
                    report.DataSources.Add(new ReportDataSource(memLayer.ML.cdv("ReportDS_DocBin"), caricaDati(tipoReport, keyParam)));
                    break;

                case reportRichiesto.bunkGroup:
                    report.ReportPath = string.Format(@"{0}\BunkGroup.rdlc", repoBasePath);
                    report.DataSources.Add(new ReportDataSource(memLayer.ML.cdv("ReportDS_DocBunkGroup"), caricaDati(tipoReport, keyParam)));
                    break;

                case reportRichiesto.bunkList:
                    report.ReportPath = string.Format(@"{0}\BunkList.rdlc", repoBasePath);
                    report.DataSources.Add(new ReportDataSource(memLayer.ML.cdv("ReportDS_DocBunkList"), caricaDati(tipoReport, keyParam)));
                    break;

                case reportRichiesto.cart:
                    report.ReportPath = string.Format(@"{0}\Cart.rdlc", repoBasePath);
                    report.DataSources.Add(new ReportDataSource(memLayer.ML.cdv("ReportDS_DocCart"), caricaDati(tipoReport, keyParam)));
                    break;

                case reportRichiesto.cartIRKGroup:
                    report.ReportPath = string.Format(@"{0}\IRK_Kitting_Group.rdlc", repoBasePath);
                    report.DataSources.Add(new ReportDataSource(memLayer.ML.cdv("ReportDS_DocIRKGroup"), caricaDati(tipoReport, keyParam)));
                    break;

                case reportRichiesto.cartIRKList:
                    report.ReportPath = string.Format(@"{0}\IRK_Kitting_List.rdlc", repoBasePath);
                    report.DataSources.Add(new ReportDataSource(memLayer.ML.cdv("ReportDS_DocIRKList"), caricaDati(tipoReport, keyParam)));
                    break;

                case reportRichiesto.offline:
                case reportRichiesto.part:
                    report.ReportPath = string.Format(@"{0}\Part.rdlc", repoBasePath);
                    report.DataSources.Add(new ReportDataSource(memLayer.ML.cdv("ReportDS_DocPart"), caricaDati(tipoReport, keyParam)));
                    break;

                case reportRichiesto.otherPart:
                    report.ReportPath = string.Format(@"{0}\OtherPart.rdlc", repoBasePath);
                    report.DataSources.Add(new ReportDataSource(memLayer.ML.cdv("ReportDS_DocOtherPart"), caricaDati(tipoReport, keyParam)));
                    break;
            }
            deviceInfo = deviceInfoParam.xmlParam;
            doEmfPrint(printerName, report, deviceInfo);
            // controllo se devo fare copia PDF... stampiamo ANCHE su pdf su una folder locale
            if (doPdfCopy)
            {
                deviceInfoParam.OutputFormat = "PDF";
                deviceInfo = deviceInfoParam.xmlParam;
                doLocalPdfPrint(report, deviceInfo);
            }
        }

        /// <summary>
        /// effettua pulizia della cartella temp x i files più vecchi di X ore (web.config)
        /// </summary>
        public void pulisciDir()
        {
            // num max ore di età x files "vecchi" da tenere in temp area...
            int maxOre = memLayer.ML.CRI("maxAgeTempAreaHours");
            int eliminati = 0;
            // ottengo elenco files *.emf
            fileMover.obj.setDirectory(@"~\temp\");
            FileInfo[] _fis = fileMover.obj.elencoFiles_FI("*.emf");
            bool fatto = false;
            foreach (FileInfo _file in _fis)
            {
                if (_file.CreationTime < DateTime.Now.AddHours(-maxOre)) // elimino files vecchi...
                {
                    fatto = fileMover.obj.eliminaFile(_file);
                    if (fatto)
                    {
                        logger.lg.scriviLog(String.Format("Eliminato file {0}", _file.Name), tipoLog.INFO);
                        eliminati++;
                    }
                }
            }
            // salvo il log degli update
            if (eliminati > 0)
            {
                logger.lg.scriviLog(String.Format("Eliminati {0} files temporanei da area temp", eliminati), tipoLog.INFO);
            }
        }

        /// <summary>
        /// effettua la stampa del cartellino indicato
        /// </summary>
        /// <param name="documento"></param>
        /// <param name="keyParam"></param>
        /// <param name="printerName">indirizzo di rete completo stampante del tipo \\nomePc\nomeStampante, oppure "" x default</param>
        /// <param name="queueOnly">true = si limita ad accodare SENZA stampare</param>
        /// <returns></returns>
        public bool stampaCartellino(tipoDocumento documento, string keyParam, string printerName, bool queueOnly)
        {
            bool answ = false;
            int idxPrintJob = 0;
            DataLayer dtProx = new DataLayer();
            // gestione coda stampa...

            // incomincio con gestione della coda di stampa... inserisco in tab la richiesta di stampa...
            if (memLayer.ML.CRB("disable_singleton"))
            {
                dtProx.taPJQ.insertQuery(documento.ToString(), keyParam, printerName);
            }
            else
            {
                DLMan.taPJQ.insertQuery(documento.ToString(), keyParam, printerName);
            }
            if (queueOnly)
            {
                answ = true;
            }
            else
            {
                // faccio un ciclo while... finchè non sono "in testa" alla coda delle esecuzioni aspetto...
                string nextJob = "ND";
                try
                {
                    if (memLayer.ML.CRB("disable_singleton"))
                    {
                        nextJob = dtProx.taPJQ.getNext()[0].KeyParam;
                    }
                    else
                    {
                        nextJob = DLMan.taPJQ.getNext()[0].KeyParam;
                    }
                }
                catch
                {
                    nextJob = "ND";
                }
                // verifico eventuali processi zombie: quelli in stato 0 con LastTry superiore a data attuale meno zombieMsTime
                if (memLayer.ML.CRB("disable_singleton"))
                {
                    dtProx.taPJQ.chiudiZoombie(DateTime.Now.AddMilliseconds(-memLayer.ML.CRI("zombieMsTime")));
                }
                else
                {
                    DLMan.taPJQ.chiudiZoombie(DateTime.Now.AddMilliseconds(-memLayer.ML.CRI("zombieMsTime")));
                }
                // ora inizio a fare le mie stampe...
                Random rand = new Random();
                int msWait = 200;
                while (nextJob != keyParam)
                {
                    // indico che ho fatto un tentativo di stampa aggiornando dtLastTry
                    if (memLayer.ML.CRB("disable_singleton"))
                    {
                        dtProx.taPJQ.updateLastTry(keyParam);
                    }
                    else
                    {
                        DLMan.taPJQ.updateLastTry(keyParam);
                    }
                    // aspetto...
                    msWait = rand.Next(memLayer.ML.confReadInt("minWait"), memLayer.ML.confReadInt("maxWait"));
                    System.Threading.Thread.Sleep(msWait);
                    // leggo prox record da coda
                    if (memLayer.ML.CRB("disable_singleton"))
                    {
                        nextJob = dtProx.taPJQ.getNext()[0].KeyParam;
                    }
                    else
                    {
                        nextJob = DLMan.taPJQ.getNext()[0].KeyParam;
                    }
                }
                // salvo idx job...
                try
                {
                    if (memLayer.ML.CRB("disable_singleton"))
                    {
                        idxPrintJob = dtProx.taPJQ.getNext()[0].IdxPrintJob;
                    }
                    else
                    {
                        idxPrintJob = DLMan.taPJQ.getNext()[0].IdxPrintJob;
                    }
                }
                catch (Exception exc)
                {
                    logger.lg.scriviLog($"Errore in recupero IdxPrint Job:{exc}", tipoLog.EXCEPTION);
                }

                // dimensioni pagina
                string outForm = "";
                string pagWidth = "";
                string pagHeigth = "";
                string margin = "";
                reportRichiesto report = reportRichiesto.bunkGroup;
                string tipo = "";

                switch (documento)
                {
                    case tipoDocumento.docBinPre:
                        tipo = "docPaint";
                        report = reportRichiesto.binPre;
                        break;

                    case tipoDocumento.docBinPost:
                        tipo = "docPaintPost";
                        report = reportRichiesto.binPost;
                        break;

                    case tipoDocumento.docCart:
                        tipo = "docCart";
                        report = reportRichiesto.cart;
                        break;

                    case tipoDocumento.docCartIRK:
                        tipo = "docCartIRK";
                        report = reportRichiesto.cartIRKList;
                        break;

                    case tipoDocumento.docCartIRKSum:
                        tipo = "docCartIRKSum";
                        report = reportRichiesto.cartIRKGroup;
                        break;

                    case tipoDocumento.docPart:
                        tipo = "docPart";
                        report = reportRichiesto.part;
                        break;

                    case tipoDocumento.docOtherPart:
                        tipo = "docOtherPart";
                        report = reportRichiesto.otherPart;
                        break;

                    case tipoDocumento.docStack:
                        tipo = "docStack";
                        report = reportRichiesto.bunkGroup;
                        break;

                    default:
                        break;
                }
                // carico di dati con lettura da web.config parametrica
                if (string.IsNullOrEmpty(printerName))
                {
                    printerName = memLayer.ML.cdv(string.Format("printer{0}", tipo));
                }
                outForm = "EMF";
                pagWidth = memLayer.ML.cdv(string.Format("PageWidth{0}", tipo));
                pagHeigth = memLayer.ML.cdv(string.Format("PageHeight{0}", tipo));
                margin = memLayer.ML.cdv(string.Format("Margin{0}", tipo));
                // compongo parametri stampa
                devInfoParam deviceInfo = new devInfoParam(outForm, pagHeigth, pagWidth, margin, margin, margin, margin);
                answ = printAndLog(keyParam, printerName, answ, report, deviceInfo);
                if (answ)
                {
                    // registro stampato in tabella queue...
                    if (memLayer.ML.CRB("disable_singleton"))
                    {
                        dtProx.taPJQ.updateStato(idxPrintJob, 1);
                    }
                    else
                    {
                        DLMan.taPJQ.updateStato(idxPrintJob, 1);
                    }
                }
                else
                {
                    // registro annullato in tab queue...
                    if (memLayer.ML.CRB("disable_singleton"))
                    {
                        dtProx.taPJQ.updateStato(idxPrintJob, -1);
                    }
                    else
                    {
                        DLMan.taPJQ.updateStato(idxPrintJob, -1);
                    }
                }
            }
            return answ;
        }

        #endregion Public Methods
    }
}