/**
 *
 * FusionCharts Exporter is an ASP.NET C# script that handles 
 * FusionCharts (since v3.1) Server Side Export feature.
 * This in conjuncture with various export classes would 
 * process FusionCharts Export Data POSTED to it from FusionCharts 
 * and convert the data to image or PDF and subsequently save to the 
 * server or response back as http response to client side as download.
 *
 * This script might be called as the FusionCharts Exporter - main module 
 *
 *    @author FusionCharts
 *    @description FusionCharts Exporter (Server-Side - ASP.NET C#)
 *    @version 3.0 [ 18 July 2014 ]
 *  
 */
/**
 *  ChangeLog / Version History:
 *  ----------------------------
 *
 *   3.0 [ 18 July 2014 ]
 *       - Support for JavaScript Chart (SVG)
 *       
 *   2.0 [ 12 February 2009 ] 
 *       - Integrated with new Export feature of FusionCharts 3.1
 *       - can save to server side directory
 *       - can provide download or open in popup window.
 *       - can report back to chart
 *       - can save as PDF/JPG/PNG/GIF
 *
 *   1.0 [ 16 August 2007 ]
 *       - can process chart data to jpg image and response back to client side as download.
 *
 */
/**
 * Copyright (c) 2014 InfoSoft Global Private Limited. All Rights Reserved
 * 
 */
/**
 *  GENERAL NOTES
 *  -------------
 *
 *  Chart would POST export data (which consists of encoded image data stream,  
 *  width, height, background color and various other export parameters like 
 *  exportFormat, exportFileName, exportAction, exportTargetWindow) to this script. 
 *  
 *  The script would process this data using appropriate resource classes & build 
 *  export binary (PDF/image) 
 *
 *  It either saves the binary as file to a server side directory or push it as
 *  Download to client side.
 *
 *
 *  ISSUES
 *  ------
 *   Q. What if someone wishes to open in the same page where the chart existed as postback
 *      replacing the old page?
 * 
 *   A. Not directly supported using any chart attribute or parameter but can do by
 *      removing/commenting the line containing 'header( content-disposition ...'
 *     
 */
/**
 * 
 *   @requires	FCIMGGenerator  Class to export FusionCharts image data to JPG, PNG, GIF binary
 *   @requires  FCPDFGenerator  Class to export FusionCharts image data to PDF binary
 *
 */

using System;
using System.IO;
using System.Web;
using System.Drawing;
using System.Collections;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.Text.RegularExpressions;
using SharpVectors.Converters;
using System.Web.Script.Serialization;

/// <summary>
/// FusionCharts Exporter is an ASP.NET C# script that handles 
/// FusionCharts (since v3.1) Server Side Export feature.
/// This in conjuncture with other resource classses would 
/// process FusionCharts Export Data POSTED to it from FusionCharts 
/// and convert the data to an image or a PDF. Subsequently, it would save 
/// to the server or respond back as an HTTP response to client side as download.
/// 
/// This script might be called as the FusionCharts Exporter - main module
/// </summary>
/// 
public partial class FCExporter : System.Web.UI.Page
{


    /// <summary>
    /// IMPORTANT: You need to change the location of folder where 
    /// the exported chart images/PDFs will be saved on your 
    /// server. Please specify the path to a folder with 
    /// write permissions in the constant SAVE_PATH below. 
    /// 
    /// Please provide the path as per ASP.NET path conventions. 
    /// You can use relative or  absolute path.
    /// 
    /// Special Cases: 
    ///     '/' means 'wwwroot' directory.
    ///     '. /' ( without the space after .) is the directory where the FCExporter.aspx file recides.
    ///     
    /// Absolute Path :
    /// 
    ///     can be like this : "C:\\myFolders\\myImages" 
    ///     ( Please never use single backslash as that would stop execution of the code instantly)
    ///     or "C:/myFolders/myImages"
    /// 
    ///     You may have a // or \ at end : "C:\\myFolders\\myImages\\"  or "C:/myFolders/myImages/"
    /// 
    ///     You can also have mixed slashes : "C:\\myFolders/myImages" 
    ///     
    /// 
    /// </summary>
    /// directory where the FCExporter.aspx file recides
    private const string SAVE_PATH = "./Exported_Images/";

    /// <summary>
    /// IMPORTANT: This constant HTTP_URI stores the HTTP reference to 
    /// the folder where exported charts will be saved. 
    /// Please enter the HTTP representation of that folder 
    /// in this constant e.g., http://www.yourdomain.com/images/
    /// </summary>
    private const string HTTP_URI = "Exported_Images/";

    /// <summary>
    /// OVERWRITEFILE sets whether the export handler would overwrite an existing file 
    /// the newly created exported file. If it is set to false the export handler would
    /// not overwrite. In this case, if INTELLIGENTFILENAMING is set to true the handler
    /// would add a suffix to the new file name. The suffix is a randomly generated GUID.
    /// Additionally, you add a timestamp or a random number as additional suffix.
    /// </summary>
    private bool OVERWRITEFILE = false;
    private bool INTELLIGENTFILENAMING = true;
    private string FILESUFFIXFORMAT = "TIMESTAMP";// // value can be either 'TIMESTAMP' or 'RANDOM'


    /// <summary>
    /// This is a constant list of the MIME types related to each export format this resource handles
    /// The value is semicolon separated key value pair for each format
    /// Each key is the format and value is the MIME type
    /// </summary>
    private const string MIMETYPES = "pdf=application/pdf;jpg=image/jpeg;jpeg=image/jpeg;gif=image/gif;png=image/png;svg=image/svg+xml";

    /// <summary>
    /// This is a constant list of all the file extensions for the export formats
    /// The value is semicolon separated key value pair for each format
    /// Each key is the format and value is the file extension 
    /// </summary>
    private const string EXTENSIONS = "pdf=pdf;jpg=jpg;jpeg=jpg;gif=gif;png=png;svg=svg";

    /// <summary>
    /// Lists the default exportParameter values taken, if not provided by chart
    /// </summary>
    private const string DEFAULTPARAMS = "exportfilename=FusionCharts;exportformat=PDF;exportaction=download;exporttargetwindow=_self";

    /// <summary>
    /// Stores server notices, if any as string [ to be sent back to chart after save ] 
    /// </summary>
    private string notices = "";
    /// <summary>
    /// Whether the export action is download. Default value. Would change as per setting retrieved from chart.
    /// </summary>
    private bool isDownload = true;

    /// <summary>
    /// DOMId of the chart
    /// </summary>
    private string DOMId;

    public bool IsSVGData { get; set; }

    public bool isImgData { get; set; }

    /// <summary>
    /// Stores SVG information.
    /// </summary>
    private TextReader svgData;

    /// <summary>
    /// Stores SVG in-memory file.
    /// </summary>
    private MemoryStream svgStream;

    private Hashtable exportData;

    /// <summary>
    /// The main function that handles all Input - Process - Output of this Export Architecture
    /// </summary>
    /// <param name="sender">FusionCharts chart SWF</param>
    /// <param name="e"></param>
    protected void Page_Load(object sender, EventArgs e)
    {

        /**
         * Retrieve export data from POST Request sent by chart
         * Parse the Request stream into export data readable by this script
         */
        exportData = parseExportRequestStream();

        // process export data and get the processed data (image/PDF) to be exported
        MemoryStream exportObject = null;

        if (IsSVGData)
        {
            if (exportData["encodedImgData"] != null && exportData["encodedImgData"].ToString() != "" && ((Hashtable)exportData["parameters"])["exportformat"].ToString() == "svg")
            {
                exportObject = exportProcessor(((Hashtable)exportData["parameters"])["exportformat"].ToString(), exportData["svg"].ToString(), (Hashtable)exportData["parameters"], exportData["encodedImgData"].ToString());
            }
            else
            {
                exportObject = exportProcessor(((Hashtable)exportData["parameters"])["exportformat"].ToString(), "svg", (Hashtable)exportData["parameters"]);
            }

        }
        else
        {
            if (isImgData)
            {
                convertRawImageDataToFile(exportData);
            }
            else
            {
                if (exportData["encodedImgData"] != null && exportData["encodedImgData"].ToString() != "" && ((Hashtable)exportData["parameters"])["exportformat"].ToString() == "svg")
                {
                    exportObject = exportProcessor(((Hashtable)exportData["parameters"])["exportformat"].ToString(), "svg", (Hashtable)exportData["parameters"], exportData["encodedImgData"].ToString());
                }
                else
                {
                    exportObject = exportProcessor(((Hashtable)exportData["parameters"])["exportformat"].ToString(), "svg", (Hashtable)exportData["parameters"]);
                }
            }
        }

        /*
         * Send the export binary to output module which would either save to a server directory
         * or send the export file to download. Download terminates the process while
         * after save the output module sends back export status 
         */
        //object exportedStatus = IsSVGData ? outputExportObject(exportObject, exportData) : outputExportObject(exportObject, (Hashtable)exportData["parameters"]);
        object exportedStatus = outputExportObject(exportObject, (Hashtable)exportData["parameters"]);

        // Dispose export object
        exportObject.Close();
        exportObject.Dispose();

        /*
         * Build Appropriate Export Status and send back to chart by flushing the  
         * procesed status to http response. This returns status back to chart. 
         * [ This is not applicable when Download action took place ]
         */
        flushStatus(exportedStatus, (Hashtable)exportData["meta"]);

    }

