Source: ReactViews/Custom/Chart/downloadHrefWorker.js

/* global onmessage:true */
import defined from 'terriajs-cesium/Source/Core/defined';

import DataUri from '../../../Core/DataUri';
import sortedIndices from '../../../Core/sortedIndices';

/**
 * Create combined arrays from arrays of column values, eg. [[values1, values2, values3], [values4, values5]].
 * The first columns of each array must be of the same type (in the above example, values1 and values4).
 * These are combined and sorted into a single column.
 * Then the subsequent columns are added, filling with null where missing. (This could be an option in future.)
 * Eg. if the values of each col are: values1=[1,3]; values2=[10,30]; values3=[100,300]; values4=[1,2]; values5=[-1,-2];
 * then the resulting array of column values are, in order, [1,2,3]; [10,null,30]; [100,null,300]; [-1,-2,null].
 * @param {Array[]} valueArrays See description above.
 * @return {Array[]} The synthesized values which could be passed to a table structure.
 */
function combineValueArrays(valueArrays) {
    if (!defined(valueArrays) || valueArrays.length < 1) {
        return;
    }
    let combinedValueArrays = [];
    // Start by copying the first set of columns into the result.
    const firstArray = valueArrays[0];
    for (let j = 0; j < firstArray.length; j++) {
        const values = firstArray[j];
        combinedValueArrays.push(values.slice());
    }
    // Then add the subsequent sets of x-columns to the end of the first result column,
    // add nulls to the end of the other existing columns,
    // add nulls to the start of the new columns,
    // and add them to the end of the result.
    for (let i = 1; i < valueArrays.length; i++) {
        const currentValueArray = valueArrays[i];
        const currentFirstArray = currentValueArray[0];
        const preExistingValuesLength = combinedValueArrays[0].length;
        combinedValueArrays[0] = combinedValueArrays[0].concat(currentFirstArray);
        const empty1 = new Array(currentFirstArray.length); // elements are undefined.
        for (let k = 1; k < combinedValueArrays.length; k++) {
            combinedValueArrays[k] = combinedValueArrays[k].concat(empty1);
        }
        const empty2 = new Array(preExistingValuesLength); // elements are undefined.
        for (let j = 1; j < currentValueArray.length; j++) {
            const values = currentValueArray[j];
            combinedValueArrays.push(empty2.concat(values));
        }
    }

    // Sort by the first column.
    combinedValueArrays = sortByFirst(combinedValueArrays);
    combinedValueArrays = combineRepeated(combinedValueArrays);

    return combinedValueArrays;
}

/**
 * Eg. sortByFirst([['b', 'a', 'c'], [1, 2, 3]]) = [['a', 'b', 'c'], [2, 1, 3]].
 * @param  {Array[]} valueArrays The array of arrays of values to sort.
 * @return {Array[]} The values sorted by the first column.
 */
function sortByFirst(valueArrays) {
    const firstValues = valueArrays[0];
    const indices = sortedIndices(firstValues);
    return valueArrays.map(function(values) {
        return indices.map(function(sortedIndex) { return values[sortedIndex]; });
    });
}

/**
 * @param  {Array[]} sortedJulianDateOrValueArrays The array of arrays of values to combine. These must be sortedByFirst. Dates must be JulianDates.
 * @param  {Integer} [firstColumnType] Eg. VarType.TIME.
 * @return {Array[]} The values, with any repeats in the first column combined into one. Dates are converted to ISO8601 string representation.
 *
 * Eg.
 * var x = [['a', 'b', 'b', 'c'], [1, 2, undefined, 3], [4, undefined, 5, undefined]];
 * combineRepeated(x);
 * # x is [['a', 'b', 'c'], [1, 2, 3], [4, 5, undefined]].
 */
function combineRepeated(sortedValueArrays) {
    const result = new Array(sortedValueArrays.length);
    for (let i = 0; i < result.length; i++) {
        result[i] = [sortedValueArrays[i][0]];
    }
    for (let j = 1; j < sortedValueArrays[0].length; j++) {
        if (sortedValueArrays[0][j] === sortedValueArrays[0][j - 1]) {
            const currentIndex = result[0].length - 1;
            for (let i = 0; i < result.length; i++) {
                if (result[i][currentIndex] === undefined) {
                    result[i][currentIndex] = sortedValueArrays[i][j];
                }
            }
        } else {
            for (let i = 0; i < result.length; i++) {
                result[i].push(sortedValueArrays[i][j]);
            }
        }
    }
    return result;
}

/**
 * Convert an array of column values, with column names, to an array of row values.
 * @param  {Array[]} columnValueArrays Array of column values, eg. [[1,2,3], [4,5,6]].
 * @param  {String[]} columnNames Array of column names, eg ['x', 'y'].
 * @return {Array[]} Array of rows, starting with the column names, eg. [['x', 'y'], [1, 4], [2, 5], [3, 6]].
 */
function toArrayOfRows(columnValueArrays, columnNames) {
    if (columnValueArrays.length < 1) {
        return;
    }
    const rows = columnValueArrays[0].map(function(value0, rowIndex) {
        return columnValueArrays.map(function(values) {
            return values[rowIndex];
        });
    });
    rows.unshift(columnNames);
    return rows;
}

onmessage = function(event) {
    const valueArrays = event.data.values.map(valuesArray => valuesArray.map(values => Array.prototype.slice.call(values))); // Convert from typed arrays.
    const nameArrays = event.data.names;
    const combinedValues = combineValueArrays(valueArrays);
    const rows = toArrayOfRows(combinedValues, nameArrays);
    const joinedRows = rows.map(function(row) { return row.join(','); });
    const csvString = joinedRows.join('\n');
    const href = DataUri.make('csv', csvString);
    postMessage(href);
};