# Source: System.js

    /**
     * @license
     * Copyright 2020 Roberto Luiz Souza Monteiro,
     *                Renata Souza Barreto,
     *                Hernane Borges de Barros Pereira.
     *
     * Licensed under the Apache License, Version 2.0 (the 'License');
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an 'AS IS' BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    /**
     * MaiaScript system library.
     * @class
     */
    function System() {
        init();

        /**
         * Creates the attributes of the class.
         */
        function init() {
            // Class attributes goes here.
        }

        /**
         * Convert Unicode caracters to Latin1.
         * @param {string}   str - Unicode string.
         * @return {string}  The Unicode string converted to Latin1.
         */
        this.base64EncodeUnicode = function(str) {
            // First we escape the string using encodeURIComponent to get the UTF-8 encoding of the characters,
            // then we convert the percent encodings into raw bytes, and finally feed it to btoa() function.
            utf8Bytes = encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
                return String.fromCharCode('0x' + p1);
            });
            return btoa(utf8Bytes);
        }

        /**
         * Converts an array to a CSV file, using the character indicated as the column separator.
         * @param {array}    csvData - CSV data.
         * @param {string}   recordSeparator - The separator characters.
         * @param {array}    header - Column descriptors.
         * @return {string}  The CSV file data.
         */
         this.createCSV = function(csvData, recordSeparator, header)
         {
            if (typeof separator != 'undefined') {
                var separator = ',';
            }
            if (typeof csvData != 'undefined') {
                var fileContents = '';
                if (typeof header != 'undefined') {
                    for (var i = 0; i < header.length; i++) {
                        fileContents += '"' + header[i] + '"';
                        if (i < header.length - 1) {
                            fileContents += recordSeparator;
                        }
                    }
                    fileContents += '\n';
                }
                for (var i = 0; i < csvData.length; i++) {
                    record = csvData[i];
                    for (var j = 0; j < record.length; j++) {
                        if (typeof record[j] == 'string') {
                            fileContents += '"' + record[j] + '"';
                        } else if (typeof record[j] == 'object') {
                            fileContents += JSON.stringify(record[j]);
                        } else {
                            fileContents += record[j];
                        }
                        if (j < record.length - 1) {
                            fileContents += recordSeparator;
                        }
                    }
                    fileContents += '\n';
                }
                return fileContents;
            } else {
                throw new Error('Invalid argument for function createCSV. Argument must be an array.');
            }
         }

        /**
         * Download a file.
         * @param {string}  fileName - File name.
         * @param {string}  fileData - Data to save.
         * @param {string}  mimeType - Mime type (default: 'text/plain').
         * @return          The file is downloaded.
         */
        this.downloadFile = function(fileName, fileData, mimeType) {
            if (typeof mimeType == 'undefined') {
                var mimeType = 'text/plain';
            }
            var uri = 'data:' + mimeType + ';charset=utf-8;base64,' + this.base64EncodeUnicode(fileData);
            var downloadLink = document.createElement('a');
            downloadLink.href = uri;
            downloadLink.download = fileName;
            document.body.appendChild(downloadLink);
            downloadLink.click();
            document.body.removeChild(downloadLink);
        }

        /**
         * Converts a CSV record to an array, using the character indicated as the column separator.
         * @param {string}   csvData - CSV file data.
         * @param {number}   numberOfHeaderLines - Number of header lines and column descriptors to ignore.
         * @param {string}   recordSeparator - The separator characters.
         * @param {boolean}  allowRepeatChar - The separator character can be repeated (for formatting).
         * @param {boolean}  doEval - Run core.eval before adding the column to the record.
         * @return {array}   The array containing the parts of the CSV or NULL if the CSV record is not well formed.
         */
         this.parseCSV = function(csvData, numberOfHeaderLines, recordSeparator, allowRepeatChar, doEval)
         {
            if (typeof csvData != 'undefined') {
                var fileLines = core.split(csvData, '\r\n');
                var csvArray = [];
                for (i = numberOfHeaderLines; i < fileLines.length; i++) {
                    var record = core.splitCSV(fileLines[i], recordSeparator, allowRepeatChar, doEval);
                    csvArray.push(record);
                }
                return csvArray;
            } else {
                throw new Error('Invalid argument for function loadCSV. Argument must be a string.');
            }
         }

        /**
         * Displays a message in the console.
         * @param {string}  text - Text to display.
         */
        this.log = function(text)
        {
            console.log(text);
        }

        /**
         * Loads a CSV file and converts it to an array, using the character indicated as the column separator.
         * @param {string}   inputFile - CSV file.
         * @param {number}   numberOfHeaderLines - Number of header lines and column descriptors to ignore.
         * @param {string}   recordSeparator - The separator characters.
         * @param {boolean}  allowRepeatChar - The separator character can be repeated (for formatting).
         * @param {boolean}  doEval - Run core.eval before adding the column to the record.
         * @return {array}   The array containing the parts of the CSV or NULL if the CSV record is not well formed.
         */
        this.loadCSV = function(inputFile, numberOfHeaderLines, recordSeparator, allowRepeatChar, doEval)
        {
            if (typeof process != 'undefined') {
                var fs = require('fs');
                var readTextFile = fs.readFileSync;

                function getXml(data) {
                    compiledCode.xml += data;
                }

                function read(input) {
                    if (/^{.*}$/.test(input)) {
                        return input.substring(1, input.length - 1);
                    } else {
                        var content = readTextFile(input, 'utf-8');
                        return content.length > 0 && content.charCodeAt(0) == 0xFEFF ? content.substring(1) : content;
                    }
                }

                if (typeof inputFile != 'undefined') {
                    var fileContents = read(String(inputFile));
                    return this.parseCSV(fileContents, numberOfHeaderLines, recordSeparator, allowRepeatChar, doEval);
                } else {
                    throw new Error('Invalid argument for function parseCSV. Argument must be a string.');
                }
            }
        }

        /**
         * Displays a message in the console.
         * @param {string}  text - Text to display.
         */
        this.print = function(text)
        {
            this.log(text);
        }

       /**
         * Displays a formated string based on format specifiers passed to the function.
         * @param {string}   fmt - A string containing format specifiers.
         * @param {object}   arguments - Objects to be formatted.
         * @return {string}  A formatted string based on format specifiers passed to the function.
         */
        this.printf = function(fmt)
        {
            this.log(string.sprintFormat(string.sprintfParse(fmt), arguments));
        }

        /**
         * Displays a message on the console and advances the cursor to the next line.
         * @param {string}  text - Text to display.
         */
        this.println = function(text)
        {
            this.log(text + '\r\n');
        }

        /**
         * Reads data from browser storage.
         * @param {object}  obj - Object to store data: {'key': value, 'key': value, ...}
         * @param {object}  callBack - Callback function to call after access to storage.
         * @return          Data from storage.
         */
        this.readDataFromStorage = function(obj, callBack) {
            for (key in obj) {
                if (typeof localStorage.getItem(key) != 'undefined') {
                    obj[key] = localStorage.getItem(key);
                } else {
                    obj[key] = {};
                }
            }
            if (typeof callBack != 'undefined') {
                callBack();
            }
        }

        /**
         * Imports a native module.
         * @param {string}   moduleName - Module name.
         * @return {object}  The native module reference.
         */
        this.require = function(moduleName)
        {
            var moduleReference;
            if (typeof process !== 'undefined') {
                var moduleReference = require(moduleName);
            }
            return moduleReference;
        }

        /**
         * Displays a message in a dialog box asking for confirmation.
         * @param {string}   text - Text to display.
         * @return {string}  User choice.
         */
        this.showConfirmDialog = function(text)
        {
            return confirm(text);
        }

        /**
         * Displays a message in a dialog box asking you to enter text.
         * @param {string}   text - Text to display.
         * @param {string}   defaultText - Default text to display in the text box.
         * @return {string}  User-typed text.
         */
        this.showInputDialog = function(text, defaultText = '')
        {
            return prompt(text, defaultText);
        }

        /**
         * Displays a message in a dialog box.
         * @param {string}  text - Text to display.
         */
        this.showMessageDialog = function(text)
        {
            alert(text);
        }

        /**
         * Load a MaiaScript module.
         * @param {string}   inputFile - Module name.
         * @return {object}  The MaiaScript module loaded.
         */
        this.source = function(inputFile)
        {
            if (typeof process != 'undefined') {
                var fs = require('fs');
                var readTextFile = fs.readFileSync;

                function getXml(data) {
                    compiledCode.xml += data;
                }

                function read(input) {
                    if (/^{.*}$/.test(input)) {
                        return input.substring(1, input.length - 1);
                    } else {
                        var content = readTextFile(input, 'utf-8');
                        return content.length > 0 && content.charCodeAt(0) == 0xFEFF ? content.substring(1) : content;
                    }
                }

                if (typeof inputFile != 'undefined') {
                    var code = read(String(inputFile));
                    core.eval(code, global);
                } else {
                    throw new Error('Invalid argument for function source. Argument must be a string.');
                }
            }
        }

        /**
         * Upload a file.
         * @param {object}    fileObject - File data structure.
         * @param {function}  callBack - callback to be called when the file is loaded.
         * @return            The file is uploaded.
         */
        this.uploadFile = function(fileObject, callBack) {
            var input = document.createElement('input');
            input.type = 'file';
            input.onchange = e => {
                var file = e.target.files[0];
                fileObject.fullFileName = file.name;
                fileObject.fileName = fileObject.fullFileName.split('.').shift();
                fileObject.fileExtension = fileObject.fullFileName.split('.').pop();
                var reader = new FileReader();
                reader.readAsText(file,'UTF-8');
                reader.onload = readerEvent => {
                    fileObject.fileData = readerEvent.target.result;
                    if (typeof callBack != 'undefined') {
                        callBack(fileObject);
                    }
                }
            }
            input.click();
        }

        /**
         * Writes data to storage.
         * @param {object}  obj - Object to store data: {'key': value, 'key': value, ...}
         * @param {object}  callBack - Callback function to call after access to storage.
         * @return          Data written to storage.
         */
        this.writeDataToStorage = function(obj, callBack) {
            for (key in obj) {
                if (typeof obj[key] != 'undefined') {
                    localStorage.setItem(key, obj[key]);
                } else {
                    localStorage.setItem(key, {});
                }
            }
            if (typeof callBack != 'undefined') {
                callBack();
            }
        }
    }

    system = new System();