    /// <summary>
    /// Parses POST stream from chart and builds a Hashtable containing 
    /// export data and parameters in a format readable by other functions.
    ///  The Hashtable contains keys 'stream' (contains encoded 
    /// image data) ; 'meta' ( Hashtable with 'width', 'height' and 'bgColor' keys) ;
    /// and 'parameters' ( Hashtable of all export parameters from chart as keys, like - exportFormat, 
    /// exportFileName, exportAction etc.)
    /// </summary>
    /// <returns>Hashtable of processed export data and parameters.</returns>
    private Hashtable parseExportRequestStream()
    {
        // store all export data
        Hashtable exportData = new Hashtable();
        string svgStr = "";

        IsSVGData = false;

        if (Request["stream_type"] == "svg")
        {
            IsSVGData = true;
            exportData["svg"] = Request["stream"];

            // Added custom parameter
            exportData["exporttargetwindow"] = "_self";

            //Get all export parameters into a Hastable
            Hashtable parameters = parseParams(Request["parameters"]);  //parseParams("exportaction=" + exportData["exportaction"].ToString());
            exportData["parameters"] = parameters;

            svgStr = exportData["svg"].ToString();
            svgStr = svgStr.Substring(0, svgStr.IndexOf("</svg>") + 6);
            // fix to replace &nbsp; string occurred in some data source specifically excel
            // need to look for a more proper method which covers all such situations
            svgStr = svgStr.Replace("&nbsp;", " ");
            exportData["svg"] = svgStr;
            if (Request["encodedImgData"] != null)
            {
                exportData["encodedImgData"] = Request["encodedImgData"];
            }
            byte[] svg = System.Text.Encoding.UTF8.GetBytes(exportData["svg"].ToString());
            svgStream = new MemoryStream(svg);
            svgData = new StreamReader(svgStream);
        }
        // If Flash Charts
        else
        {
            if (Request["stream_type"] == "image-data")
            {
                isImgData = true;
                if (Request["stream"] == null || Request["stream"].Trim() == "") raise_error("100", true);
                exportData["stream"] = Request["stream"].Trim().Replace(" ", "+");
                exportData["stream"] = exportData["stream"].ToString().Substring(exportData["stream"].ToString().IndexOf(",") + 1);
                String[] parametersArray = Request["parameters"].ToString().Split('|');
                exportData["exportfilename"] = parametersArray[0].Split('=')[1];
                exportData["exportformat"] = parametersArray[1].Split('=')[1].ToLower();
            }
            else
            {
                isImgData = false;
                //String of compressed image data
                exportData["stream"] = Request["stream"];

                //Halt execution  if image stream is not provided.
                if (Request["stream"] == null || Request["stream"].Trim() == "") raise_error("100", true);

                //Get all export parameters into a Hastable
                Hashtable parameters = parseParams(Request["parameters"]);
                exportData["parameters"] = parameters;
            }
        }

        //get width and height of the chart
        Hashtable meta = new Hashtable();

        meta["width"] = Request["meta_width"];
        //Halt execution on error
        if (Request["meta_width"] == null || Request["meta_width"].Trim() == "") raise_error("101", true);

        meta["height"] = Request["meta_height"];
        //Halt execution on error
        if (Request["meta_height"] == null || Request["meta_height"].Trim() == "") raise_error("101", true);


        //Background color of chart
        meta["bgcolor"] = Request["meta_bgColor"];
        if (meta["bgcolor"] == null || meta["bgcolor"].ToString().Trim() == "")
        {
            // Send notice if BgColor is not provided
            raise_error(" Background color not specified. Taking White (FFFFFF) as default background color.");
            // Set White as Default Background color            
            meta["bgcolor"] = "FFFFFF";
        }

        // DOMId of the chart
        meta["DOMId"] = Request["meta_DOMId"] == null ? "" : Request["meta_DOMId"];
        DOMId = meta["DOMId"].ToString();

        exportData["meta"] = meta;

        return exportData;
    }
    /// <summary>
    /// Decode a base64 encoded string
    /// </summary>
    /// <param name="data">A base64 encoded string </param>
    /// <returns>String decoded from input </returns>
    private byte[] base64Decode(string data)
    {
        return Convert.FromBase64String(data);
    }

    private void convertRawImageDataToFile(Hashtable exportData)
    {
        String fileName = "";
        fileName = Server.MapPath(".") + "\\temp\\" + exportData["exportfilename"].ToString() + "." + exportData["exportformat"].ToString().ToLower();
        System.IO.File.WriteAllBytes(fileName, base64Decode(exportData["stream"].ToString()));
        byte[] data = System.IO.File.ReadAllBytes(fileName);

        string mime = getMime(exportData["exportformat"].ToString());
        Response.ContentType = mime;
        Response.AddHeader("Content-Disposition", "attachment" + "; filename=\"" + fileName + "." + exportData["exportformat"].ToString().ToLower() + "\"");

        Hashtable retStatus = new Hashtable();
        retStatus["filepath"] = "";

        // set the output strem to Response stream as the file is going to be downloaded
        retStatus["outStream"] = Response.OutputStream;

        Stream outStream = (Stream)retStatus["outStream"];
        outStream.Flush();
        outStream.Close();

        Response.End();
    }
    /// <summary>
    /// Parse export 'parameters' string into a Hashtable 
    /// Also synchronise default values from defaultparameterValues Hashtable
    /// </summary>
    /// <param name="strParams">A string with parameters (key=value pairs) separated  by | (pipe)</param>
    /// <returns>Hashtable containing parsed key = value pairs.</returns>
    private Hashtable parseParams(string strParams)
    {

        //default parameter values
        Hashtable defaultParameterValues = bang(DEFAULTPARAMS);

        // get parameters
        Hashtable parameters = bang(strParams, new char[] { '|', '=' });

        // sync with default values
        // iterate through each default parameter value
        foreach (DictionaryEntry param in defaultParameterValues)
        {
            // if a parameter from the defaultParameterValues Hashtable is not present
            // in the parameters hashtable take the parameter and value from default
            // parameter hashtable and add it to params hashtable
            // This is needed to ensure proper export
            if (parameters[param.Key] == null) parameters[param.Key] = param.Value.ToString();
        }

        // set a global flag which denotes whether the export is download or not
        // this is needed in many a functions 
        isDownload = parameters["exportaction"].ToString().ToLower() == "download";


        // return parameters
        return parameters;


    }

    /// <summary>
    /// Get image data from the json object Request["encodedImgData"].
    /// </summary>
    /// <param name="imageData">(Dictionary<string, Dictionary<string, string>>) all image Image data as a combined object</param>
    /// <param name="imageName">(string) Image Name</param>
    /// <returns></returns> 
    private string getImageData(Dictionary<string, Dictionary<string, string>> imageData, string imageName)
    {
        string data = "";
        foreach (string key in imageData.Keys)
        {
            if ((imageData[key]["name"] + "." + imageData[key]["type"]) == imageName)
            {
                data = imageData[key]["encodedData"];
                break;
            }
        };
        return data;
    }
    private string stichImageToSVG(string svgData, string imageData)
    {
        JavaScriptSerializer ser = new JavaScriptSerializer();
        var data = ser.Deserialize<Dictionary<string, Dictionary<string, string>>>(imageData);

        List<string> rawImageDataArray = new List<string>();
        List<string> hrefArray = new List<string>();
        
        // /(<image[^>]*xlink:href *= *[\"']?)([^\"']*)/i
        Regex regex = new Regex("<image.+?xlink:href=\"(.+?)\".+?/?>");
        int counter = 0;
        foreach (Match match in regex.Matches(svgData))
        {
            string[] temp1 = match.Value.Split(new string[] { "xlink:href=" }, StringSplitOptions.None);
            hrefArray.Add(temp1[1].Split('"')[1]);
            string[] imageNameArray = hrefArray[counter].Split('/');
            rawImageDataArray.Add(getImageData(data, imageNameArray[imageNameArray.Length - 1]));
            counter+=1;
        }
        for (int index = 0; index < rawImageDataArray.Count; index++)
        {
            svgData = svgData.Replace(hrefArray[index], rawImageDataArray[index]);
        }
    
        return svgData;
    }

