# Source: Bot.js

    /**
     * Copyright (c) 2017 Baidu, Inc. All Rights Reserved.
     *
     * 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.
     */

    /**
     * @file Bot class
     * @author kira3007<yuanpeng01@baidu.com>
     */

    'use strict';

    const Request = require('./Request');
    const Response = require('./Response');
    const Certificate = require('./Certificate');
    const BotMonitor = require('bot-monitor-sdk');
    const fs = require('fs');

    var privateKey = '';

    /**
     * Bot基类. 请继承此类
     *
     * @example
     * const BaseBot = require('bot-sdk');
     * class MyBot extends BaseBot {
     *     constructor(postData) {
     *          super(postData);
     *          //...
     *     }
     * }
     *
     */
    class Bot {

        /**
         * 构造函数，以及DuerOS请求bot的request为参数，request协议参考[http://TODO]
         *
         * @param {Object} postData DuerOS请求body
         */
        constructor(postData) {
            this.request = new Request(postData);

            this.session = this.request.getSession();

            this.nlu = this.request.getNlu();
            this.response = new Response(this.request, this.session, this.nlu);

            this._eventHandler = new Map();
            this._intentHandler= new Map();

            this.botMonitor = new BotMonitor(postData);
        }

        /**
         * 对SessionEnded添加处理函数
         *
         * @example
         * this.addLaunchHandler(()=>{
         *      // 进入bot，提示用户如何操作
         * });
         *
         * @param {Function} handler 意图处理函数。返回值非null，将作为bot的response返回DuerOS
         *                        函数返回值参考Response.build() 的输入参数
         * @return {Bot} 返回自己
         * @public
         */
        addLaunchHandler(handler) {
            return this._addHandler('LaunchRequest', handler);
        }

        /**
         * 对SessionEnded添加处理函数
         *
         * @example
         * this.addSessionEndedHandler(()=>{
         *     // todo some clear job
         *     // this.clearSession()
         * });
         *
         * @param {Function} handler 意图处理函数。返回值非null，将作为bot的response返回DuerOS
         *                        函数返回值参考Response.build() 的输入参数
         * @return {Bot} 返回自己
         * @public
         */
        addSessionEndedHandler(handler) {
            return this._addHandler('SessionEndedRequest', handler);
        }

        /**
         * 对一个intent添加处理函数
         *
         * @example
         * this.addIntentHandler('intentName', ()=>{
         *     //this.getSlot('slotName');
         * });
         *
         * @param {string} intent 意图名：'intentName'
         * @param {Function} handler 意图处理函数。返回值非null，将作为bot的response返回DuerOS
         *                        函数返回值参考Response.build() 的输入参数
         * @return {Bot} 返回自己
         * @public
         */
        addIntentHandler(intent, handler) {
            return this._addHandler('#'+intent, handler);
        }

        /**
         * @param {string} intent 条件，可以是意图名(加'#'开头)，LaunchRequest，SessionEndedRequest
         * @param {Function} handler 条件处理函数
         *
         * @return {Bot} 返回自己
         * @private
         */
        _addHandler(intent, handler) {
            this._intentHandler.set(intent, () => {
                return handler.call(this);
            });
            return this;
        }

        /**
         * 对一个事件添加处理函数。比如设备端反馈的音频播放开始事件
         *
         * @example
         * this.addEventListener('Audio', (event)=>{
         *     // event 为事件数据
         *     // 具体数据结构参考[TODO]
         * });
         *
         * @param {string} event  事件名
         * @param {Function} handler 事件处理函数。返回值非null，将作为bot的response返回DuerOS
         *                        函数返回值参考Response.build() 的输入参数
         * @return {Bot} 返回自己
         * @public
         */
        addEventListener(event, handler) {
            this._eventHandler.set(event, (args) => {
                return handler.call(this, args);
            });
            return this;
        }

        /**
         * 初始化认证校验
         *
         * @example
         * this.initCertificate(
         *      {signature: '###', signaturecerturl: ''},
         *      '{"version":""}'
         *  );
         *
         * @param {Object} headers http请求的header
         * @param {string} body 请求体
         * @return {Certificate} Certificate实例
         * @public
         */
        initCertificate(headers, body) {
            return this.certificate = new Certificate(headers, body);
        }

        /**
         * 获取DuerOS请求中的意图名
         *
         * @return {string|null}
         * @public
         */
        getIntentName() {
            if (this.nlu) {
                return this.nlu.getIntentName();
            }
        }

        /**
         * 获取session的一个字段对应的值
         *
         * @param {string} field 字段名
         * @param {Mixed} defaultValue  默认值  当此字段没有值时，返回defaultValue
         * @return {Mixied}
         * @public
         */
        getSessionAttribute(field = null, defaultValue = null) {
            return this.session.getData(field, defaultValue);
        }

        /**
         * 设置session的一个字段的值
         *
         * @param {string} field 字段名
         * @param {Mixed} value 字段对应的值
         * @param {Mixed} defaultValue  默认值  当value为空时，使用defaultValue
         * @return {null}
         * @public
         */

        setSessionAttribute(field, value, defaultValue = null) {
            return this.session.setData(field, value, defaultValue);
        }

        /**
         * 清空session的所有字段
         *
         * @return {null}
         * @public
         */
        clearSessionAttribute() {
            return this.session.clear();
        }

        /**
         * 根据槽位名获取槽位对应的值
         *
         * @param {string} field  槽位名
         * @param {Integer} index 第几个intent，默认第一个
         * @return {string}
         * @public
         */
        getSlot(field, index = 0) {
            if (this.nlu) {
                return this.nlu.getSlot(field, index);
            }
        }

        /**
         * 设置槽位的值。如果该槽位不存在，新增一个槽位名，并设置对于的值
         *
         * @param {string} field 槽位名
         * @param {string} value 值
         * @param {Integer} index 第几个intent，默认第一个
         * @return {null}
         * @public
         */
        setSlot(field, value, index = 0) {
            if (this.nlu) {
                return this.nlu.setSlot(field, value, index);
            }
        }

        /**
         * 设置多轮继续，等待用户回复
         *
         * @return {null}
         * @public
         */
        waitAnswer() {
            //should_end_session
            this.response.setShouldEndSession(false);
        }

        /**
         * 设置多轮结束，此时bot结束多轮对话
         *
         * @public
         */
        endSession() {
            this.response.setShouldEndSession(true);
        }

        /**
         * 设置私钥
         *
         * @param {string} filename 私钥路径
         * @return {Promise}
         */
        setPrivateKey(filename) {
            return new Promise(function(resolve, reject){
                if(!filename) {
                    reject();
                }

                if(privateKey) {
                    resolve(privateKey);
                }else{
                    fs.readFile(filename, {encoding: "utf8"}, (err, data) => {
                        if (err) {
                            console.error('get private key error');
                            reject();
                        } else {
                            privateKey = data;
                            resolve(data);
                        }
                    });
                }
            });
        }

        /**
         * bot执行的入口
         *
         * @param {boolean} build 是否需要打包response，输出JSON String
         * @return {Promise}
         * @public
         */
        run(build = true) {
            var _this = this;
            if(this.certificate && this.certificate.enableVerifyRequestSign()) {
                return _this.certificate.verifyRequest().then(function(){
                    return _this._run(build);
                }, function(){
                    return _this.response.illegalRequest();
                });
            }

            return this._run(build);
        }

        /**
         * @param {boolean} build 是否需要打包response，输出JSON String
         * @return {Promise|string}
         * @private
         */
        _run(build = true) {
            //handler event
            let eventHandler = this._getRegisterEventHandler();

            if (this.request.getType() == 'IntentRequset' && !this.nlu && !eventHandler) {
                return this.response.defaultResult();
            }

            //intercept beforeHandler
            let ret;

            //event process
            if (eventHandler) {
                this.botMonitor.setDeviceEventStart();
                let event = this.request.getEventData();
                ret = eventHandler(event);
                this.botMonitor.setDeviceEventEnd();
            } else {
                this.botMonitor.setEventStart();
                ret = this._intentDispatch();
                this.botMonitor.setEventEnd();
            }

            let res = this.response.build(ret);
            var _this = this;
            res.then(function (responseData) {
                _this.botMonitor.setResponseData(responseData);
                _this.botMonitor.uploadData();
            });

            if (!build) {
                return ret;
            }
            return res;
        }

        /**
         * @return {Object}
         * @private
         */
        _intentDispatch() {
            if (!this._intentHandler.size) {
                return;
            }

            let rules = this._intentHandler.keys();
            for (let rule of rules) {

                if (this._checkHandler(rule)) {
                    let ret = this._intentHandler.get(rule)();

                    if (ret) {
                        return ret;
                    }
                }
            }
        }

        _getRegisterEventHandler() {
            let eventData = this.request.getEventData() || {};
            if (eventData.type) {
                let key = eventData.type;
                return this._eventHandler.get(key);
            }
        }

        /**
         * @param {string} rule 判断条件
         * @return {boolean}
         * @private
         */
        _checkHandler(rule) {
            let token = this._getToken(rule);
            if (!token instanceof Array) {
                return false;
            }

            let arr = [];
            for (let t of token) {
                if (t.type == 'str') {
                    arr.push(t.value);
                } else {
                    arr.push(this._tokenValue(t.value));
                }
            }

            let str = arr.join('');
            let func = new Function('', 'return ' +  str + ';');
            return func();
        }

        /**
         * @param {string} ruleString 判断条件
         * @return {Array}
         * @private
         */
        _getToken(ruleString) {
            function tokenize(token, rule) {
                if (rule === '' || rule === null) {
                    return token;
                }

                token = token || [];
                let rgCom = /[^"']*/;
                let m = rgCom.exec(rule);
                token.push({
                    type: 'no_str',
                    value: m[0]
                });

                let last = rule.substring(m[0].length);
                if (last !== '' || last !== null) {
                    for (let i = 1; i < last.length; i++) {
                        let c = last[i];
                        if (c == '\\'){
                            ++i;
                            continue;
                        }

                        if (c == last[0]) {
                            let s = last.substr(0, i + 1);
                            last = last.substring(s.length);
                            token.push({
                                type: 'str',
                                value: s,
                            });

                            break;
                        }
                    }
                }

                if (last) {
                    return tokenize(token, last);
                }

                return token;
            }

            let token = [];
            return tokenize(token, ruleString);
        }

        /**
         * @param {string} str 字符串片段
         * @return {string}
         * @private
         */
        _tokenValue (str) {
            if (str === '' || str === null) {
                return '';
            }

            let rg = new Map([
                    ['intent', /#([\w\.\d_]+)/],
                    ['session', /session\.([\w\.\d_]+)/],
                    ['slot', /slot\.([\w\d_]+)/],
                    ['requestType', /^(LaunchRequest|SessionEndedRequest)$/]
                ]);

            let self = this;
            for(let [k, r] of rg) {
                str = str.replace(r, function() {
                    let m = arguments[1];

                    if (k == 'intent') {
                        return (self.getIntentName() == m).toString();
                    } else if (k == 'session') {
                        return '"' + (self.getSessionAttribute(m)).toString() + '"';
                    } else if (k == 'slot') {
                        return '"' + (self.getSlot(m)).toString() + '"';
                    } else if (k == 'requestType') {
                        return (self.request.getType() == m).toString();
                    }
                });
            }

            return str;
        }
    }

    /**
     * Bot可以返回的卡片
     * @namespace Bot.Card
     */
    Bot.Card = {};

    /**
     * @static
     * @public
     * @see {@link TextCard}
     */
    Bot.Card.TextCard = require('./card/TextCard');

    /**
     * @static
     * @public
     * @see {@link ImageCard}
     */
    Bot.Card.ImageCard = require('./card/ImageCard');

    /**
     * @static
     * @public
     * @see {@link StandardCard}
     */
    Bot.Card.StandardCard = require('./card/StandardCard');

    /**
     * @static
     * @public
     * @see {@link ListCard}
     */
    Bot.Card.ListCard = require('./card/ListCard');

    /**
     * @static
     * @public
     * @see {@link LinkAccountCard}
     */
    Bot.Card.LinkAccountCard = require('./card/LinkAccountCard');

    /**
     * Bot 可以返回指令
     * @namespace Bot.Directive
     */
    Bot.Directive = {};

    /**
     * 音频指令
     * @namespace Bot.Directive.AudioPlayer
     */
    Bot.Directive.AudioPlayer = {};

    /**
     * 音频播放指令
     *
     * @static
     * @public
     * @see {@link Play}
     */
    Bot.Directive.AudioPlayer.Play = require('./directive/AudioPlayer/Play.js');

    /**
     * 音频停止指令
     *
     * @static
     * @public
     * @see {@link Stop}
     */
    Bot.Directive.AudioPlayer.Stop = require('./directive/AudioPlayer/Stop.js');

    module.exports = Bot;