"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.execute = exports.initArgparse = void 0;
const express_1 = __importDefault(require("express"));
const body_parser_1 = __importDefault(require("body-parser"));
// FIXME
//import logger from 'morgan';
const errorhandler_1 = __importDefault(require("errorhandler"));
const qv = __importStar(require("query-validation"));
const Tp = __importStar(require("thingpedia"));
const ThingTalk = __importStar(require("thingtalk"));
const Utils = __importStar(require("../lib/utils/misc-utils"));
const localparserclient_1 = __importDefault(require("../lib/prediction/localparserclient"));
const I18n = __importStar(require("../lib/i18n"));
function learn(req, res) {
    res.status(501).json({ error: 'Learning is not available with this Genie server' });
}
async function tokenize(params, data, res) {
    const app = res.app;
    if (params.locale !== app.args.locale) {
        res.status(404).json({ error: 'Unsupported language' });
        return;
    }
    const tokenized = await app.backend.tokenizer.tokenize(data.q);
    if (data.entities)
        Utils.renumberEntities(tokenized, data.entities);
    res.json(tokenized);
}
const QUERY_PARAMS = {
    q: 'string',
    store: '?string',
    access_token: '?string',
    thingtalk_version: '?string',
    limit: '?integer',
    expect: '?string',
    choices: '?array',
    context: '?string',
    entities: '?object',
    tokenized: 'boolean',
    skip_typechecking: 'boolean',
    developer_key: '?string',
};
async function queryNLU(params, data, res) {
    const app = res.app;
    if (params.locale !== app.args.locale) {
        res.status(400).json({ error: 'Unsupported language' });
        return;
    }
    const result = await res.app.backend.nlu.sendUtterance(data.q, data.context ? data.context.split(' ') : undefined, data.entities, data);
    res.json(result);
}
const NLG_PARAMS = {
    context: 'string',
    entities: 'object',
    target: 'string',
    limit: '?number',
};
async function queryNLG(params, data, res) {
    const app = res.app;
    if (params.locale !== app.args.locale) {
        res.status(400).json({ error: 'Unsupported language' });
        return;
    }
    const result = await res.app.backend.nlg.generateUtterance(data.context.split(' '), data.entities, data.target.split(' '));
    res.json({
        candidates: result.slice(0, data.limit ? parseInt(data.limit) : undefined),
    });
}
function initArgparse(subparsers) {
    const parser = subparsers.add_parser('server', {
        add_help: true,
        description: "Expose a Genie-compatible NLP API over HTTP."
    });
    parser.add_argument('-p', '--port', {
        required: false,
        help: "HTTP port to listen on",
        default: 8400,
    });
    parser.add_argument('--nlu-model', {
        required: true,
        help: "Path to the NLU model, pointing to a model directory.",
    });
    parser.add_argument('--nlg-model', {
        required: false,
        help: "Path to the NLG model, pointing to a model directory.",
    });
    parser.add_argument('--thingpedia', {
        required: true,
        help: 'Path to ThingTalk file containing class definitions.'
    });
    parser.add_argument('-l', '--locale', {
        required: false,
        default: 'en-US',
        help: `BGP 47 locale tag of the language to evaluate (defaults to 'en-US', English)`
    });
    parser.add_argument('--debug', {
        action: 'store_true',
        help: 'Enable debugging.',
        default: true
    });
    parser.add_argument('--no-debug', {
        action: 'store_false',
        dest: 'debug',
        help: 'Disable debugging.',
    });
}
exports.initArgparse = initArgparse;
async function execute(args) {
    const tpClient = new Tp.FileClient(args);
    const schemas = new ThingTalk.SchemaRetriever(tpClient, null, true);
    const app = express_1.default();
    const i18n = I18n.get(args.locale);
    app.backend = {
        schemas,
        i18n,
        tokenizer: i18n.getTokenizer(),
        nlu: new localparserclient_1.default(args.nlu_model, args.locale, undefined, undefined, tpClient)
    };
    app.backend.nlu.start();
    if (args.nlg_model && args.nlg_model !== args.nlu_model) {
        app.backend.nlg = new localparserclient_1.default(args.nlg_model, args.locale, undefined, undefined, tpClient);
        app.backend.nlg.start();
    }
    else {
        app.backend.nlg = app.backend.nlu;
    }
    app.args = args;
    app.set('port', args.port);
    app.use(body_parser_1.default.json());
    app.use(body_parser_1.default.urlencoded({ extended: true }));
    //app.use(logger('dev'));
    app.use((req, res, next) => {
        res.set('Access-Control-Allow-Origin', '*');
        next();
    });
    app.post('/:locale/query', qv.validatePOST(QUERY_PARAMS, { accept: 'application/json' }), (req, res, next) => {
        queryNLU(req.params, req.body, res).catch(next);
    });
    app.post('/:locale/answer', qv.validatePOST(NLG_PARAMS, { accept: 'application/json' }), (req, res, next) => {
        queryNLG(req.params, req.body, res).catch(next);
    });
    app.post('/:locale/tokenize', qv.validatePOST({ q: 'string', entities: '?object' }, { accept: 'application/json' }), (req, res, next) => {
        tokenize(req.params, req.body, res).catch(next);
    });
    app.post('/:locale/learn', learn);
    // if we get here, we have a 404 error
    app.use('/', (req, res) => {
        res.status(404).json({ error: 'Invalid endpoint' });
    });
    app.use(errorhandler_1.default());
    const server = app.listen(app.get('port'));
    await new Promise((resolve, reject) => {
        process.on('SIGINT', resolve);
        process.on('SIGTERM', resolve);
    });
    await app.backend.nlu.stop();
    if (app.backend.nlg !== app.backend.nlu)
        await app.backend.nlg.stop();
    server.close();
}
exports.execute = execute;
//# sourceMappingURL=server.js.map