    private string stichImageToSVGAndGetString(string svgData, string imageData)
    {
        
        return stichImageToSVG(svgData, imageData);
    }

    private MemoryStream stichImageToSVGAndGetStream(string svgData, string imageData)
    {

        svgData = stichImageToSVG(svgData, imageData);
        byte[] svg = System.Text.Encoding.UTF8.GetBytes(svgData.ToString());
        return new MemoryStream(svg);
    }
    /// <summary>
    /// Get Export data from and build the export binary/objct.
    /// </summary>
    /// <param name="strFormat">(string) Export format</param>
    /// <param name="stream">(string) Export image data in FusionCharts compressed format</param>
    /// <param name="meta">{Hastable)Image meta data in keys "width", "heigth" and "bgColor"</param>
    /// <param name="meta">{string)Image data</param>
    /// <returns></returns>

    private MemoryStream exportProcessor(string strFormat, string stream, Hashtable meta, string imageData)
    {
        return stichImageToSVGAndGetStream(stream, imageData);
    }
    private MemoryStream exportProcessor(string strFormat, string stream, Hashtable meta)
    {
        if (exportData["encodedImgData"] != null && exportData["encodedImgData"].ToString() != "")
        {
            svgStream = stichImageToSVGAndGetStream(exportData["svg"].ToString(), exportData["encodedImgData"].ToString());
            svgData = new StringReader(stichImageToSVGAndGetString(exportData["svg"].ToString(), exportData["encodedImgData"].ToString()));
        }
        strFormat = strFormat.ToLower();
        // initilize memeory stream object to store output bytes
        MemoryStream exportObjectStream = new MemoryStream();

        // Handle Export class as per export format
        switch (strFormat)
        {
            case "pdf":
                if (!IsSVGData)
                {
                    // Instantiate Export class for PDF, build Binary stream and store in stream object
                    FCPDFGenerator PDFGEN = new FCPDFGenerator(stream, meta["width"].ToString(), meta["height"].ToString(), meta["bgcolor"].ToString());
                    exportObjectStream = PDFGEN.getBinaryStream(strFormat);
                }
                else
                {
                    exportObjectStream = GetJSImage(meta, true);
                }

                break;
            case "jpg":
            case "jpeg":
            case "png":
            case "gif":
                if (!IsSVGData)
                {
                    // Instantiate Export class for Images, build Binary stream and store in stream object
                    FCIMGGenerator IMGGEN = new FCIMGGenerator(stream, meta["width"].ToString(), meta["height"].ToString(), meta["bgcolor"].ToString());
                    exportObjectStream = IMGGEN.getBinaryStream(strFormat);
                }
                else
                {
                    exportObjectStream = GetJSImage(meta, false);
                }
                break;
            case "svg":
                exportObjectStream = svgStream;
                break;
            default:
                // In case the format is not recognized
                raise_error(" Invalid Export Format.", true);
                break;
        }

        return exportObjectStream;
    }

    private MemoryStream GetJSImage(Hashtable exportData, bool processPdf)
    {
        MemoryStream exportObjectStream = new MemoryStream();

        //string filename = exportData["filename"].ToString();
        string type = exportData["exportformat"].ToString().ToLower();

        if (processPdf)
        {
            type = "jpg";
        }

        SharpVectors.Renderers.Wpf.WpfDrawingSettings ds = new SharpVectors.Renderers.Wpf.WpfDrawingSettings();

        StreamSvgConverter ssc = new StreamSvgConverter(ds);
        ssc.SaveXaml = false;
        ssc.SaveZaml = false;

        ImageEncoderType encoder = ImageEncoderType.JpegBitmap;

        switch (type)
        {
            case "png":
                encoder = ImageEncoderType.PngBitmap;
                break;
            case "jpeg":
                encoder = ImageEncoderType.JpegBitmap;
                break;
        }

        ssc.EncoderType = encoder;
        ssc.SaveXaml = false;

        if (ssc.Convert(svgData, exportObjectStream))
        {

            if (processPdf)
            {
                FCJSPDFGenerator PDFGEN = new FCJSPDFGenerator(true, exportObjectStream, ssc.Drawing.Bounds.Width.ToString(), ssc.Drawing.Bounds.Height.ToString());
                exportObjectStream = PDFGEN.getBinaryStream(type);
            }
        }

        svgData.Close();
        svgData.Dispose();
        svgStream.Close();
        svgStream.Dispose();

        return exportObjectStream;

    }

    /// <summary>
    /// Checks whether the export action is download or save.
    /// If action is 'download', send export parameters to 'setupDownload' function.
    /// If action is not-'download', send export parameters to 'setupServer' function.
    /// In either case it gets exportSettings and passes the settings along with 
    /// processed export binary (image/PDF) to the output handler function if the
    /// export settings return a 'ready' flag set to 'true' or 'download'. The export
    /// process would stop here if the action is 'download'. In the other case, 
    /// it gets back success status from output handler function and returns it.
    /// </summary>
    /// <param name="exportObj">Export binary/object in memery stream</param>
    /// <param name="exportParams">Hashtable of export parameters</param>
    /// <returns>Export success status ( filename if success, false if not)</returns>
    private object outputExportObject(MemoryStream exportObj, Hashtable exportParams)
    {
        //pass export paramters and get back export settings as per export action
        Hashtable exportActionSettings = (isDownload ? setupDownload(exportParams) : setupServer(exportParams));

        // set default export status to true
        bool status = true;

        // filepath returned by server setup would be a string containing the file path
        // where the export file is to be saved.
        // If filepath is a boolean (i.e. false) the server setup must have failed. Hence, terminate process.
        if (exportActionSettings["filepath"] is bool)
        {
            status = false;
            raise_error(" Failed to export.", true);
        }
        else
        {
            // When 'filepath' is a sting write the binary to output stream
            try
            {
                // Write export binary stream to output stream
                Stream outStream = (Stream)exportActionSettings["outStream"];
                exportObj.WriteTo(outStream);
                outStream.Flush();
                outStream.Close();
                exportObj.Close();
            }
            catch (ArgumentNullException e)
            {
                raise_error(" Failed to export. Error:" + e.Message);
                status = false;
            }
            catch (ObjectDisposedException e)
            {
                raise_error(" Failed to export. Error:" + e.Message);
                status = false;
            }


            if (isDownload)
            {
                // If 'download'- terminate imediately
                // As nothing is to be written to response now.
                Response.End();
            }

        }

        // This is the response after save action
        // If status remains true return the 'filepath'. Otherwise return false to denote failure.
        return (status ? exportActionSettings["filepath"] : false);


    }
    /// <summary>
    /// Flushes exported status message/or any status message to the chart or the output stream.
    /// It parses the exported status through parser function parseExportedStatus,
    /// builds proper response string using buildResponse function and flushes the response
    /// string to the output stream and terminates the program.
    /// </summary>
    /// <param name="filename">Name of the exported file or false on failure</param>
    /// <param name="meta">Image's meta data</param>
    /// <param name="msg">Additional messages</param>
    private void flushStatus(object filename, Hashtable meta, string msg)
    {
        // Process and flush message to response stream and terminate
        Response.Output.Write(buildResponse(parseExportedStatus(filename, meta, msg)));
        Response.Flush();
        Response.End();
    }

    /// <summary>
    /// Flushes exported status message/or any status message to the chart or the output stream.
    /// It parses the exported status through parser function parseExportedStatus,
    /// builds proper response string using buildResponse function and flushes the response
    /// string to the output stream and terminates the program.
    /// </summary>
    /// <param name="filename">Name of the exported file or false on failure</param>
    /// <param name="meta">Image's meta data</param>
    /// <param name="meta"></param>
    private void flushStatus(object filename, Hashtable meta)
    {
        flushStatus(filename, meta, "");
    }


