"use strict";
// -*- mode: typescript; indent-tabs-mode: nil; js-basic-offset: 4 -*-
//
// This file is part of Genie
//
// Copyright 2020 The Board of Trustees of the Leland Stanford Junior University
//
// 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.
//
// Author: Giovanni Campagna <gcampagn@cs.stanford.edu>
Object.defineProperty(exports, "__esModule", { value: true });
const thingtalk_1 = require("thingtalk");
// above MORE_SIZE, we set the "more" bit
const MORE_SIZE = 50;
// above PAGE_SIZE, we set the count but don't actually show the full list of results
const PAGE_SIZE = 10;
/**
 * Run the dialogue, executing ThingTalk and invoking the policy at the
 * right time.
 */
class InferenceStatementExecutor {
    constructor(engine) {
        this._engine = engine;
        this._schemas = this._engine.schemas;
    }
    _inferType(key, jsValue) {
        if (key === 'distance') // HACK
            return new thingtalk_1.Type.Measure('m');
        if (typeof jsValue === 'boolean')
            return thingtalk_1.Type.Boolean;
        if (typeof jsValue === 'string')
            return thingtalk_1.Type.String;
        if (typeof jsValue === 'number')
            return thingtalk_1.Type.Number;
        if (jsValue instanceof thingtalk_1.Builtin.Currency)
            return thingtalk_1.Type.Currency;
        if (jsValue instanceof thingtalk_1.Builtin.Entity)
            return new thingtalk_1.Type.Entity('');
        if (jsValue instanceof thingtalk_1.Builtin.Time)
            return thingtalk_1.Type.Time;
        if (jsValue instanceof Date)
            return thingtalk_1.Type.Date;
        if (Array.isArray(jsValue) && jsValue.length > 0)
            return new thingtalk_1.Type.Array(this._inferType(key, jsValue[0]));
        if (Array.isArray(jsValue))
            return new thingtalk_1.Type.Array(thingtalk_1.Type.Any);
        return thingtalk_1.Type.Any;
    }
    _outputTypeToSchema(outputType) {
        const [kind, fnamesplit] = outputType.split(':');
        let fname = fnamesplit;
        let ftype = 'query';
        if (fname.startsWith('action/')) {
            ftype = 'action';
            fname = fname.substring('action/'.length);
        }
        return this._schemas.getSchemaAndNames(kind, ftype, fname);
    }
    async _mapResult(outputType, outputValue) {
        const mappedResult = {};
        if (outputType === null) {
            // fallback
            for (const key in outputValue) {
                const jsValue = outputValue[key];
                mappedResult[key] = thingtalk_1.Ast.Value.fromJS(this._inferType(key, jsValue), jsValue);
            }
            return mappedResult;
        }
        if (outputType.indexOf('+') >= 0) {
            const types = outputType.split('+');
            outputType = types[types.length - 1];
        }
        const aggregation = /^([a-zA-Z]+)\(([^)]+)\)$/.exec(outputType);
        if (aggregation !== null) {
            let operator;
            [, operator, outputType] = aggregation;
            const field = Object.keys(outputValue)[0];
            const value = outputValue[field];
            if (operator === 'count') {
                mappedResult[field] = thingtalk_1.Ast.Value.fromJS(thingtalk_1.Type.Number, outputValue[field]);
                return mappedResult;
            }
            const schema = await this._outputTypeToSchema(outputType);
            const type = schema.getArgType(field) || this._inferType(field, value);
            mappedResult[field] = thingtalk_1.Ast.Value.fromJS(type, value);
        }
        else {
            const schema = await this._outputTypeToSchema(outputType);
            for (const key in outputValue) {
                const value = outputValue[key];
                const type = schema.getArgType(key) || this._inferType(key, value);
                if (value === null || value === undefined)
                    continue;
                if (type instanceof thingtalk_1.Type.Compound)
                    mappedResult[key] = this._mapCompound(key + '.', schema, value);
                else
                    mappedResult[key] = thingtalk_1.Ast.Value.fromJS(type, value);
            }
        }
        return mappedResult;
    }
    _mapCompound(prefix, schema, object) {
        const result = {};
        for (const key in object) {
            const value = object[key];
            const type = schema.getArgType(prefix + key) || this._inferType(prefix + key, value);
            if (value === null || value === undefined)
                continue;
            if (type instanceof thingtalk_1.Type.Compound)
                result[key] = this._mapCompound(prefix + key + '.', schema, object);
            else
                result[key] = thingtalk_1.Ast.Value.fromJS(type, value);
        }
        return new thingtalk_1.Ast.Value.Object(result);
    }
    async _iterateResults(app, into, intoRaw) {
        let count = 0;
        if (app === null)
            return [false, undefined, undefined];
        let errorCode, errorMessage;
        for await (const value of app.mainOutput) {
            if (count >= MORE_SIZE)
                return [true, errorCode, errorMessage];
            if (value instanceof Error) {
                const err = value;
                if (typeof err.code === 'string'
                    && !err.code.startsWith('E')) // error codes starting with E are reserved for system errors
                    errorCode = err.code;
                if (!errorMessage)
                    errorMessage = value.message;
            }
            else {
                const mapped = await this._mapResult(value.outputType, value.outputValue);
                into.push(new thingtalk_1.Ast.DialogueHistoryResultItem(null, mapped, value.outputValue));
                intoRaw.push([value.outputType, value.outputValue]);
                count++;
            }
        }
        // if we get here, we iterated all results from the app, so we can stop
        return [false, errorCode, errorMessage];
    }
    async executeStatement(stmt) {
        const program = new thingtalk_1.Ast.Program(null, [], [], [stmt]);
        const app = await this._engine.createApp(program);
        const results = [];
        const rawResults = [];
        const [more, errorCode, errorMessage] = await this._iterateResults(app, results, rawResults);
        const resultList = new thingtalk_1.Ast.DialogueHistoryResultList(null, results.slice(0, PAGE_SIZE), new thingtalk_1.Ast.Value.Number(results.length), more, (errorCode ? new thingtalk_1.Ast.Value.Enum(errorCode) :
            (errorMessage ? new thingtalk_1.Ast.Value.String(errorMessage) : null)));
        return [resultList, rawResults, undefined];
    }
}
exports.default = InferenceStatementExecutor;
//# sourceMappingURL=statement_executor.js.map