using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Web.Hosting;
using System.Text;
using System.IO;
using System.Configuration;
using System.Collections;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using FastReport;
using FastReport.Utils;
using FastReport.Data;
using FastReport.Export.Pdf;
using FastReport.Export.OoXML;
using FastReport.Export.RichText;
using FastReport.Export.Odf;
using FastReport.Export.Mht;
using FastReport.Export.Csv;
using FastReport.Export.Dbf;
using FastReport.Export.Xml;
using FastReport.Export.Text;
using FastReport.Export.Html;
using FastReport.Export.XAML;
using FastReport.Export.Image;
using FastReport.Export;
using System.Net;
using System.ServiceModel.Web;

namespace FastReport.Service
{
    public partial class ReportService : IFastReportService
    {
        public string About()
        {
            string s = "FastReport.NET Service " + Config.Version;
            return s;
        }

        public bool CheckPreparedReport(string uuid)
        {
            if (!PreparedPathExists())
                throw new WebFaultException<ErrorHandler>(new ErrorHandler { Cause = "Upload disabled", ErrorCode = 102 },
                    System.Net.HttpStatusCode.InternalServerError);

            string preparedPath = GetPreparedPath();
            string preparedFile = GetPreparedFileName(uuid);
            return File.Exists(Path.Combine(preparedPath, preparedFile));
        }

        public string PutPreparedReport(Stream preparedReport)
        {
            if (!PreparedPathExists())
                throw new WebFaultException<ErrorHandler>(new ErrorHandler { Cause = "Upload disabled", ErrorCode = 102 },
                    System.Net.HttpStatusCode.InternalServerError);

            string uuid = "";
            string preparedPath = GetPreparedPath();
            if (Directory.Exists(preparedPath))
            {
                MemoryStream preparedStream = PreparePostStream(preparedReport);
                if (preparedReport != null && preparedStream.Length > 0)
                {
                    using (Report r = new Report())
                    {
                        Config.WebMode = true;
                        try
                        {
                            MemoryStream checkPrepared = new MemoryStream();
                            preparedStream.WriteTo(checkPrepared);
                            checkPrepared.Position = 0;
                            r.LoadPrepared(checkPrepared);
                            preparedStream.Position = 0;
                            string tempUuid = Guid.NewGuid().ToString();
                            StorePrepared(tempUuid, preparedPath, preparedStream);
                            uuid = tempUuid;
                        }
                        catch (Exception e)
                        {                            
                            throw new WebFaultException<ErrorHandler>(new ErrorHandler { Cause = e.Message, ErrorCode = 100 }, 
                                System.Net.HttpStatusCode.InternalServerError);
                        }
                    }
                }
                else
                {
                    throw new WebFaultException<ErrorHandler>(new ErrorHandler { Cause = "Empty input data", ErrorCode = 101 }, 
                        System.Net.HttpStatusCode.BadRequest);
                }
            }
            else
                throw new WebFaultException<ErrorHandler>(new ErrorHandler { Cause = "Upload directory not found", ErrorCode = 107 },
                    System.Net.HttpStatusCode.BadRequest);
            return uuid;
        }