    /// <summary>
    /// Parses the exported status and builds an array of export status information. As per
    /// status it builds a status array which contains statusCode (0/1), statusMesage, fileName,
    /// width, height and notice in some cases.
    /// </summary>
    /// <param name="filename">exported status ( false if failed/error, filename as stirng if success)</param>
    /// <param name="meta">Hastable containing meta descriptions of the chart like width, height</param>
    /// <param name="msg">custom message to be added as statusMessage.</param>
    /// <returns></returns>
    private ArrayList parseExportedStatus(object filename, Hashtable meta, string msg)
    {

        ArrayList arrStatus = new ArrayList();
        // get status
        bool status = (filename is string ? true : false);

        // add notices 
        if (notices.Trim() != "") arrStatus.Add("notice=" + notices.Trim());

        // DOMId of the chart
        arrStatus.Add("DOMId=" + (meta["DOMId"] == null ? DOMId : meta["DOMId"].ToString()));

        // add width and height
        // provide 0 as width and height on failure	
        if (meta["width"] == null) meta["width"] = "0";
        if (meta["height"] == null) meta["height"] = "0";
        arrStatus.Add("height=" + (status ? meta["height"].ToString() : "0"));
        arrStatus.Add("width=" + (status ? meta["width"].ToString() : "0"));

        // add file URI
        arrStatus.Add("fileName=" + (status ? (Regex.Replace(HTTP_URI, @"([^\/]$)", "${1}/") + filename) : ""));
        arrStatus.Add("statusMessage=" + (msg.Trim() != "" ? msg.Trim() : (status ? "Success" : "Failure")));
        arrStatus.Add("statusCode=" + (status ? "1" : "0"));

        return arrStatus;

    }


    /// <summary>
    /// Builds response from an array of status information. Joins the array to a string.
    /// Each array element should be a string which is a key=value pair. This array are either joined by 
    /// a & to build a querystring (to pass to chart) or joined by a HTML <BR> to show neat
    /// and clean status informaton in Browser window if download fails at the processing stage. 
    /// </summary>
    /// <param name="arrMsg">Array of string containing status data as [key=value ]</param>
    /// <returns>A string to be written to output stream</returns>
    private string buildResponse(ArrayList arrMsg)
    {
        // Join export status array elements into querystring key-value pairs in case of 'save' action
        // or separate with <BR> in case of 'download' action. This would make the imformation readable in browser window.
        string msg = isDownload ? "" : "&";
        msg += string.Join((isDownload ? "<br>" : "&"), (string[])arrMsg.ToArray(typeof(string)));
        return msg;
    }

    /// <summary>
    /// Finds if a directory is writable
    /// </summary>
    /// <param name="path">String Path</param>
    /// <returns></returns>
    private bool isDirectoryWritable(string path)
    {
        DirectoryInfo info = new DirectoryInfo(path);
        return (info.Attributes & FileAttributes.ReadOnly) != FileAttributes.ReadOnly;

    }
    /// <summary>
    /// check server permissions and settings and return ready flag to exportSettings 
    /// </summary>
    /// <param name="exportParams">Various export parameters</param>
    /// <returns>Hashtable containing various export settings</returns>
    private Hashtable setupServer(Hashtable exportParams)
    {

        //get export file name
        string exportFile = exportParams["exportfilename"].ToString();
        // get extension related to specified type 
        string ext = getExtension(exportParams["exportformat"].ToString());

        Hashtable retServerStatus = new Hashtable();

        //set server status to true by default
        retServerStatus["ready"] = true;

        // Open a FileStream to be used as outpur stream when the file would be saved
        FileStream fos;

        // process SAVE_PATH : the path where export file would be saved
        // add a / at the end of path if / is absent at the end

        string path = SAVE_PATH;
        // if path is null set it to folder where FCExporter.aspx is present
        if (path.Trim() == "") path = "./";
        path = Regex.Replace(path, @"([^\/]$)", "${1}/");

        try
        {
            // check if the path is relative if so assign the actual path to path
            path = HttpContext.Current.Server.MapPath(path);
        }
        catch (HttpException e)
        {
            raise_error(e.Message);
        }


        // check whether directory exists
        // raise error and halt execution if directory does not exists
        if (!Directory.Exists(path)) raise_error(" Server Directory does not exist.", true);

        // check if directory is writable or not
        bool dirWritable = isDirectoryWritable(path);

        // build filepath
        retServerStatus["filepath"] = exportFile + "." + ext;

        // check whether file exists
        if (!File.Exists(path + retServerStatus["filepath"].ToString()))
        {
            // need to create a new file if does not exists
            // need to check whether the directory is writable to create a new file  
            if (dirWritable)
            {
                // if directory is writable return with ready flag

                // open the output file in FileStream
                fos = File.Open(path + retServerStatus["filepath"].ToString(), FileMode.Create, FileAccess.Write);

                // set the output stream to the FileStream object
                retServerStatus["outStream"] = fos;
                return retServerStatus;
            }
            else
            {
                // if not writable halt and raise error
                raise_error("403", true);
            }
        }

        // add notice that file exists 
        raise_error(" File already exists.");

        //if overwrite is on return with ready flag 
        if (OVERWRITEFILE)
        {
            // add notice while trying to overwrite
            raise_error(" Export handler's Overwrite setting is on. Trying to overwrite.");

            // see whether the existing file is writable
            // if not halt raising error message
            if ((new FileInfo(path + retServerStatus["filepath"].ToString())).IsReadOnly)
                raise_error(" Overwrite forbidden. File cannot be overwritten.", true);

            // if writable return with ready flag 
            // open the output file in FileStream
            // set the output stream to the FileStream object
            fos = File.Open(path + retServerStatus["filepath"].ToString(), FileMode.Create, FileAccess.Write);
            retServerStatus["outStream"] = fos;
            return retServerStatus;
        }

        // raise error and halt execution when overwrite is off and intelligent naming is off 
        if (!INTELLIGENTFILENAMING)
        {
            raise_error(" Export handler's Overwrite setting is off. Cannot overwrite.", true);
        }

        raise_error(" Using intelligent naming of file by adding an unique suffix to the exising name.");
        // Intelligent naming 
        // generate new filename with additional suffix
        exportFile = exportFile + "_" + generateIntelligentFileId();
        retServerStatus["filepath"] = exportFile + "." + ext;

        // return intelligent file name with ready flag
        // need to check whether the directory is writable to create a new file  
        if (dirWritable)
        {
            // if directory is writable return with ready flag
            // add new filename notice
            // open the output file in FileStream
            // set the output stream to the FileStream object
            raise_error(" The filename has changed to " + retServerStatus["filepath"].ToString() + ".");
            fos = File.Open(path + retServerStatus["filepath"].ToString(), FileMode.Create, FileAccess.Write);

            // set the output stream to the FileStream object
            retServerStatus["outStream"] = fos;
            return retServerStatus;
        }
        else
        {
            // if not writable halt and raise error
            raise_error("403", true);
        }

        // in any unknown case the export should not execute	
        retServerStatus["ready"] = false;
        raise_error(" Not exported due to unknown reasons.");
        return retServerStatus;

    }
    /// <summary>
    /// setup download headers and return ready flag in exportSettings 
    /// </summary>
    /// <param name="exportParams">Various export parameters</param>
    /// <returns>Hashtable containing various export settings</returns>
    private Hashtable setupDownload(Hashtable exportParams)
    {

        //get export filename
        string exportFile = exportParams["exportfilename"].ToString();
        //get extension
        string ext = getExtension(exportParams["exportformat"].ToString());
        //get mime type
        string mime = getMime(exportParams["exportformat"].ToString());
        // get target window
        string target = exportParams["exporttargetwindow"].ToString().ToLower();

        // set content-type header 
        Response.ContentType = mime;

        // set content-disposition header 
        // when target is _self the type is 'attachment'
        // when target is other than self type is 'inline'
        // NOTE : you can comment this line in order to replace present window (_self) content with the image/PDF  
        Response.AddHeader("Content-Disposition", (target == "_self" ? "attachment" : "inline") + "; filename=\"" + exportFile + "." + ext + "\"");

        // return exportSetting array. 'Ready' key should be set to 'download'
        Hashtable retStatus = new Hashtable();
        retStatus["filepath"] = "";

        // set the output strem to Response stream as the file is going to be downloaded
        retStatus["outStream"] = Response.OutputStream;
        return retStatus;

    }

