// o---------------------------------------------------------------------------------o
    // | This file is part of the RGraph package - you can learn more at:                |
    // |                                                                                 |
    // |                       https://www.rgraph.net/license.html                       |
    // |                                                                                 |
    // | RGraph is dual-licensed under the Open Source GPL license. That means that it's |
    // | free to use and there are no restrictions on what you can use RGraph for!       |
    // | If the GPL license does not suit you however, then there's an inexpensive       |
    // | commercial license option available. See the URL above for more details.        |
    // o---------------------------------------------------------------------------------o

    //
    // Initialise the various objects
    //
    RGraph = window.RGraph || {isrgraph:true,isRGraph: true,rgraph:true};


    //
    // This function has been taken out of the RGraph.common.core.js file to
    // enable the CSV reader to work standalone.
    //
    if (!RGraph.AJAX) RGraph.AJAX = function ()
    {
        var args = RGraph.getArgs(arguments, 'url,callback');

        // Mozilla, Safari, ...
        if (window.XMLHttpRequest) {
            var httpRequest = new XMLHttpRequest();

        // MSIE
        } else if (window.ActiveXObject) {
            var httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
        }

        httpRequest.onreadystatechange = function ()
        {
            if (this.readyState == 4 && this.status == 200) {
                this.__user_callback__ = args.callback;

                this.__user_callback__(this.responseText);
            }
        }

        httpRequest.open('GET', args.url, true);
        httpRequest.send();
    };








    //
    // Use the AJAX function above to fetch a string
    //
    if (!RGraph.AJAX.getString) RGraph.AJAX.getString = function ()
    {
        var args = RGraph.getArgs(arguments, 'url,callback');

        RGraph.AJAX(args.url, function ()
        {
            var str = String(this.responseText);

            args.callback(str);
        });
    };








    // This function simply creates UID. Formerly the function in
    // RGraph.common.core.js was being used - but now the CSV code
    // is now standalone, hence this function
    if (!RGraph.createUID) RGraph.createUID = function ()
    {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c)
        {
            var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
            return v.toString(16);
        });
    };








    //
    // This function allows both object based arguments to functions
    // and also regular arguments as well.
    //
    // You can call it from inside a function like this:
    //
    // args = RGraph.HTMLTable.getArgs(arguments, 'object,id,foo,bar');
    //
    // So you're passing it the arguments object and a comma seperated list of names
    // for the arguments.
    //
    // @param array args   The arguments object that you get when inside a function
    // @param string names A comma seperated list of desired names for the arguments
    //                     eg: 'object,color,size'
    //
    if (!RGraph.getArgs) RGraph.getArgs = function (args, names)
    {
        var ret   = {};
        var count = 0;
        names     = names.trim().split(/ *, */);

        if (   args
            && args[0]
            && args.length === 1
            && typeof args[0][names[0]] !== 'undefined') {
            
            for (var i=0; i<names.length; ++i) {
                if (typeof args[0][names[i]] === 'undefined') {
                    args[0][names[i]] = null;
                }
            }

            return args[0];
        } else {
            for (var i in names) {
                ret[names[i]] = typeof args[count] === 'undefined' ? null : args[count];
                
                count += 1;
            }
        }

        return ret;
    };








    RGraph.CSV = function ()
    {
        var args = RGraph.getArgs(arguments, 'url,callback,separator,eol');

        //
        // Some default values
        //
        this.url       = args.url;
        this.ready     = args.callback;
        this.data      = null;
        this.numrows   = null;
        this.numcols   = null;
        this.separator = args.separator || ',';
        this.endofline = args.eol || /\r?\n/;
        this.uid       = RGraph.createUID();








        //
        // A Custom split function
        // 
        // @param string str  The CSV string to split
        // @param mixed  char The character to split on - or it can also be an object like this:
        //                    {
        //                        preserve: false, // Whether to preserve whitespace
        //                        char: ','        // The character to split on
        //                    }
        //
        this.splitCSV = function (str, split)
        {
            // Defaults
            var arr            = [];
            var field          = '';
            var inDoubleQuotes = false;
            var inSingleQuotes = false;
            var preserve       = (typeof split === 'object' && split.preserve) ? true : false;
            
            // The character to split the CSV string on
            if (typeof split === 'object') {
                if (typeof split.char === 'string') {
                    split = split.char;
                } else {
                    split = ',';
                }
            } // If not an object just leave the char as it's supplied



            for (var i=0,len=str.length; i<len; i+=1) {
                
                char = str.charAt(i);
                
                if ( (char === '"') && !inDoubleQuotes) {
                    inDoubleQuotes = true;
                    continue;
                
                } else if ( (char === '"') && inDoubleQuotes) {
                    inDoubleQuotes = false;
                    continue;
                }
                if ( (char === "'") && !inSingleQuotes) {
                    inSingleQuotes = true;
                    continue;
                
                } else if ( (char === "'") && inSingleQuotes) {
                    inSingleQuotes = false;
                    continue;
    
                } else if (char === split && !inDoubleQuotes && !inSingleQuotes) {
                    // TODO look ahead in order to allow for multi-character separators
                    arr.push(field);
                    field = '';
                    continue;
    
                } else {
                    field = field + char;
                }
            }
            
            // Add the last field
            arr.push(field);
            
            // Now trim each value if necessary
            if (!preserve) {
                for (i=0,len=arr.length; i<len; i+=1) {
                    arr[i] = arr[i].trim();
                }
            }
    
            return arr;
        };








        //
        // This function splits the CSV data into an array so that it can be useful.
        //
        this.fetch = function ()
        {
            var sep = this.separator,
                eol = this.endofline,
                obj = this;

            if (this.url.substring(0,3) === 'id:' || this.url.substring(0,4) === 'str:') {

                // Get rid of any surrounding whitespace
                if (this.url.substring(0,3) === 'id:') {
                    var data = document.getElementById(this.url.substring(3)).innerHTML.trim();
                
                } else if (this.url.substring(0,4) === 'str:') {
                    var data = this.url.substring(4).trim();
                }

                // Store the CSV data on the CSV object (ie - this object)
                obj.data = data.split(eol);

                // Store the number of rows
                obj.numrows = obj.data.length;

                for (var i=0,len=obj.data.length; i<len; i+=1) {


                    //
                    // Split the individual line
                    //
                    //var row = obj.data[i].split(sep);
                    var row = obj.splitCSV(obj.data[i], {preserve: false, char: sep});


                    if (!obj.numcols) {
                        obj.numcols = row.length;
                    }

                    //
                    // If the cell is purely made up of numbers - convert it
                    //
                    for (var j=0; j<row.length; j+=1) {
                        if ((/^\-?[0-9.]+$/).test(row[j])) {
                            row[j] = parseFloat(row[j]);
                        }
                            
                        // Assign the split-up-row back to the data array
                        obj.data[i] = row;
                    }
                }
                
                // Call the ready function straight away
                obj.ready(obj);

            } else {

                RGraph.AJAX.getString({url: this.url, callback: function (data)
                {
                    data = data.replace(/(\r?\n)+$/, '');

                    //
                    // Split the lines in the CSV
                    //
                    obj.data = data.split(eol);

                    //
                    // Store the number of rows
                    //
                    obj.numrows = obj.data.length;



                    //
                    // Loop thru each lines in the CSV file
                    //
                    for (var i=0,len=obj.data.length; i<len; i+=1) {
                        //
                        // Use the new split function to split each row NOT preserving whitespace
                        //
                        //var row = obj.data[i].split(sep);
                        var row = obj.splitCSV(obj.data[i], {preserve: false, char: sep});

                        if (!obj.numcols) {
                            obj.numcols = row.length;
                        }

                        //
                        // If the cell is purely made up of numbers - convert it
                        //
                        for (var j=0; j<row.length; j+=1) {
                            if ((/^\-?[0-9.]+$/).test(row[j])) {
                                row[j] = parseFloat(row[j]);
                            }

                            // Assign the split-up-row back to the data array
                            obj.data[i] = row;
                        }
                    }

                    // Call the ready function straight away
                    obj.ready(obj);
                }});
            }
        };








        //
        // Returns a row of the CSV file
        // 
        // @param number index The index of the row to fetch
        // @param        start OPTIONAL If desired you can specify a column to
        //                              start at (which starts at 0 by default)
        //
        this.row =
        this.getRow = function (index)
        {
            var row    = [],
                start  = parseInt(arguments[1]) || 0,
                length = arguments[2];

            // Convert a string based row name to a
            // numeric index
            if (typeof index === 'string') {
                for (var i=0; i<this.data.length; ++i) {
                    if (this.data[i][0] === index) {
                        var found = true;
                        index = i;
                        break;
                    }
                }
                
                if (!found) {
                    return null;
                }
            }


            if (start < 0) {
                row = this.data[index].slice(this.data[index].length  - Math.abs(start));
            } else {
                row = this.data[index].slice(start);
            }

            // Zero length
            if (typeof length === 'number' && length === 0) {
                row = [];

            }  else {
                // Positive length
                if (typeof length === 'number' && length > 0) {
                    row = row.slice(0, length)
                
                // Negative length
                } else if (typeof length === 'number' && length < 0) {
                    for (var i=0; i<Math.abs(length); ++i) {
                        row.pop();
                    }
                }
            }
            
            

            return row;
        };








        //
        // This fuunction allows you to fetch a column
        // of the HTML table data.
        //
        this.col       =
        this.column    =
        this.getColumn =
        this.getCol    = function (index)
        {
            var col    = [],
                start  = arguments[1] || 0,
                length = arguments[2];


            // Convert a string based column name to a
            // numeric index
            if (RGraph.isString(index)) {
                for (var i=0; i<this.data.length; ++i) {
                    if (this.data[0][i] === index) {
                        var found = true;
                        index = i;
                        break;
                    }
                }
                
                if (!found) {
                    return null;
                }
            }


            if (start >= 0) {
                for (var i=start; i<this.data.length; i+=1) {
                    if (this.data[i]) {
                        col.push(this.data[i][index]);
                    } else {
                        col.push(null);
                    }
                }
            } else {
                for (var i=(this.data.length - Math.abs(start)); i<this.data.length; i+=1) {
                    if (this.data[i]) {
                        col.push(this.data[i][index]);
                    } else {
                        col.push(null);
                    }
                }
            }

            // Zero length
            if (typeof length === 'number' && length === 0) {
                col = [];

            }  else {
                // Positive length
                if (typeof length === 'number' && length > 0) {
                    col = col.slice(0, length)
                
                // Negative length
                } else if (typeof length === 'number' && length < 0) {
                    for (var i=0; i<Math.abs(length); ++i) {
                        col.pop();
                    }
                }
            }

            return col;
        };









        // Fetch the CSV file
        this.fetch();
    };