        public string PutReport(Stream report)
        {
            if (!PreparedPathExists())
                throw new WebFaultException<ErrorHandler>(new ErrorHandler { Cause = "Upload disabled", ErrorCode = 102 },
                    System.Net.HttpStatusCode.InternalServerError);

            string uuid = "";
            string preparedPath = GetPreparedPath();

            if (Directory.Exists(preparedPath))
            {
                MemoryStream reportStream = PreparePostStream(report);
                if (report != null && reportStream.Length > 0)
                {
                    using (Report r = new Report())
                    {
                        Config.WebMode = true;
                        try
                        {
                            reportStream.Position = 0;
                            r.Load(reportStream);
                            r.PreparePhase1();
                            r.PreparePhase2();
                            MemoryStream reportPrepared = new MemoryStream();
                            r.SavePrepared(reportPrepared);
                            string tempUuid = Guid.NewGuid().ToString();
                            StorePrepared(tempUuid, preparedPath, reportPrepared);
                            uuid = tempUuid;
                        }
                        catch (Exception e)
                        {
                            throw new WebFaultException<ErrorHandler>(new ErrorHandler { Cause = e.Message, ErrorCode = 100 },
                                System.Net.HttpStatusCode.InternalServerError);
                        }
                    }
                }
                else
                {
                    throw new WebFaultException<ErrorHandler>(new ErrorHandler { Cause = "Empty input data", ErrorCode = 101 },
                        System.Net.HttpStatusCode.BadRequest);
                }
            }
            else
                throw new WebFaultException<ErrorHandler>(new ErrorHandler { Cause = "Upload directory not found", ErrorCode = 107 },
                    System.Net.HttpStatusCode.BadRequest);
            return uuid;
        }


        private void StorePrepared(string uuid, string preparedPath, MemoryStream preparedStream)
        {
            if (Directory.Exists(preparedPath))
            {
                int storageTimeoutInMinutes = 0;
                string s = ConfigurationManager.AppSettings["FastReport.PreparedCleanupDelay"];

                if (Int32.TryParse(s, out storageTimeoutInMinutes))                
                    CleanStorage(preparedPath, "*.fpx", storageTimeoutInMinutes, 1);

                string fileName = GetPreparedFileName(uuid);
                string fullPath = Path.Combine(preparedPath, fileName);
                using (FileStream fileStream = new FileStream(fullPath, FileMode.Create))
                    preparedStream.WriteTo(fileStream);
            }
        }

        private int CleanStorage(string FileStoragePath, string maskStorage, int StorageTimeoutInMinutes, int CleanupInMinutes)
        {
            string touch = Path.Combine(FileStoragePath, "FRS-touch");
            int filesCount = 0;
            if (StorageTimeoutInMinutes > 0 && 
                CleanupInMinutes > 0 && 
                !String.IsNullOrEmpty(FileStoragePath) && 
                Directory.Exists(FileStoragePath))
            {
                if (File.Exists(touch))
                {
                    DateTime created = File.GetLastWriteTime(touch);
                    if (DateTime.Now > created.AddMinutes(CleanupInMinutes))
                    {
                        File.SetLastWriteTime(touch, DateTime.Now);
                        string[] dir = Directory.GetFiles(FileStoragePath, maskStorage);
                        foreach (string file in dir)
                        {
                            try
                            {
                                if (DateTime.Now > File.GetLastWriteTime(file).AddMinutes(StorageTimeoutInMinutes))
                                    File.Delete(file);
                            }
                            catch
                            {
                                //nothing
                            }
                        }
                    }
                }
                else
                    using (FileStream file = File.Create(touch)) { };
                filesCount = Directory.GetFiles(FileStoragePath, maskStorage).Length;
            }
            return filesCount;
        }

        private string GetPreparedFileName(string uuid)
        {
            if (!String.IsNullOrEmpty(uuid))
                return String.Concat(uuid.Replace("-", ""), ".fpx");
            else
                return String.Empty;
        }

        private bool PreparedPathExists()
        {
            string s = ConfigurationManager.AppSettings["FastReport.PreparedPath"];
            return !string.IsNullOrEmpty(s);
        }

        private bool ReportsPathExists()
        {
            string s = ConfigurationManager.AppSettings["FastReport.ReportsPath"];
            return !string.IsNullOrEmpty(s);
        }

        private string GetPreparedPath()
        {
            string s = ConfigurationManager.AppSettings["FastReport.PreparedPath"]; 
            string result = HostingEnvironment.MapPath(s);
            if (String.IsNullOrEmpty(result))
                result = s;
            return result;
        }

