/*!
 * 
 * 4DView Pro library 0.0.0
 * 
 * Copyright(c) 4D SAS.  All rights reserved.
 * 
 * 4D (the "Software") and the corresponding source code remain
 * the exclusive property of 4D and/or its licensors and are protected by national
 * and/or international legislations.
 * 
 * This file is part of the source code of the Software provided under the relevant
 * 4D License Agreement available on http://www.4D.com/license whose compliance
 * constitutes a prerequisite to any use of this file and more generally of the
 * Software and the corresponding source code.
 * 
 */

class Utils {

    static get spread() {
        if (useRibbon)
            return designer.wrapper.spread;
        else
            return spread;
    }

    static get currentDocument() {
        return currentDocument;
    }

    static set currentDocument(doc) {
        currentDocument = doc;
    }

    static get currentSheet() {
        return Utils.spread.getActiveSheet();
    }

    static get currentCell() {
        let sheet = Utils.currentSheet;
        return {
            row: sheet.getActiveRowIndex(),
            column: sheet.getActiveColumnIndex()
        };
    }
}

Utils.contextMenu = undefined;

Utils.diagnostics = {
    events: []
};

Utils.logEvent = function ({ type, data }) {
    if (Utils.diagnostics.events.length >= 500) {
        Utils.diagnostics.events = [];
    }

    Utils.diagnostics.events.push({
        time: new Date(),
        type,
        data
    });
}

Utils.adjustFormat = function (format) {
    let propertyName = '';

    if (format === '_fullDateTimePattern_') {
        propertyName = 'fullDateTimePattern';
    } else if (format === '_sortableDateTimePattern_') {
        propertyName = 'sortableDateTimePattern';
    } else if (format === '_universalSortableDateTimePattern_') {
        propertyName = 'universalSortableDateTimePattern';
    } else if (format === '_longDatePattern_') {
        propertyName = 'longDatePattern';
    } else if (format === '_monthDayPattern_') {
        propertyName = 'monthDayPattern';
    } else if (format === '_shortDatePattern_') {
        propertyName = 'shortDatePattern';
    } else if (format === '_yearMonthPattern_') {
        propertyName = 'yearMonthPattern';
    } else if (format === '_longTimePattern_') {
        propertyName = 'longTimePattern';
    } else if (format === '_shortTimePattern_') {
        propertyName = 'shortTimePattern';
    }

    if (propertyName != '') {
        let culture = GC.Spread.Common.CultureManager.getCultureInfo();
        format = culture.DateTimeFormat[propertyName];
    }

    return format;
}

/**
 * http://help.grapecity.com/spread/SpreadJSWeb/webframe.html#resulterror.html
 */
Utils.errors = {
    invalidCellReference: GC.Spread.CalcEngine.CalcError.parse("#REF!"),
    wrongTypeOfArgument: GC.Spread.CalcEngine.CalcError.parse("#VALUE!"),
    numberRelated: GC.Spread.CalcEngine.CalcError.parse("#NUM!"),
    notAny: GC.Spread.CalcEngine.CalcError.parse("#N/A"),
    divisionByZero: GC.Spread.CalcEngine.CalcError.parse("#DIV/0!")
};

