"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>
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.addInitialDontCare = exports.startNewRequest = exports.initialRequest = void 0;
const assert_1 = __importDefault(require("assert"));
const thingtalk_1 = require("thingtalk");
const C = __importStar(require("../ast_manip"));
const state_manip_1 = require("../state_manip");
const refinement_helpers_1 = require("./refinement-helpers");
function adjustStatementsForInitialRequest(loader, expr) {
    // TODO implement rules (streams)
    if (expr.first.schema.functionType === 'stream')
        return null;
    if (!C.checkValidQuery(expr))
        return null;
    const newStatements = [];
    if (expr.expressions.length > 1) {
        // query + action
        // split into two statements, one getting the data, and the other using it
        assert_1.default(expr.expressions.length === 2);
        const table = expr.expressions[0];
        const action = expr.expressions[1];
        assert_1.default(action instanceof thingtalk_1.Ast.InvocationExpression);
        const confirm = loader.ttUtils.normalizeConfirmAnnotation(action.invocation.schema);
        if (confirm === 'auto') {
            const compoundStmt = new thingtalk_1.Ast.ExpressionStatement(null, expr);
            newStatements.push(compoundStmt);
        }
        else {
            const queryStmt = new thingtalk_1.Ast.ExpressionStatement(null, table);
            newStatements.push(queryStmt);
            const newAction = action.clone();
            const in_params = newAction.invocation.in_params;
            for (const in_param of in_params) {
                if (in_param.value instanceof thingtalk_1.Ast.EventValue) // TODO
                    return null;
                if (!(in_param.value instanceof thingtalk_1.Ast.VarRefValue))
                    continue;
                if (in_param.value.name.startsWith('__const_'))
                    continue;
                // TODO: parameter passing for non ID parameter
                if (in_param.value.name !== 'id')
                    return null;
                // parameter passing
                // FIXME we need a new ThingTalk value type...
                in_param.value = new thingtalk_1.Ast.Value.Undefined(true);
            }
            const actionStmt = new thingtalk_1.Ast.ExpressionStatement(null, newAction);
            newStatements.push(actionStmt);
        }
    }
    else if (expr.first.schema.functionType === 'action') {
        // action only
        // add a query, if the action refers to an ID entity
        const action = expr.first;
        assert_1.default(action instanceof thingtalk_1.Ast.InvocationExpression);
        // for "confirm=auto", the query is added to a compound command
        // and for "confirm=display_result", the query is added as a separate statement
        // this is necessary to be consistent and avoid ambiguity between
        // "play some song" (empty parameter) and "play songs" (parameter replaced with bare table)
        //
        // so for example, "book some restaurant" becomes
        // ```
        // $dialogue @org.thingpedia.dialogue.transaction.execute;
        // now => @uk.ac.cam.multiwoz.Restaurant.Restaurant() => notify;
        // now => @uk.ac.cam.multiwoz.Restaurant.make_booking() => notify;
        // ```
        //
        // and "play some song" becomes:
        // ```
        // $dialogue @org.thingpedia.dialogue.transaction.execute;
        // now => @com.spotify.song() => @com.spotify.play_song(song=id);
        // ```
        // first, check that we did not already have an entity parameter
        // (we need to reject that)
        let hasIDArg = false;
        for (const param of action.invocation.in_params) {
            const type = action.invocation.schema.getArgType(param.name);
            if (!(type instanceof thingtalk_1.Type.Entity) || !loader.idQueries.has(type.type))
                continue;
            hasIDArg = true;
            if (param.value.isEntity)
                return null;
        }
        if (!hasIDArg) {
            newStatements.push(new thingtalk_1.Ast.ExpressionStatement(null, expr));
            return newStatements.map(C.adjustDefaultParameters);
        }
        const confirm = loader.ttUtils.normalizeConfirmAnnotation(action.invocation.schema);
        const clone = action.clone();
        let newTable = null;
        for (const param of clone.invocation.in_params) {
            const type = clone.invocation.schema.getArgType(param.name);
            if (!(type instanceof thingtalk_1.Type.Entity) || !loader.idQueries.has(type.type))
                continue;
            assert_1.default(param.value.isUndefined);
            // this assertion will fire if there are two entity parameters of
            // ID type in the same action
            assert_1.default(newTable === null);
            const query = loader.idQueries.get(type.type);
            newTable = new thingtalk_1.Ast.InvocationExpression(null, new thingtalk_1.Ast.Invocation(null, new thingtalk_1.Ast.DeviceSelector(null, query.class.name, null, null), query.name, [], query), query);
            if (confirm === 'auto')
                param.value = new thingtalk_1.Ast.Value.VarRef('id');
        }
        assert_1.default(newTable);
        if (confirm === 'auto') {
            newStatements.push(new thingtalk_1.Ast.ExpressionStatement(null, new thingtalk_1.Ast.ChainExpression(null, [newTable, clone], clone.schema)));
        }
        else {
            newStatements.push(new thingtalk_1.Ast.ExpressionStatement(null, newTable));
            newStatements.push(new thingtalk_1.Ast.ExpressionStatement(null, clone));
        }
    }
    else {
        newStatements.push(new thingtalk_1.Ast.ExpressionStatement(null, expr));
    }
    return newStatements.map(C.adjustDefaultParameters);
}
function initialRequest(loader, stmt) {
    const newStatements = adjustStatementsForInitialRequest(loader, C.toChainExpression(stmt));
    if (newStatements === null)
        return null;
    const history = newStatements.map((stmt) => new thingtalk_1.Ast.DialogueHistoryItem(null, stmt, null, 'accepted'));
    return new thingtalk_1.Ast.DialogueState(null, 'org.thingpedia.dialogue.transaction', 'execute', null, history);
}
exports.initialRequest = initialRequest;
function getStatementDevice(stmt) {
    return stmt.last.schema.class.name;
}
function startNewRequest(loader, ctx, expr) {
    const stmt = C.toChainExpression(expr);
    if (stmt.first.schema.functionType === 'stream')
        return null;
    if (loader.flags.strict_multidomain && getStatementDevice(ctx.current.stmt.expression) === getStatementDevice(stmt))
        return null;
    const newStatements = adjustStatementsForInitialRequest(loader, stmt);
    if (newStatements === null)
        return null;
    const newItems = newStatements.map((stmt) => new thingtalk_1.Ast.DialogueHistoryItem(null, stmt, null, 'accepted'));
    return state_manip_1.addNewItem(ctx, 'execute', null, 'accepted', ...newItems);
}
exports.startNewRequest = startNewRequest;
function addInitialDontCare(expr, dontcare) {
    const chain = C.toChainExpression(expr);
    const table = chain.lastQuery;
    if (!table)
        return null;
    if (!C.isSameFunction(table.schema, dontcare.schema))
        return null;
    assert_1.default(dontcare.ast instanceof thingtalk_1.Ast.DontCareBooleanExpression);
    const arg = table.schema.getArgument(dontcare.ast.name);
    if (!arg || arg.is_input)
        return null;
    if (arg.getAnnotation('filterable') === false)
        return null;
    if (!table.schema.is_list)
        return null;
    const clone = chain.clone();
    const filterExpression = refinement_helpers_1.findOrMakeFilterExpression(clone);
    assert_1.default(filterExpression);
    if (!(filterExpression.expression instanceof thingtalk_1.Ast.InvocationExpression))
        return null;
    if (C.filterUsesParam(filterExpression.filter, dontcare.ast.name))
        return null;
    filterExpression.filter = new thingtalk_1.Ast.BooleanExpression.And(null, [filterExpression.filter, dontcare.ast]).optimize();
    return clone;
}
exports.addInitialDontCare = addInitialDontCare;
//# sourceMappingURL=initial-request.js.map