        private string GetReportsPath()
        {
            string s = ConfigurationManager.AppSettings["FastReport.ReportsPath"];            
            string result = HostingEnvironment.MapPath(s);
            if (String.IsNullOrEmpty(result))
                result = s;
            return result;
        }

        public Stream GetPreparedByUUID(string uuid)
        {
            if (!PreparedPathExists())
                throw new WebFaultException<ErrorHandler>(new ErrorHandler { Cause = "Upload disabled", ErrorCode = 102 },
                    System.Net.HttpStatusCode.InternalServerError);
            string preparedPath = GetPreparedPath();
            string preparedFile = GetPreparedFileName(uuid);
            string fullPath = Path.Combine(preparedPath, preparedFile);
            MemoryStream resultFile = new MemoryStream();
            if (File.Exists(fullPath))
            {
                File.SetLastWriteTime(fullPath, DateTime.Now);
                using (FileStream file = new FileStream(fullPath, FileMode.Open))
                {
                    file.CopyTo(resultFile);
                    resultFile.Position = 0;
                }
            }
            else
                throw new WebFaultException<ErrorHandler>(new ErrorHandler { Cause = "File not found", ErrorCode = 404 },
                    System.Net.HttpStatusCode.NotFound);

            return resultFile;
        }

        public Stream GetLogoByUUIDFixed(string uuid)
        {
            return GetLogoByUUID(uuid, "200", "200");
        }

        public Stream GetLogoByUUID(string uuid, string width, string height)
        {            
            int dx, dy;
            MemoryStream stream = new MemoryStream();
            if (Int32.TryParse(width, out dx) && Int32.TryParse(height, out dy))
            {
                Stream preparedStream = GetPreparedByUUID(uuid);
                using (Report r = new Report())
                {
                    Config.WebMode = true;
                    try
                    {
                        r.LoadPrepared(preparedStream);
                        MemoryStream pngStream = new MemoryStream();
                        ImageExport pngExport = new ImageExport();
                        pngExport.ImageFormat = ImageExportFormat.Png;
                        pngExport.Resolution = 96;
                        pngExport.PageRange = PageRange.PageNumbers;
                        pngExport.PageNumbers = "1";
                        r.Export(pngExport, pngStream);
                        pngStream.Position = 0;
                        ResizePNG(pngStream, stream, dx, dy);
                    }
                    catch (Exception e)
                    {
                        throw new WebFaultException<ErrorHandler>(new ErrorHandler { Cause = e.Message, ErrorCode = 103 }, 
                            System.Net.HttpStatusCode.InternalServerError);
                    }
                    stream.Position = 0;
                }
            }
            else
                throw new WebFaultException<ErrorHandler>(new ErrorHandler { Cause = "Wrong width or height", ErrorCode = 106 },
                    System.Net.HttpStatusCode.BadRequest);

            return stream;
        }

        private void ResizePNG(Stream sourceStream, Stream targetStream, int maxWidth, int maxHeight)
        {
            Bitmap image = new Bitmap(sourceStream);
            float scaleX = (float)maxWidth / (float)image.Width;
            float scaleY = (float)maxHeight / (float)image.Height;
            float ratio = Math.Min(scaleX, scaleY);
            int newWidth = (int)Math.Round(image.Width * ratio);
            int newHeight = (int)Math.Round(image.Height * ratio);
            Bitmap newImage = new Bitmap(newWidth, newHeight, PixelFormat.Format24bppRgb);
            using (Graphics graphics = Graphics.FromImage(newImage))
            {
                graphics.CompositingQuality = CompositingQuality.HighQuality;
                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                graphics.SmoothingMode = SmoothingMode.HighQuality;
                graphics.DrawImage(image, 0, 0, newWidth, newHeight);
            }
            newImage.Save(targetStream, System.Drawing.Imaging.ImageFormat.Png);
        }


        private void Write(Stream stream, string line)
        {
            byte[] b = Encoding.UTF8.GetBytes(line);
            stream.Write(b, 0, b.Length);
        }