    /// <summary>
    ///  gets file extension checking the export type. 
    /// </summary>
    /// <param name="exportType">(string) export format</param>
    /// <returns>string extension name</returns>
    private string getExtension(string exportType)
    {
        // get a Hashtable array of [type=> extension] 
        // from EXTENSIONS constant 
        Hashtable extensionList = bang(EXTENSIONS);
        exportType = exportType.ToLower();

        // if extension type is present in $extensionList return it, otherwise return the type 
        return (extensionList[exportType].ToString() != null ? extensionList[exportType].ToString() : exportType);
    }
    /// <summary>
    /// gets mime type for an export type
    /// </summary>
    /// <param name="exportType">Export format</param>
    /// <returns>Mime type as stirng</returns>
    private string getMime(string exportType)
    {
        // get a Hashtable array of [type=> extension] 
        // from MIMETYPES constant 
        Hashtable mimelist = bang(MIMETYPES);
        string ext = getExtension(exportType);

        // get mime type asociated to extension
        string mime = mimelist[ext].ToString() != null ? mimelist[ext].ToString() : "";
        return mime;
    }

    /// <summary>
    /// generates a file suffix for a existing file name to apply smart file naming 
    /// </summary>
    /// <returns>a string containing GUID and random number /timestamp</returns>
    private string generateIntelligentFileId()
    {
        // Generate Guid
        string guid = System.Guid.NewGuid().ToString("D");

        // check FILESUFFIXFORMAT type 
        if (FILESUFFIXFORMAT.ToLower() == "timestamp")
        {
            // Add time stamp with file name
            guid += "_" + DateTime.Now.ToString("ddMMyyyyHHmmssff");
        }
        else
        {
            // Add Random Number with fileName
            guid += "_" + (new Random()).Next().ToString();
        }

        return guid;
    }


    /// <summary>
    /// Helper function that splits a string containing delimiter separated key value pairs 
    /// into hashtable
    /// </summary>
    /// <param name="str">delimiter separated key value pairs</param>
    /// <param name="delimiterList">List of delimiters</param>
    /// <returns></returns>
    private Hashtable bang(string str, char[] delimiterList)
    {
        Hashtable retArray = new Hashtable();
        // split string as per first delimiter
        if (str == null || str.Trim() == "") return retArray;
        string[] tmpArray = str.Split(delimiterList[0]);


        // iterate through each element of split string
        for (int i = 0; i < tmpArray.Length; i++)
        {
            // split each element as per second delimiter
            string[] tmp2Array = tmpArray[i].Split(delimiterList[1]);

            if (tmp2Array.Length >= 2)
            {
                // if the secondary split creats at-least 2 array elements
                // make the fisrt element as the key and the second as the value
                // of the resulting array
                retArray[tmp2Array[0].ToLower()] = tmp2Array[1];
            }
        }
        return retArray;

    }
    private Hashtable bang(string str)
    {
        return bang(str, new char[2] { ';', '=' });
    }
    private void raise_error(string msg)
    {
        raise_error(msg, false);
    }
    /// <summary>
    /// Error reporter function that has a list of error messages. It can terminate the execution
    /// and send successStatus=0 along with a error message. It can also append notice to a global variable
    /// and continue execution of the program. 
    /// </summary>
    /// <param name="msg">error code as Integer (referring to the index of the errMessages
    /// array containing list of error messages) OR, it can be a string containing the error message/notice</param>
    /// <param name="halt">Whether to halt execution</param>
    private void raise_error(string msg, bool halt)
    {
        Hashtable errMessages = new Hashtable();

        //list of defined error messages
        errMessages["100"] = " Insufficient data.";
        errMessages["101"] = " Width/height not provided.";
        errMessages["102"] = " Insufficient export parameters.";
        errMessages["400"] = " Bad request.";
        errMessages["401"] = " Unauthorized access.";
        errMessages["403"] = " Directory write access forbidden.";
        errMessages["404"] = " Export Resource class not found.";

        // Find whether error message is passed in msg or it is a custom error string.
        string err_message = ((msg == null || msg.Trim() == "") ? "ERROR!" :
                (errMessages[msg] == null ? msg : errMessages[msg].ToString())
            );

        // Halt executon after flushing the error message to response (if halt is true)
        if (halt)
        {
            flushStatus(false, new Hashtable(), err_message);

        }
        // add error to notices global variable
        else
        {
            notices += err_message;
        }

    }



}


/// <summary>
/// FusionCharts Image Generator Class
/// FusionCharts Exporter - 'Image Resource' handles 
/// FusionCharts (since v3.1) Server Side Export feature that
/// helps FusionCharts exported as Image files in various formats. 
/// </summary>
public class FCIMGGenerator
{
    //Array - Stores multiple chart export data
    private ArrayList arrExportData = new ArrayList();
    //stores number of pages = length of $arrExportData array
    private int numPages = 0;


    /// <summary>
    /// Generates bitmap data for the image from a FusionCharts export format
    /// the height and width of the original export needs to be specified
    /// the default background color can also be specified
    /// </summary>
    public FCIMGGenerator(string imageData_FCFormat, string width, string height, string bgcolor)
    {
        setBitmapData(imageData_FCFormat, width, height, bgcolor);
    }

    /// <summary>
    /// Gets the binary data stream of the image
    /// The passed parameter determines the file format of the image
    /// to be exported
    /// </summary>
    public MemoryStream getBinaryStream(string strFormat)
    {

        // the image object 
        Bitmap exportObj = getImageObject();

        // initiates a new binary data sream
        MemoryStream outStream = new MemoryStream();

        // determines the image format
        switch (strFormat)
        {
            case "jpg":
            case "jpeg":
                exportObj.Save(outStream, ImageFormat.Jpeg);
                break;
            case "png":
                exportObj.Save(outStream, ImageFormat.Png);
                break;
            case "gif":
                exportObj.Save(outStream, ImageFormat.Gif);
                break;
            case "tiff":
                exportObj.Save(outStream, ImageFormat.Tiff);
                break;
            default:
                exportObj.Save(outStream, ImageFormat.Bmp);
                break;
        }
        exportObj.Dispose();

        return outStream;

    }


    /// <summary>
    /// Generates bitmap data for the image from a FusionCharts export format
    /// the height and width of the original export needs to be specified
    /// the default background color can also be specified
    /// </summary>
    private void setBitmapData(string imageData_FCFormat, string width, string height, string bgcolor)
    {
        Hashtable chartExportData = new Hashtable();
        chartExportData["width"] = width;
        chartExportData["height"] = height;
        chartExportData["bgcolor"] = bgcolor;
        chartExportData["imagedata"] = imageData_FCFormat;
        arrExportData.Add(chartExportData);
        numPages++;
    }

    /// <summary>
    /// Generates bitmap data for the image from a FusionCharts export format
    /// the height and width of the original export needs to be specified
    /// the default background color should also be specified
    /// </summary>
    private Bitmap getImageObject(int id)
    {
        Hashtable rawImageData = (Hashtable)arrExportData[id];

        // create blank bitmap object which would store image pixel data
        Bitmap image = new Bitmap(Convert.ToInt16(rawImageData["width"]), Convert.ToInt16(rawImageData["height"]), System.Drawing.Imaging.PixelFormat.Format24bppRgb);

        // drwaing surface
        Graphics gr = Graphics.FromImage(image);

        // set background color
        gr.Clear(ColorTranslator.FromHtml("#" + rawImageData["bgcolor"].ToString()));

        string[] rows = rawImageData["imagedata"].ToString().Split(';');

        for (int yPixel = 0; yPixel < rows.Length; yPixel++)
        {
            //Split each row into 'color_count' columns.			
            String[] color_count = rows[yPixel].Split(',');
            //Set horizontal row index to 0
            int xPixel = 0;

            for (int col = 0; col < color_count.Length; col++)
            {
                //Now, if it's not empty, we process it				
                //Split the 'color_count' into color and repeat factor
                String[] split_data = color_count[col].Split('_');

                //Reference to color
                string hexColor = split_data[0];
                //refer to repeat factor
                int fRepeat = int.Parse(split_data[1]);

                //If color is not empty (i.e. not background pixel)
                if (hexColor != "")
                {
                    //If the hexadecimal code is less than 6 characters, pad with 0
                    hexColor = hexColor.Length < 6 ? hexColor.PadLeft(6, '0') : hexColor;
                    for (int k = 1; k <= fRepeat; k++)
                    {

                        //draw pixel with specified color
                        image.SetPixel(xPixel, yPixel, ColorTranslator.FromHtml("#" + hexColor));
                        //Increment horizontal row count
                        xPixel++;
                    }
                }
                else
                {
                    //Just increment horizontal index
                    xPixel += fRepeat;
                }
            }
        }
        gr.Dispose();
        return image;

    }

