"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DatasetAnalysis = exports.DatasetQuery = void 0;
const lodash_1 = __importDefault(require("lodash"));
const Logging_1 = require("./Logging");
// A query, meant to filter a dataset (of some type) and return a new dataset
class DatasetQuery {
    constructor(dataRecords, options) {
        var _a, _b;
        this.dataset = dataRecords;
        // calculate TickInterval by taking the difference in ticks between the first 2 ticks of the dataset
        this.calculateTickInterval();
        this.labelFilter = options === null || options === void 0 ? void 0 : options.labelFilter;
        this.valueField = options === null || options === void 0 ? void 0 : options.valueField;
        this.smoothing = options === null || options === void 0 ? void 0 : options.smoothing;
        this.excludeEmpty = options === null || options === void 0 ? void 0 : options.excludeEmpty;
        this.startTick = (_a = options === null || options === void 0 ? void 0 : options.startTick) !== null && _a !== void 0 ? _a : this.findStartTick(this.dataset);
        this.endTick = (_b = options === null || options === void 0 ? void 0 : options.endTick) !== null && _b !== void 0 ? _b : this.findEndTick(this.dataset);
        if ((options === null || options === void 0 ? void 0 : options.startTick) || (options === null || options === void 0 ? void 0 : options.endTick))
            this.dataset = this.trimToFilter(this.dataset);
        if (this.labelFilter || this.valueField || this.excludeEmpty)
            this.dataset = this.applyFilter(this.dataset);
    }
    calculateTickInterval() {
        // uses  built in  dataset on construction
        // dataset can contain many records for 1 tick - need to find the first 2 unique tick values
        // then subtract to find the interval
        let firstTick = lodash_1.default.minBy(this.dataset, 'tick').tick;
        let secondTick = lodash_1.default.minBy(lodash_1.default.filter(this.dataset, (o) => {
            return o.tick != firstTick;
        }), 'tick').tick;
        this.tickInterval = secondTick - firstTick;
        return this.tickInterval;
    }
    uniqueLabels(dataset = this.dataset) {
        return lodash_1.default.uniq(lodash_1.default.map(dataset, 'label'));
    }
    groupByLabel(dataset = this.dataset) {
        return lodash_1.default.groupBy(dataset, (o) => {
            return o.label;
        });
    }
    groupByTickChunk(dataset, tickChunkSize) {
        // take the math.floor() value and multiply by tickChunkSize to get the start tick
        return lodash_1.default.groupBy(dataset, (o) => {
            return Math.floor(o.tick / tickChunkSize) * tickChunkSize;
        });
    }
    summaryByLabel(dataset = this.dataset) {
        // this will also require the 'valueField' to be set, so we know which field to target
        // this will return a dictionary of labels, with the valueField as the value field used
        // instead of returning the records themselves, find the min, max, avg, count, stddev
        if (!this.valueField)
            throw new Error('Cannot create summary by label! Value field is null - don\'t know what field to use for value');
        let ret = {};
        let grouped = this.groupByLabel(dataset);
        for (let label in grouped) {
            let values = lodash_1.default.map(grouped[label], this.valueField);
            // desc should describe what the data represents - so "'valueField' of "=
            ret[label] = {
                description: `${this.valueField} of ${label} per ${this.tickInterval} ticks`,
                valueField: this.valueField,
                sum: lodash_1.default.sum(values),
                min: lodash_1.default.min(values),
                max: lodash_1.default.max(values),
                avg: lodash_1.default.sum(values) / values.length,
                count: values.length,
                // calculate standard deviation
                stddev: DatasetAnalysis.calculateStdDev(values)
            };
        }
        return ret;
    }
    summaryByTickChunk(dataset = this.dataset, tickChunkSize = 600, convertToSeconds = true) {
        Logging_1.Logging.log('info', `Creating summary by tick chunk of ${tickChunkSize} ticks for dataset of ${dataset.length} records`);
        // this will also require the 'valueField' to be set, so we know which field to target
        // this will return a dictionary of labels, with the valueField as the value field used
        // instead of returning the records themselves, find the min, max, avg, count, stddev
        if (!this.valueField)
            throw new Error('Cannot create summary by tick chunk! Value field is null - don\'t know what field to use for value');
        // Also going to require label filter here - otherwise, data doesnt mean much summarized
        if (!this.labelFilter)
            throw new Error('Cannot create summary by tick chunk! Label filter is null - don\'t know what label to use for filtering');
        // #TODO implement changes here to convert to seconds if needed. build desc separately first
        let ret = {};
        let grouped = this.groupByTickChunk(dataset, tickChunkSize);
        // Could translate this to 'items' per second ((value * tickInterval) / 60)
        for (let label in grouped) {
            let values = lodash_1.default.map(grouped[label], this.valueField);
            // desc should describe what the data represents - so "'valueField' of "=
            ret[label] = {
                description: `${this.valueField}` + (this.labelFilter ? ` of ${this.labelFilter}` : '') + ` from ${label} to ${Number.parseInt(label) + tickChunkSize} per ${this.tickInterval} ticks`,
                valueField: this.valueField,
                sum: lodash_1.default.sum(values),
                min: lodash_1.default.min(values),
                max: lodash_1.default.max(values),
                avg: lodash_1.default.sum(values) / values.length,
                count: values.length,
                // calculate standard deviation
                stddev: DatasetAnalysis.calculateStdDev(values)
            };
        }
        return ret;
    }
    // Trims the dataset to be within startTick and endTick, if set
    // if ONE not set, fill as needed with min/max. If BOTH not set, do nothing, return dataset
    trimToFilter(dataset) {
        if (!this.startTick)
            this.startTick = this.findStartTick(dataset);
        if (!this.endTick)
            this.endTick = this.findEndTick(dataset);
        return lodash_1.default.filter(dataset, (o) => {
            return o.tick >= this.startTick && o.tick <= this.endTick;
        });
    }
    /*
    * Using labelField, valueField, smoothing, and excludeEmpty, filter the dataset as follows
    * 1. if labelField is set, filter out all ticks that 'label' do not match the labelField
    * 2. if valueField is set and excludeEmpty is true, filter out all ticks that tick['valueField'] is 0
    * */
    applyFilter(dataset) {
        return lodash_1.default.filter(dataset, (t) => {
            if (this.labelFilter && t.label != this.labelFilter)
                return false;
            if (this.valueField && this.excludeEmpty && t[this.valueField] == 0)
                return false;
            return true;
        });
    }
    /*
    * Queries the dataset, finding the first tick that matches the labelField
    * Does not filter on ticks, but does filter on the rest (empty, label, etc etc)
    * */
    findStartTick(dataset) {
        return lodash_1.default.minBy(dataset, (t) => {
            return t.tick;
        }).tick;
    }
    /*
    * Queries the dataset, finding the last tick that matches the labelField
     */
    findEndTick(dataset) {
        return lodash_1.default.maxBy(dataset, (t) => {
            return t.tick;
        }).tick;
    }
}
exports.DatasetQuery = DatasetQuery;
class DatasetAnalysis {
    static calculateStdDev(array) {
        if (array.length === 0)
            return 0;
        let sum = array.reduce((a, b) => a + b, 0);
        let avg = sum / array.length;
        let sqDiffSum = array.map(n => Math.pow(n - avg, 2))
            .reduce((a, b) => a + b, 0);
        return Math.sqrt(sqDiffSum / array.length);
    }
    static createPlotDataFromDataset(dataset, startTick, endTick, labelField, valueField, smoothing = 1, excludeEmpty = false) {
        Logging_1.Logging.log('info', `Creating plot data from dataset of ${dataset.length} records`);
        // takes a subset of the dataset, returning a new dataset with only the specified range
        // if data uses cons/prod, those fields are used, otherwise Count is looked for. Else, throws error
        if (dataset.length == 0)
            return [];
        if (!valueField)
            throw new Error("Cannot create plot data! Value field is null - don't know what field to use for 'y' coordinate");
        // a labelField is not 'required' - if not provided, results may contain mixed labels (items)
        let ret = [];
        let tempDs = new DatasetQuery(dataset, {
            startTick,
            endTick,
            labelFilter: labelField,
            valueField,
            smoothing,
            excludeEmpty
        });
        // convert into our x,y format, using valueField as the y value
        ret = lodash_1.default.map(tempDs, (o) => {
            return {
                x: o.tick,
                y: o[valueField],
                label: o.label,
                tag: valueField
            };
        });
        // smooth our data if need be
        if (smoothing > 1) {
            ret = DatasetAnalysis.smoothData(ret, smoothing);
        }
        return ret;
    }
    static smoothData(data, smooth) {
        Logging_1.Logging.log('info', `Smoothing ${data.length} data points with smooth factor of ${smooth}`);
        let smoothedData = [];
        if (smooth >= 1 && data.length >= smooth) {
            for (let i = 0; i < data.length - smooth + 1; i++) {
                const windowData = data.slice(i, i + smooth);
                const sum = windowData.reduce((acc, cur) => acc + cur.y, 0);
                const average = sum / smooth;
                smoothedData.push({
                    x: data[i + Math.floor(smooth / 2)].x,
                    y: average,
                    label: data[i + Math.floor(smooth / 2)].label,
                    tag: data[i + Math.floor(smooth / 2)].tag
                });
            }
            Logging_1.Logging.log('verbose', `Smoothed ${data.length} down to ${smoothedData.length}`);
        }
        else
            smoothedData = data;
        return smoothedData;
    }
    static createSubsetOfDataset(dataset, startTick, endTick, labelField = null, excludeEmpty = false) {
        return lodash_1.default.filter(dataset, (o) => {
            return o.tick >= startTick && o.tick <= endTick && ((labelField && o.label == labelField) || !labelField);
        });
    }
    static createSummaryOfGenericDataset(result, startTick = undefined, endTick = undefined) {
        var _a, _b;
        Logging_1.Logging.log('info', `Creating summary of generic dataset of ${result.data.length} records`);
        if (!result)
            throw new Error('Cannot create summary of generic dataset! Input is null');
        let tempDs = result.data;
        if (startTick != undefined && endTick != undefined) {
            tempDs = DatasetAnalysis.createSubsetOfDataset(tempDs, startTick, endTick);
        }
        result.data = tempDs;
        result.minTick = (_a = lodash_1.default.minBy(result.data, 'tick')) === null || _a === void 0 ? void 0 : _a.tick;
        result.maxTick = (_b = lodash_1.default.maxBy(result.data, 'tick')) === null || _b === void 0 ? void 0 : _b.tick;
        result.labels = lodash_1.default.uniq(lodash_1.default.map(result.data, 'label'));
        return result;
    }
    static createSummaryOfItemDataset(result, startTick = undefined, endTick = undefined) {
        Logging_1.Logging.log('info', `Creating summary of item dataset of ${result.data.length} records`);
        // passed in is the 'empty' version, with only data and trial defined
        // we now need to do all the math here
        if (!result)
            throw new Error('Cannot create summary of item dataset! Input is null');
        // filter out data (ticks)
        let tempDs = result.data;
        if (startTick != undefined && endTick != undefined) {
            tempDs = DatasetAnalysis.createSubsetOfDataset(tempDs, startTick, endTick);
        }
        result.data = tempDs;
        // create summary data - generics first, then our own
        result = this.createSummaryOfGenericDataset(result);
        //
        // // prepare maps
        // result.averageConsByLabel = {};
        // result.averageProdByLabel = {};
        //
        // // for each label, we need to calculate the average consumption and production
        // let secondsLength = result.trial.length / (60)
        // for (let l of result.labels) {
        //     let lData = _.filter(result.data, (o: any) => {
        //         return o.label == l
        //     })
        //     let totalCons = _.sumBy(lData, 'cons');
        //     let totalProd = _.sumBy(lData, 'prod');
        //
        //     result.averageConsByLabel[l] = totalCons / secondsLength;
        //     result.averageProdByLabel[l] = totalProd / secondsLength;
        // }
        return result;
    }
    static createSummaryOfElectricDataset(result, startTick = undefined, endTick = undefined) {
        Logging_1.Logging.log('info', `Creating summary of electric dataset of ${result.data.length} records`);
        // passed in is the 'empty' version, with only data and trial defined
        // we now need to do all the math here
        if (!result)
            throw new Error('Cannot create summary of electric dataset! Input is null');
        // filter out data (ticks)
        let tempDs = result.data;
        if (startTick != undefined && endTick != undefined) {
            tempDs = DatasetAnalysis.createSubsetOfDataset(tempDs, startTick, endTick);
        }
        result.data = tempDs;
        // need to implement more here for summary data - for now, just subsets if provided
        return result;
    }
    static createSummaryOfCircuitDataset(result, startTick = undefined, endTick = undefined) {
        Logging_1.Logging.log('info', `Creating summary of circuit dataset of ${result.data.length} records`);
        // passed in is the 'empty' version, with only data and trial defined
        // we now need to do all the math here
        if (!result)
            throw new Error('Cannot create summary of circuit dataset! Input is null');
        // filter out data (ticks)
        let tempDs = result.data;
        if (startTick != undefined && endTick != undefined) {
            tempDs = DatasetAnalysis.createSubsetOfDataset(tempDs, startTick, endTick);
        }
        result.data = tempDs;
        // need to implement more here for summary data - for now, just subsets if provided
        return result;
    }
    static createSummaryOfPollutionDataset(result, startTick = undefined, endTick = undefined) {
        Logging_1.Logging.log('info', `Creating summary of pollution dataset of ${result.data.length} records`);
        // passed in is the 'empty' version, with only data and trial defined
        // we now need to do all the math here
        if (!result)
            throw new Error('Cannot create summary of pollution dataset! Input is null');
        // filter out data (ticks)
        let tempDs = result.data;
        if (startTick != undefined && endTick != undefined) {
            tempDs = DatasetAnalysis.createSubsetOfDataset(tempDs, startTick, endTick);
        }
        result.data = tempDs;
        // need to implement more here for summary data - for now, just subsets if provided
        return result;
    }
    static createSummaryOfSystemDataset(result, startTick = undefined, endTick = undefined) {
        Logging_1.Logging.log('info', `Creating summary of system dataset of ${result.data.length} records`);
        // passed in is the 'empty' version, with only data and trial defined
        // we now need to do all the math here
        if (!result)
            throw new Error('Cannot create summary of system dataset! Input is null');
        // filter out data (ticks)
        let tempDs = result.data;
        if (startTick != undefined && endTick != undefined) {
            tempDs = DatasetAnalysis.createSubsetOfDataset(tempDs, startTick, endTick);
        }
        result.data = tempDs;
        // need to implement more here for summary data - for now, just subsets if provided
        return result;
    }
}
exports.DatasetAnalysis = DatasetAnalysis;
//# sourceMappingURL=DatasetAnalysis.js.map