        public Stream GetTest(Stream preparedReport)
        {
            return PreparePostStream(preparedReport);
        }

        private MemoryStream PreparePostStream(Stream preparedReport)
        {
            MemoryStream stream = new MemoryStream();
            if (preparedReport != null)
            {
                preparedReport.CopyTo(stream);
                stream.Position = 0;
                MultipartParser parser = new MultipartParser(stream);
                if (parser.Success)
                    stream = new MemoryStream(parser.FileContents);
                else
                    stream.Position = 0;
            }
            return stream;
        }

        public Stream GetReportXml(ReportRequest request)
        {
            return GetReport(request);
        }

        public Stream GetReport(ReportRequest request)
        {
            ReportItem report = request.Report;
            GearItem gear = request.Gear;
            MemoryStream stream = new MemoryStream();
            try
            {
                string reportsRoot = GetReportsPath();
                string reportPath = Path.GetFullPath(Path.Combine(reportsRoot, report.Path));
                if (report != null && gear != null && CheckReportAccess(reportPath, reportsRoot))
                {
                    using (Report r = new Report())
                    {
                        Config.WebMode = true;

                        r.Load(reportPath);

                        if (report.Parameters != null)
                            foreach (KeyValuePair<string, string> param in report.Parameters)
                            {
                                r.SetParameterValue(param.Key, param.Value);
                            }

                        string connectionStringName = ConfigurationManager.AppSettings["FastReport.ConnectionStringName"];
                        string connectionString = null;
                        if (!string.IsNullOrEmpty(connectionStringName))
                            connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;

                        if (!string.IsNullOrEmpty(connectionString))
                        {
                            // change first connection on default
                            foreach (DataConnectionBase connection in r.Dictionary.Connections)
                            {
                                connection.ConnectionString = connectionString;
                                break;
                            }
                        }

                        // prepare without dialogs
                        r.PreparePhase1();
                        r.PreparePhase2();

                        // export
                        PrepareOutput(gear, r, stream);
                    }
                }
                else
                    throw new WebFaultException<ErrorHandler>(new ErrorHandler { Cause = "Wrong report or gear", ErrorCode = 130 },
                        System.Net.HttpStatusCode.InternalServerError);
            }
            catch (Exception e)
            {
                throw new WebFaultException<ErrorHandler>(new ErrorHandler { Cause = e.Message, ErrorCode = 131 },
                    System.Net.HttpStatusCode.InternalServerError);
            }
            stream.Position = 0;
            return stream;
        }

        public Stream GetPreparedReportByID(string id)
        {
            List<ReportItem> list = GetReportsList();
            ReportItem report = null;
            GearItem gear = new GearItem();
            gear.Name = "FPX";
            foreach (ReportItem item in list)
                if (item.ID == id)
                {
                    report = item;
                    break;
                }
            if (report != null)
            {
                ReportRequest request = new ReportRequest();
                request.Gear = gear;
                request.Report = report;
                return GetReport(request);
            }
            else
                throw new WebFaultException<ErrorHandler>(new ErrorHandler { Cause = "File not found", ErrorCode = 404 },
                    System.Net.HttpStatusCode.NotFound);
        }