    /// <summary>
    /// Retreives the bitmap image object
    /// </summary>
    private Bitmap getImageObject()
    {
        return getImageObject(0);
    }

}

/// <summary>
/// FusionCharts Exporter - 'PDF Resource' handles 
/// FusionCharts (since v3.1) Server Side Export feature that
/// helps FusionCharts exported as PDF file.
/// </summary>
public class FCJSPDFGenerator
{

    //Array - Stores multiple chart export data
    private ArrayList arrExportData = new ArrayList();
    //stores number of pages = length of $arrExportData array
    private int numPages = 1;

    private bool _IsJsChart = false;
    private string _ImagePath = "";
    private MemoryStream _ImageStream;
    private string _width = "", _height = "";

    public FCJSPDFGenerator(bool IsJsChart, string fileName, string width, string height)
    {
        this._IsJsChart = IsJsChart;
        this._ImagePath = fileName;
        this._width = width;
        this._height = height;

    }

    public FCJSPDFGenerator(bool IsJsChart, MemoryStream ImageStream, string width, string height)
    {
        this._IsJsChart = IsJsChart;
        this._ImageStream = ImageStream;
        this._width = width;
        this._height = height;

    }

    /// <summary>
    /// Gets the binary data stream of the image
    /// The passed parameter determines the file format of the image
    /// to be exported
    /// </summary>
    public MemoryStream getBinaryStream(string strFormat)
    {
        byte[] exportObj = getPDFObjects(false);

        MemoryStream outStream = new MemoryStream();

        outStream.Write(exportObj, 0, exportObj.Length);

        return outStream;

    }

    //create image PDF object containing the chart image 
    private byte[] addImageToPDF(int id, bool isCompressed)
    {

        MemoryStream imgObj = new MemoryStream();

        //PDF Object number
        int imgObjNo = 6 + id * 3;

        //Get chart Image binary
        byte[] imgBinary = getBitmapData24(this._ImageStream);

        //get the length of the image binary
        int len = imgBinary.Length;

        string width = this._width;
        string height = this._height;

        //Build PDF object containing the image binary and other formats required
        //string strImgObjHead = imgObjNo.ToString() + " 0 obj\n<<\n/Subtype /Image /ColorSpace /DeviceRGB /BitsPerComponent 8 /HDPI 72 /VDPI 72 " + (isCompressed ? "" : "") + "/Width " + width + " /Height " + height + " /Length " + len.ToString() + " >>\nstream\n";
        // Use it for JPG.
        string strImgObjHead = imgObjNo.ToString() + " 0 obj\n<<\n/Subtype /Image /Filter /DCTDecode /ColorSpace /DeviceRGB /BitsPerComponent 8 /Width " + width + " /Height " + height + " /Length " + len.ToString() + " >>\nstream\n";

        imgObj.Write(stringToBytes(strImgObjHead), 0, strImgObjHead.Length);
        imgObj.Write(imgBinary, 0, (int)imgBinary.Length);

        string strImgObjEnd = "endstream\nendobj\n";
        imgObj.Write(stringToBytes(strImgObjEnd), 0, strImgObjEnd.Length);

        imgObj.Close();
        return imgObj.ToArray();
    }

    private byte[] getPDFObjects(bool isCompressed)
    {
        MemoryStream PDFBytes = new MemoryStream();

        //Store all PDF objects in this temporary string to be written to ByteArray
        string strTmpObj = "";


        //start xref array
        ArrayList xRefList = new ArrayList();
        xRefList.Add("xref\n0 ");
        xRefList.Add("0000000000 65535 f \n"); //Address Refenrece to obj 0

        //Build PDF objects s equentially
        //version and header
        strTmpObj = "%PDF-1.3\n%{FC}\n";
        PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);

        //OBJECT 1 : info (optional)
        strTmpObj = "1 0 obj<<\n/Author (FusionCharts)\n/Title (FusionCharts)\n/Creator (FusionCharts)\n>>\nendobj\n";
        xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 1
        PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);

        //OBJECT 2 : Starts with Pages Catalogue
        strTmpObj = "2 0 obj\n<< /Type /Catalog /Pages 3 0 R >>\nendobj\n";
        xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 2
        PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);

        //OBJECT 3 : Page Tree (reference to pages of the catalogue)
        strTmpObj = "3 0 obj\n<<  /Type /Pages /Kids [";
        for (int i = 0; i < numPages; i++)
        {
            strTmpObj += (((i + 1) * 3) + 1) + " 0 R\n";
        }
        strTmpObj += "] /Count " + numPages + " >>\nendobj\n";

        xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 3
        PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);

        int itr = 0;
        string iWidth = this._width;
        string iHeight = this._height;

        //OBJECT 4..7..10..n : Page config
        strTmpObj = (((itr + 2) * 3) - 2) + " 0 obj\n<<\n/Type /Page /Parent 3 0 R \n/MediaBox [ 0 0 " + iWidth + " " + iHeight + " ]\n/Resources <<\n/ProcSet [ /PDF ]\n/XObject <</R" + (itr + 1) + " " + ((itr + 2) * 3) + " 0 R>>\n>>\n/Contents [ " + (((itr + 2) * 3) - 1) + " 0 R ]\n>>\nendobj\n";
        xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 4,7,10,13,16...
        PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);

        //OBJECT 5...8...11...n : Page resource object (xobject resource that transforms the image)
        xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 5,8,11,14,17...
        string xObjR = getXObjResource(itr);
        PDFBytes.Write(stringToBytes(xObjR), 0, xObjR.Length);

        //OBJECT 6...9...12...n : Binary xobject of the page (image)
        byte[] imgBA = addImageToPDF(itr, isCompressed);
        xRefList.Add(calculateXPos((int)PDFBytes.Length));//refenrece to obj 6,9,12,15,18...
        PDFBytes.Write(imgBA, 0, imgBA.Length);

        //xrefs	compilation
        xRefList[0] += ((xRefList.Count - 1) + "\n");

        //get trailer
        string trailer = getTrailer((int)PDFBytes.Length, xRefList.Count - 1);

        //write xref and trailer to PDF
        string strXRefs = string.Join("", (string[])xRefList.ToArray(typeof(string)));
        PDFBytes.Write(stringToBytes(strXRefs), 0, strXRefs.Length);
        //
        PDFBytes.Write(stringToBytes(trailer), 0, trailer.Length);

        //write EOF
        string strEOF = "%%EOF\n";
        PDFBytes.Write(stringToBytes(strEOF), 0, strEOF.Length);

        PDFBytes.Close();
        return PDFBytes.ToArray();

    }


    //Build Image resource object that transforms the image from First Quadrant system to Second Quadrant system
    private string getXObjResource()
    {
        return getXObjResource(0);
    }
    private string getXObjResource(int itr)
    {

        string width = this._width;
        string height = this._height;
        return (((itr + 2) * 3) - 1) + " 0 obj\n<< /Length " + (24 + (width + height).Length) + " >>\nstream\nq\n" + width + " 0 0 " + height + " 0 0 cm\n/R" + (itr + 1) + " Do\nQ\nendstream\nendobj\n";
    }

    private string calculateXPos(int posn)
    {
        return posn.ToString().PadLeft(10, '0') + " 00000 n \n";
    }


    private string getTrailer(int xrefpos)
    {
        return getTrailer(xrefpos, 7);
    }

    private string getTrailer(int xrefpos, int numxref)
    {
        return "trailer\n<<\n/Size " + numxref.ToString() + "\n/Root 2 0 R\n/Info 1 0 R\n>>\nstartxref\n" + xrefpos.ToString() + "\n";
    }


    private byte[] getBitmapData24(string fileName)
    {
        return File.ReadAllBytes(fileName);
    }

    private byte[] getBitmapData24(MemoryStream ImageStream)
    {
        return ImageStream.ToArray();
    }

    // converts a hexadecimal colour string to it's respective byte value
    private byte[] hexToBytes(string strHex)
    {
        if (strHex == null || strHex.Trim().Length == 0) strHex = "00";
        strHex = Regex.Replace(strHex, @"[^0-9a-fA-f]", "");
        if (strHex.Length % 2 != 0) strHex = "0" + strHex;

        int len = strHex.Length / 2;
        byte[] bytes = new byte[len];

        for (int i = 0; i < len; i++)
        {
            string hex = strHex.Substring(i * 2, 2);
            bytes[i] = byte.Parse(hex, System.Globalization.NumberStyles.HexNumber);
        }
        return bytes;

    }

    private byte[] stringToBytes(string str)
    {
        if (str == null) str = "";
        return System.Text.Encoding.ASCII.GetBytes(str);
    }
}