Utils.b64ToBlob = function (b64string) {
    let byteString = atob(b64string);

    // write the bytes of the string to an ArrayBuffer
    let ia = new Uint8Array(byteString.length);
    for (let i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    // write the ArrayBuffer to a blob, and you're done
    let blob = new Blob([ia.buffer], { type: "application/octet-stream" });

    return blob;
};

// return the sheet corresponding to the sheet index, 
// check boundaries and return current sheet if sheet index = -1 or is undefined
Utils.resolveSheet = function (sheetIndex) {
    if (((sheetIndex == undefined) || (sheetIndex == -1)) && (Utils.spread.getSheetCount() > 0))
        return Utils.currentSheet;
    else if ((sheetIndex >= 0) && (sheetIndex < Utils.spread.getSheetCount()))
        return Utils.spread.getSheet(sheetIndex);
    else
        return null;
}

// resolve a given sheet index to a sheet index that exists, and resolve sheet index = -1 to current sheet index
Utils.resolveSheetIndex = function (sheetIndex) {
    let trueSheetIndex = null;
    if (typeof (sheetIndex) === 'number') {
        if (sheetIndex == -1) {
            trueSheetIndex = Utils.spread.getSheetIndex(Utils.currentSheet.name());
        } else if ((sheetIndex >= 0) && (sheetIndex < Utils.spread.getSheetCount())) {
            trueSheetIndex = sheetIndex;
        }
    }
    return trueSheetIndex;
}

// same as resolveSheet but returns workbook when sheet Index = -2 or is undefined
Utils.resolveSheetOrWorkbook = function (sheetIndex) {
    let instance = null;
    if ((sheetIndex == undefined) || (sheetIndex == -2)) {
        instance = Utils.spread;
    } else {
        instance = Utils.resolveSheet(sheetIndex);
    }
    return instance;
}


// resolve 4D View Pro ranges
Utils.getRanges = function (range, instancesArray) {

    let sheet = Utils.resolveSheet(range.sheet);

    if (sheet != null) {

        let hasRow = ('row' in range) && (typeof (range.row) === 'number');
        let hasColumn = ('column' in range) && (typeof (range.column) === 'number');
        let hasRowCount = ('rowCount' in range) && (typeof (range.rowCount) === 'number');
        let hasColumnCount = ('columnCount' in range) && (typeof (range.columnCount) === 'number');
        let hasName = ("name" in range) && (typeof (range.name) === 'string');

        if (hasName) {
            // === name
            let n = sheet.getCustomName(range.name);
            if (n != null) {
                let expression = GC.Spread.Sheets.CalcEngine.expressionToFormula(sheet, n.getExpression());
                let rangesInExpression = GC.Spread.Sheets.CalcEngine.formulaToRanges(sheet, expression);
                if ((rangesInExpression != null) && (rangesInExpression.constructor === Array)) {
                    rangesInExpression.forEach(rangeInExpression => {
                        let i = Utils.spread.getSheetIndex(rangeInExpression.sheetName);
                        let s = Utils.spread.getSheet(i);
                        if (s != null) {
                            rangeInExpression.ranges.forEach(rangeElement => {
                                instancesArray.push({
                                    'sheet': s,
                                    'row': (rangeElement.row == -1) ? 0 : rangeElement.row,
                                    'column': (rangeElement.col == -1) ? 0 : rangeElement.col,
                                    'rowCount': (rangeElement.rowCount == -1) ? 1048576 : rangeElement.rowCount,
                                    'columnCount': (rangeElement.colCount == -1) ? 1048576 : rangeElement.colCount
                                });

                            });
                        }
                    });
                }
            }
        }
        else if (hasRow && hasColumn && !hasRowCount && !hasColumnCount) {
            // === cell
            instancesArray.push(
                {
                    'sheet': sheet,
                    'row': range.row,
                    'column': range.column,
                    'rowCount': 1,
                    'columnCount': 1
                });
        } else if (hasRow && hasColumn && hasRowCount && hasColumnCount) {
            // === range
            instancesArray.push(
                {
                    'sheet': sheet,
                    'row': range.row,
                    'column': range.column,
                    'rowCount': range.rowCount,
                    'columnCount': range.columnCount
                });
        } else if (hasRow && hasRowCount && !hasColumn && !hasColumnCount) {
            // === row
            instancesArray.push(
                {
                    'sheet': sheet,
                    'row': range.row,
                    'column': 0,
                    'rowCount': range.rowCount,
                    'columnCount': 1048576
                });
        } else if (hasColumn && hasColumnCount && !hasRow && !hasRowCount) {
            // === column
            instancesArray.push(
                {
                    'sheet': sheet,
                    'row': 0,
                    'column': range.column,
                    'rowCount': 1048576,
                    'columnCount': range.columnCount
                });
        } else if (!hasColumn && !hasColumnCount && !hasRow && !hasRowCount) {
            // === all
            instancesArray.push(
                {
                    'sheet': sheet,
                    'row': 0,
                    'column': 0,
                    'rowCount': sheet.getRowCount(),
                    'columnCount': sheet.getColumnCount()
                });
        }
    }
}

Utils.getFirstRange = function (ranges) {

    let ret = null;

    if ((ranges.constructor === Array)
        && (ranges.length > 0)) {

        let instancesArray = [];

        Utils.getRanges(ranges[0], instancesArray);

        if (instancesArray.length > 0) {
            ret = instancesArray[0];
        }
    }

    return ret;
}


/**
 * Copied from SJS: _indexToLetters (Todo: Any impact on culture change ?)
 * @param {number} index Column index
 */
Utils.indexToLetters = function (index) {
    let t = 'A';
    let aCode = t.charCodeAt(0);
    let sb = '';
    for (; index > 0; index = parseInt((index - 1) / 26), 10) {
        let n = (index - 1) % 26;
        sb = String.fromCharCode(aCode + n) + sb;
    }
    return sb;
};

Utils.addCommand = function (name, handler) {
    Utils.logEvent({ type: 'addCommand', data: { name, handler } });

    if (Commands[name]) {
        console.error(`Duplicate command name: ${name} for handler:`, handler);
        return false;
    }

    Commands[name] = handler;

    return true;
};

Utils.getCommand = function (name) {
    return Commands[name];
};

Utils.send4DEvent = function send4DEvent(event, data) {
    Utils.logEvent({ type: 'send4DEvent', data: { event, data } });

    $4d._vp_sendEvent(event, data);
};

Utils.defineGlobalCustomFunction = function (name, handler, type) {
    Utils.logEvent({ type: 'defineGlobalCustomFunction', data: { name, handler, type } });

    GC.Spread.CalcEngine.Functions.defineGlobalCustomFunction(name, handler);
};

var vp_timerStarted = false;
var vp_timer = null;

function vp_startOptimizer() {

    if (vp_timerStarted) {
        clearTimeout(vp_timer);
    } else {
        Utils.spread.suspendPaint();
    }

    vp_timerStarted = true;

    vp_timer = setTimeout(function () {
        vp_timerStarted = false;
        Utils.spread.resumePaint();
        Utils.send4DEvent('onDataChange');
    }, 200);
}