        private void PrepareOutput(GearItem gear, Report r, MemoryStream stream)
        {
            switch (gear.Name)
            {
                case "FPX":
                    {
                        r.SavePrepared(stream);
                        break;
                    }
                case "PDF":
                    {
                        ExportPDF(r, stream, gear.Properties);
                        break;
                    }
                case "XLSX":
                    {
                        ExportXLSX(r, stream, gear.Properties);
                        break;
                    }
                case "DOCX":
                    {
                        ExportDOCX(r, stream, gear.Properties);
                        break;
                    }
                case "PPTX":
                    {
                        ExportPPTX(r, stream, gear.Properties);
                        break;
                    }
                case "ODS":
                    {
                        ExportODS(r, stream, gear.Properties);
                        break;
                    }
                case "ODT":
                    {
                        ExportODT(r, stream, gear.Properties);
                        break;
                    }
                case "MHT":
                    {
                        ExportMHT(r, stream, gear.Properties);
                        break;
                    }
                case "CSV":
                    {
                        ExportCSV(r, stream, gear.Properties);
                        break;
                    }
                case "DBF":
                    {
                        ExportDBF(r, stream, gear.Properties);
                        break;
                    }
                case "XML":
                    {
                        ExportXML(r, stream, gear.Properties);
                        break;
                    }
                case "TXT":
                    {
                        ExportTXT(r, stream, gear.Properties);
                        break;
                    }
                case "RTF":
                    {
                        ExportRTF(r, stream, gear.Properties);
                        break;
                    }
                case "HTML":
                    {
                        ExportHTML(r, stream, gear.Properties);
                        break;
                    }
            }
        }

        private void ExportRTF(Report r, MemoryStream stream, Dictionary<string, string> parameters)
        {
            RTFExport rtf = new RTFExport();
            if (parameters != null)
            {
                FillGeneralProperties(rtf, parameters);
                // add parameters
            }
            r.Export(rtf, stream);
        }

        private void ExportTXT(Report r, MemoryStream stream, Dictionary<string, string> parameters)
        {
            TextExport txt = new TextExport();
            if (parameters != null)
            {
                FillGeneralProperties(txt, parameters);
                // add parameters
            }
            r.Export(txt, stream);
        }

        private void ExportXML(Report r, MemoryStream stream, Dictionary<string, string> parameters)
        {
            XMLExport xml = new XMLExport();
            if (parameters != null)
            {
                FillGeneralProperties(xml, parameters);
                // add parameters
            }
            r.Export(xml, stream);
        }

        private void ExportDBF(Report r, MemoryStream stream, Dictionary<string, string> parameters)
        {
            DBFExport dbf = new DBFExport();
            if (parameters != null)
            {
                FillGeneralProperties(dbf, parameters);
                // add parameters
            }
            r.Export(dbf, stream);
        }

        private void ExportCSV(Report r, MemoryStream stream, Dictionary<string, string> parameters)
        {
            CSVExport csv = new CSVExport();
            if (parameters != null)
            {
                FillGeneralProperties(csv, parameters);
                // add parameters
            }
            r.Export(csv, stream);
        }

        private void ExportMHT(Report r, MemoryStream stream, Dictionary<string, string> parameters)
        {
            MHTExport mht = new MHTExport();
            if (parameters != null)
            {
                FillGeneralProperties(mht, parameters);
                // add parameters
            }
            r.Export(mht, stream);
        }

        private void ExportODT(Report r, MemoryStream stream, Dictionary<string, string> parameters)
        {
            ODTExport odt = new ODTExport();
            if (parameters != null)
            {
                FillGeneralProperties(odt, parameters);
                // add parameters
            }
            r.Export(odt, stream);
        }

        private void ExportODS(Report r, MemoryStream stream, Dictionary<string, string> parameters)
        {
            ODSExport ods = new ODSExport();
            if (parameters != null)
            {
                FillGeneralProperties(ods, parameters);
                // add parameters
            }
            r.Export(ods, stream);
        }

        private void ExportPPTX(Report r, MemoryStream stream, Dictionary<string, string> parameters)
        {
            PowerPoint2007Export pptx = new PowerPoint2007Export();
            if (parameters != null)
            {
                FillGeneralProperties(pptx, parameters);
                // add parameters
            }
            r.Export(pptx, stream);
        }

        private void ExportDOCX(Report r, MemoryStream stream, Dictionary<string, string> parameters)
        {
            Word2007Export docx = new Word2007Export();
            if (parameters != null)
            {
                FillGeneralProperties(docx, parameters);
                // add parameters
            }
            r.Export(docx, stream);
        }