/// <summary>
/// FusionCharts Exporter - 'PDF Resource' handles 
/// FusionCharts (since v3.1) Server Side Export feature that
/// helps FusionCharts exported as PDF file.
/// </summary>
public class FCPDFGenerator
{

    //Array - Stores multiple chart export data
    private ArrayList arrExportData = new ArrayList();
    //stores number of pages = length of $arrExportData array
    private int numPages = 0;

    /// <summary>
    /// Generates a PDF file with the given parameters
    /// The imageData_FCFormat parameter is the FusionCharts export format data
    /// width, height are the respective width and height of the original image
    /// bgcolor determines the default background colour
    /// </summary>
    public FCPDFGenerator(string imageData_FCFormat, string width, string height, string bgcolor)
    {
        setBitmapData(imageData_FCFormat, width, height, bgcolor);
    }

    /// <summary>
    /// Gets the binary data stream of the image
    /// The passed parameter determines the file format of the image
    /// to be exported
    /// </summary>
    public MemoryStream getBinaryStream(string strFormat)
    {
        byte[] exportObj = getPDFObjects(true);

        MemoryStream outStream = new MemoryStream();

        outStream.Write(exportObj, 0, exportObj.Length);

        return outStream;

    }

    /// <summary>
    /// Generates bitmap data for the image from a FusionCharts export format
    /// the height and width of the original export needs to be specified
    /// the default background color should also be specified
    /// </summary>
    private void setBitmapData(string imageData_FCFormat, string width, string height, string bgcolor)
    {
        Hashtable chartExportData = new Hashtable();
        chartExportData["width"] = width;
        chartExportData["height"] = height;
        chartExportData["bgcolor"] = bgcolor;
        chartExportData["imagedata"] = imageData_FCFormat;
        arrExportData.Add(chartExportData);
        numPages++;
    }



    //create image PDF object containing the chart image 
    private byte[] addImageToPDF(int id, bool isCompressed)
    {

        MemoryStream imgObj = new MemoryStream();

        //PDF Object number
        int imgObjNo = 6 + id * 3;

        //Get chart Image binary
        byte[] imgBinary = getBitmapData24(id, isCompressed);

        //get the length of the image binary
        int len = imgBinary.Length;

        string width = getMeta("width", id);
        string height = getMeta("height", id);

        //Build PDF object containing the image binary and other formats required
        //string strImgObjHead = imgObjNo.ToString() + " 0 obj\n<<\n/Subtype /Image /ColorSpace /DeviceRGB /BitsPerComponent 8 /HDPI 72 /VDPI 72 " + (isCompressed ? "" : "") + "/Width " + width + " /Height " + height + " /Length " + len.ToString() + " >>\nstream\n";
        string strImgObjHead = imgObjNo.ToString() + " 0 obj\n<<\n/Subtype /Image /ColorSpace /DeviceRGB /BitsPerComponent 8 /HDPI 72 /VDPI 72 " + (isCompressed ? "/Filter /RunLengthDecode " : "") + "/Width " + width + " /Height " + height + " /Length " + len.ToString() + " >>\nstream\n";



        imgObj.Write(stringToBytes(strImgObjHead), 0, strImgObjHead.Length);
        imgObj.Write(imgBinary, 0, (int)imgBinary.Length);

        string strImgObjEnd = "endstream\nendobj\n";
        imgObj.Write(stringToBytes(strImgObjEnd), 0, strImgObjEnd.Length);

        imgObj.Close();
        return imgObj.ToArray();

    }
    private byte[] addImageToPDF(int id)
    {
        return addImageToPDF(id, true);
    }
    private byte[] addImageToPDF()
    {
        return addImageToPDF(0, true);
    }



    //Main PDF builder function
    private byte[] getPDFObjects()
    {
        return getPDFObjects(true);
    }

    private byte[] getPDFObjects(bool isCompressed)
    {
        MemoryStream PDFBytes = new MemoryStream();

        //Store all PDF objects in this temporary string to be written to ByteArray
        string strTmpObj = "";


        //start xref array
        ArrayList xRefList = new ArrayList();
        xRefList.Add("xref\n0 ");
        xRefList.Add("0000000000 65535 f \n"); //Address Refenrece to obj 0

        //Build PDF objects sequentially
        //version and header
        strTmpObj = "%PDF-1.3\n%{FC}\n";
        PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);

        //OBJECT 1 : info (optional)
        strTmpObj = "1 0 obj<<\n/Author (FusionCharts)\n/Title (FusionCharts)\n/Creator (FusionCharts)\n>>\nendobj\n";
        xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 1
        PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);

        //OBJECT 2 : Starts with Pages Catalogue
        strTmpObj = "2 0 obj\n<< /Type /Catalog /Pages 3 0 R >>\nendobj\n";
        xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 2
        PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);

        //OBJECT 3 : Page Tree (reference to pages of the catalogue)
        strTmpObj = "3 0 obj\n<<  /Type /Pages /Kids [";
        for (int i = 0; i < numPages; i++)
        {
            strTmpObj += (((i + 1) * 3) + 1) + " 0 R\n";
        }
        strTmpObj += "] /Count " + numPages + " >>\nendobj\n";

        xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 3
        PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);


        //Each image page
        for (int itr = 0; itr < numPages; itr++)
        {
            string iWidth = getMeta("width", itr);
            string iHeight = getMeta("height", itr);
            //OBJECT 4..7..10..n : Page config
            strTmpObj = (((itr + 2) * 3) - 2) + " 0 obj\n<<\n/Type /Page /Parent 3 0 R \n/MediaBox [ 0 0 " + iWidth + " " + iHeight + " ]\n/Resources <<\n/ProcSet [ /PDF ]\n/XObject <</R" + (itr + 1) + " " + ((itr + 2) * 3) + " 0 R>>\n>>\n/Contents [ " + (((itr + 2) * 3) - 1) + " 0 R ]\n>>\nendobj\n";
            xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 4,7,10,13,16...
            PDFBytes.Write(stringToBytes(strTmpObj), 0, strTmpObj.Length);


            //OBJECT 5...8...11...n : Page resource object (xobject resource that transforms the image)
            xRefList.Add(calculateXPos((int)PDFBytes.Length)); //refenrece to obj 5,8,11,14,17...
            string xObjR = getXObjResource(itr);
            PDFBytes.Write(stringToBytes(xObjR), 0, xObjR.Length);

            //OBJECT 6...9...12...n : Binary xobject of the page (image)
            byte[] imgBA = addImageToPDF(itr, isCompressed);
            xRefList.Add(calculateXPos((int)PDFBytes.Length));//refenrece to obj 6,9,12,15,18...
            PDFBytes.Write(imgBA, 0, imgBA.Length);
        }

        //xrefs	compilation
        xRefList[0] += ((xRefList.Count - 1) + "\n");

        //get trailer
        string trailer = getTrailer((int)PDFBytes.Length, xRefList.Count - 1);

        //write xref and trailer to PDF
        string strXRefs = string.Join("", (string[])xRefList.ToArray(typeof(string)));
        PDFBytes.Write(stringToBytes(strXRefs), 0, strXRefs.Length);
        //
        PDFBytes.Write(stringToBytes(trailer), 0, trailer.Length);

        //write EOF
        string strEOF = "%%EOF\n";
        PDFBytes.Write(stringToBytes(strEOF), 0, strEOF.Length);

        PDFBytes.Close();
        return PDFBytes.ToArray();

    }


    //Build Image resource object that transforms the image from First Quadrant system to Second Quadrant system
    private string getXObjResource()
    {
        return getXObjResource(0);
    }
    private string getXObjResource(int itr)
    {

        string width = getMeta("width", itr);
        string height = getMeta("height", itr);
        return (((itr + 2) * 3) - 1) + " 0 obj\n<< /Length " + (24 + (width + height).Length) + " >>\nstream\nq\n" + width + " 0 0 " + height + " 0 0 cm\n/R" + (itr + 1) + " Do\nQ\nendstream\nendobj\n";
    }

    private string calculateXPos(int posn)
    {
        return posn.ToString().PadLeft(10, '0') + " 00000 n \n";
    }


    private string getTrailer(int xrefpos)
    {
        return getTrailer(xrefpos, 7);
    }

    private string getTrailer(int xrefpos, int numxref)
    {
        return "trailer\n<<\n/Size " + numxref.ToString() + "\n/Root 2 0 R\n/Info 1 0 R\n>>\nstartxref\n" + xrefpos.ToString() + "\n";
    }


    private byte[] getBitmapData24()
    {
        return getBitmapData24(0, true);
    }
    private byte[] getBitmapData24(int id, bool isCompressed)
    {

        string rawImageData = getMeta("imagedata", id);
        string bgColor = getMeta("bgcolor", id);

        MemoryStream imageData24 = new MemoryStream();

        // Split the data into rows using ; as separator
        string[] rows = rawImageData.Split(';');

        for (int yPixel = 0; yPixel < rows.Length; yPixel++)
        {
            //Split each row into 'color_count' columns.			
            string[] color_count = rows[yPixel].Split(',');

            for (int col = 0; col < color_count.Length; col++)
            {
                //Now, if it's not empty, we process it				
                //Split the 'color_count' into color and repeat factor
                string[] split_data = color_count[col].Split('_');

                //Reference to color
                string hexColor = split_data[0] != "" ? split_data[0] : bgColor;
                //If the hexadecimal code is less than 6 characters, pad with 0
                hexColor = hexColor.Length < 6 ? hexColor.PadLeft(6, '0') : hexColor;

                //refer to repeat factor
                int fRepeat = int.Parse(split_data[1]);

                // convert color string to byte[] array
                byte[] rgb = hexToBytes(hexColor);

                // Set the repeated pixel in MemoryStream
                for (int cRepeat = 0; cRepeat < fRepeat; cRepeat++)
                {
                    imageData24.Write(rgb, 0, 3);
                }

            }
        }

        int len = (int)imageData24.Length;
        imageData24.Close();

        //Compress image binary
        if (isCompressed)
        {
            return new PDFCompress(imageData24.ToArray()).RLECompress();
        }
        else
        {
            return imageData24.ToArray();
        }
    }

    // converts a hexadecimal colour string to it's respective byte value
    private byte[] hexToBytes(string strHex)
    {
        if (strHex == null || strHex.Trim().Length == 0) strHex = "00";
        strHex = Regex.Replace(strHex, @"[^0-9a-fA-f]", "");
        if (strHex.Length % 2 != 0) strHex = "0" + strHex;

        int len = strHex.Length / 2;
        byte[] bytes = new byte[len];

        for (int i = 0; i < len; i++)
        {
            string hex = strHex.Substring(i * 2, 2);
            bytes[i] = byte.Parse(hex, System.Globalization.NumberStyles.HexNumber);
        }
        return bytes;

    }

    private string getMeta(string metaName)
    {
        return getMeta(metaName, 0);
    }

    private string getMeta(string metaName, int id)
    {
        if (metaName == null) metaName = "";
        Hashtable chartData = (Hashtable)arrExportData[id];
        return (chartData[metaName] == null ? "" : chartData[metaName].ToString());
    }

    private byte[] stringToBytes(string str)
    {
        if (str == null) str = "";
        return System.Text.Encoding.ASCII.GetBytes(str);
    }


}


/// <summary>
/// This is an ad-hoc class to compress PDF stream.
/// Currently this class compresses binary (byte) stream using RLE which 
/// PDF 1.3 specification has thus formulated:
/// 
/// The RunLengthDecode filter decodes data that has been encoded in a simple 
/// byte-oriented format based on run length. The encoded data is a sequence of 
/// runs, where each run consists of a length byte followed by 1 to 128 bytes of data. If 
/// the length byte is in the range 0 to 127, the following length + 1 (1 to 128) bytes 
/// are copied literally during decompression. If length is in the range 129 to 255, the 
/// following single byte is to be copied 257 - length (2 to 128) times during decompression. 
/// A length value of 128 denotes EOD.
/// 
/// The chart image compression ratio comes to around 10:3 
/// 
/// </summary>
public class PDFCompress
{

    /// <summary>
    /// stores the output compressed data in MemoryStream object later to be converted to byte[] array
    /// </summary>
    private MemoryStream _Compressed = new MemoryStream();

    /// <summary>
    ///  Uncompresses data as byte[] array
    /// </summary>
    private byte[] _UnCompressed;


    /// <summary>
    /// Takes the uncompressed byte array
    /// </summary>
    /// <param name="UnCompressed">uncompressed data</param>
    public PDFCompress(byte[] UnCompressed)
    {
        _UnCompressed = UnCompressed;
    }

    /// <summary>
    /// Write compressed data as RunLength
    /// </summary>
    /// <param name="length">The length of repeated data</param>
    /// <param name="encodee">The byte to be repeated</param>
    /// <returns></returns>
    private int WriteRunLength(int length, byte encodee)
    {
        // write the repeat length
        _Compressed.WriteByte((byte)(257 - length));
        // write the byte to be repeated
        _Compressed.WriteByte(encodee);

        //re-set repeat length
        length = 1;
        return length;
    }

    private void WriteNoRepeater(MemoryStream NoRepeatBytes)
    {
        // write the length of non repeted data
        _Compressed.WriteByte((byte)((int)NoRepeatBytes.Length - 1));
        // write the non repeated data put literally
        _Compressed.Write(NoRepeatBytes.ToArray(), 0, (int)NoRepeatBytes.Length);

        // re-set non repeat byte storage stream
        NoRepeatBytes.SetLength(0);
    }

    /// <summary>
    /// compresses uncompressed data to compressed data in byte array
    /// </summary>
    /// <returns></returns>
    public byte[] RLECompress()
    {
        // stores non repeatable data
        MemoryStream NoRepeat = new MemoryStream();

        // repeat counter
        int _RL = 1;

        // 2 consecutive bytes to compare
        byte preByte = 0, postByte = 0;

        // iterate through the uncompressed bytes
        for (int i = 0; i < _UnCompressed.Length - 1; i++)
        {
            // get 2 consecutive bytes
            preByte = _UnCompressed[i];
            postByte = _UnCompressed[i + 1];

            // if both are same there is scope for repitition
            if (preByte == postByte)
            {
                // but flush the non repeatable data (if present) to compressed stream 
                if (NoRepeat.Length > 0) WriteNoRepeater(NoRepeat);

                // increase repeat count
                _RL++;

                // if repeat count reaches limit of repeat i.e. 128 
                // write the repeat data and reset the repeat counter
                if (_RL > 128) _RL = WriteRunLength(_RL - 1, preByte);

            }
            else
            {
                // when consecutive bytes do not match

                // store non-repeatable data
                if (_RL == 1) NoRepeat.WriteByte(preByte);

                // write repeated length and byte (if present ) to output stream
                if (_RL > 1) _RL = WriteRunLength(_RL, preByte);

                // write non repeatable data to out put stream if the length reaches limit
                if (NoRepeat.Length == 128) WriteNoRepeater(NoRepeat);
            }
        }

        // at the end of iteration 
        // take care of the last byte

        // if repeated 
        if (_RL > 1)
        {
            // write run length and byte (if present ) to output stream
            _RL = WriteRunLength(_RL, preByte);
        }
        else
        {
            // if non repeated byte is left behind
            // write non repeatable data to output stream 
            NoRepeat.WriteByte(postByte);
            WriteNoRepeater(NoRepeat);
        }


        // wrote EOD
        _Compressed.WriteByte((byte)128);

        //close streams
        NoRepeat.Close();
        _Compressed.Close();

        // return compressed data in byte array
        return _Compressed.ToArray();
    }

}