        private void ExportXLSX(Report r, MemoryStream stream, Dictionary<string, string> parameters)
        {
            Excel2007Export xlsx = new Excel2007Export();
            if (parameters != null)
            {
                FillGeneralProperties(xlsx, parameters);
                // add parameters
            }
            r.Export(xlsx, stream);
        }

        private void ExportPDF(Report r, MemoryStream stream, Dictionary<string, string> parameters)
        {
            PDFExport pdf = new PDFExport();
            if (parameters != null)
            {
                FillGeneralProperties(pdf, parameters);
                bool value;
                if (parameters.ContainsKey("EmbeddingFonts") && bool.TryParse(parameters["EmbeddingFonts"], out value))
                    pdf.EmbeddingFonts = value;
                if (parameters.ContainsKey("PdfA") && bool.TryParse(parameters["PdfA"], out value))
                    pdf.PdfCompliance = value ? PDFExport.PdfStandard.PdfA_2a : PDFExport.PdfStandard.None;
                if (parameters.ContainsKey("Background") && bool.TryParse(parameters["Background"], out value))
                    pdf.Background = value;
                if (parameters.ContainsKey("PrintOptimized") && bool.TryParse(parameters["PrintOptimized"], out value))
                    pdf.PrintOptimized = value;
                if (parameters.ContainsKey("Title"))
                    pdf.Title = parameters["Title"];
                if (parameters.ContainsKey("Author"))
                    pdf.Author = parameters["Author"];
                if (parameters.ContainsKey("Subject"))
                    pdf.Subject = parameters["Subject"];
                if (parameters.ContainsKey("Keywords"))
                    pdf.Keywords = parameters["Keywords"];
                if (parameters.ContainsKey("Creator"))
                    pdf.Creator = parameters["Creator"];
                if (parameters.ContainsKey("Producer"))
                    pdf.Producer = parameters["Producer"];                
            }
            r.Export(pdf, stream);
        }

        private void ExportHTML(Report r, MemoryStream stream, Dictionary<string, string> parameters)
        {
            HTMLExport html = new HTMLExport();
            if (parameters != null)
            {
                FillGeneralProperties(html, parameters);
                bool value;
                if (parameters.ContainsKey("Layers") && bool.TryParse(parameters["Layers"], out value))
                    html.Layers = value;
                else
                    html.Layers = false;
                if (parameters.ContainsKey("SinglePage") && bool.TryParse(parameters["SinglePage"], out value))
                    html.SinglePage = value;
                else
                    html.SinglePage = false;
            }
            html.Navigator = false;
            html.SubFolder = false;
            html.SaveStreams = true;
            r.Export(html, (Stream)null);
            ZipArchive zip = new ZipArchive();            
            for (int i = 0; i < html.GeneratedStreams.Count; i++)
                zip.AddStream(html.GeneratedFiles[i], html.GeneratedStreams[i]);
            zip.SaveToStream(stream);
        }

        private void FillGeneralProperties(ExportBase export, Dictionary<string, string> parameters)
        {
            if (parameters.ContainsKey("PageRange"))
            {
                if (parameters["PageRange"] == "All")
                    export.PageRange = PageRange.All;
                else
                    export.PageRange = PageRange.PageNumbers;
            }
            if (parameters.ContainsKey("PageNumbers"))
            {
                export.PageNumbers = parameters["PageNumbers"];
            }
            if (parameters.ContainsKey("Zoom"))
            {
                float zoom;
                if (float.TryParse(parameters["Zoom"], out zoom))
                    export.Zoom = zoom;                
            }
        }

        private bool CheckReportAccess(string reportPath, string reportsRoot)
        {
            if (File.Exists(reportPath) && Path.GetExtension(reportPath) == ".frx")
                return reportPath.IndexOf(reportsRoot) != -1;
            else
                return false;
        }

    }
}
