/**
* Created by Karl-Heinz Wind
**/

// Temporary fix for 0.30.0 api
process.env["NTBA_FIX_319"] = 1;

module.exports = function (RED) {
    "use strict";
    var telegramBot = require('node-telegram-bot-api');
    const Agent = require('socks5-https-client/lib/Agent')

    // --------------------------------------------------------------------------------------------
    // The configuration node
    // holds the token
    // and establishes the connection to the telegram bot
    // you can either select between polling mode and webhook mode.
    function TelegramBotNode(n) {
        RED.nodes.createNode(this, n);

        var self = this;

        // contains all the nodes that make use of the bot connection maintained by this configuration node.
        this.users = [];

        this.status = "disconnected";

        // Reading configuration properties...
        this.botname = n.botname;
        this.verbose = n.verboselogging;

        this.usernames = [];
        if (n.usernames) {
            this.usernames = n.usernames.split(',');
        }

        this.chatids = [];
        if (n.chatids) {
            this.chatids = n.chatids.split(',').map(function (item) {
                return parseInt(item, 10);
            });
        }

        this.baseApiUrl = n.baseapiurl;

        this.updateMode = n.updatemode;
        if(!this.updateMode){
            this.updateMode = "polling";
        }

        // 1. optional when polling mode is used
        this.pollInterval = parseInt(n.pollinterval);
        if (isNaN(this.pollInterval)) {
            this.pollInterval = 300;
        }

        // 2. optional when webhook is used.
        this.botHost = n.bothost;

        this.publicBotPort = parseInt(n.publicbotport);
        if (isNaN(this.publicBotPort)) {
            this.publicBotPort = 8443;
        }

        this.localBotPort = parseInt(n.localbotport);
        if (isNaN(this.localBotPort)) {
            this.localBotPort = this.publicBotPort;
        }

        // 3. optional when webhook and self signed certificate is used
        this.privateKey = n.privatekey;
        this.certificate = n.certificate;
        this.useSelfSignedCertificate = n.useselfsignedcertificate;
        this.sslTerminated = n.sslterminated;

        // 4. optional when request via SOCKS5 is used. 
        this.useSocks = n.usesocks;
        if(this.useSocks){
            this.socksRequest = {
                agentClass: Agent,
                agentOptions: {
                    socksHost: n.sockshost,
                    socksPort: n.socksport,
                    socksUsername: n.socksusername,
                    socksPassword: n.sockspassword,
                },
            };
        }
        
        this.useWebhook = false;
        if (this.updateMode == "webhook") {
            if (this.botHost && (this.sslTerminated || (this.privateKey && this.certificate))) {
                this.useWebhook = true;
            } else{
                self.error("Configuration data for webhook is not complete. Defaulting to polling mode.");
            }
        }

        // Activates the bot or returns the already activated bot. 
        this.getTelegramBot = function () {
            if (!this.telegramBot) {
                if (this.credentials) {
                    this.token = this.credentials.token;
                    if (this.token) {
                        this.token = this.token.trim();
                        if (!this.telegramBot) {

                            if (this.useWebhook){
                                var webHook =
                                {
                                    autoOpen: true,
                                    port : this.localBotPort,
                                };
                                if (!this.sslTerminated) {
                                    webHook.key = this.privateKey;
                                    webHook.cert = this.certificate;
                                }
                                var options =
                                {
                                    webHook: webHook,
                                    baseApiUrl: this.baseApiUrl,
                                    request: this.socksRequest,
                                };
                                this.telegramBot = new telegramBot(this.token, options);

                                this.telegramBot.on('webhook_error', function (error) {
                                   
                                    self.setUsersStatus({ fill: "red", shape: "ring", text: "webhook error" })
                                    
                                    if(self.verbose) {
                                        self.warn("Webhook error: " + error.message);
                                    }
                                    
                                    // TODO: check if we should abort in future when this happens
                                    // self.abortBot(error.message, function () {
                                    //     self.warn("Bot stopped: Webhook error.");
                                    // });
                                });

                                var botUrl = "https://" + this.botHost + ":" + this.publicBotPort + "/" + this.token; 
                                var setWebHookOptions;
                                if (!this.sslTerminated && this.useSelfSignedCertificate){
                                    setWebHookOptions = {
                                        certificate: options.webHook.cert,
                                    };
                                }
                                this.telegramBot.setWebHook(botUrl, setWebHookOptions).then(function (success) {
                                    
                                    if(self.verbose) {
                                        self.telegramBot.getWebHookInfo().then(function (result) {
                                            self.log("Webhook enabled: " + JSON.stringify(result));
                                        });
                                    }

                                    if(success) {
                                        self.status = "connected";
                                    }
                                    else {
                                        self.abortBot("Failed to set webhook " + botUrl, function () {
                                            self.warn("Bot stopped: Webhook not set.");
                                        });
                                    }   
                                });     
                            }
                            else {
                                var polling = {
                                    autoStart: true,
                                    interval: this.pollInterval
                                }

                                var options = {
                                    polling: polling,
                                    baseApiUrl: this.baseApiUrl,
                                    request: this.socksRequest,
                                };
                                this.telegramBot = new telegramBot(this.token, options);
                                self.status = "connected";

                                this.telegramBot.on('polling_error', function (error) {

                                    self.setUsersStatus({ fill: "red", shape: "ring", text: "polling error" })

                                    if(self.verbose) {
                                        self.warn(error.message);
    
                                        var stopPolling = false;
                                        var hint;
                                        if (error.message === "ETELEGRAM: 401 Unauthorized") {
                                            hint = "Please check if the bot token is valid: " + self.credentials.token;
                                            stopPolling = true;
                                        }
                                        else if (error.message.startsWith("EFATAL: Error: connect ETIMEDOUT")) {
                                            hint = "Timeout connecting to server. Trying again.";
                                        }
                                        else if (error.message.startsWith("EFATAL: Error: read ECONNRESET")) {
                                            hint = "Network connection may be down. Trying again.";
                                        }
                                        else if (error.message.startsWith("EFATAL: Error: getaddrinfo EAI_AGAIN")) {
                                            hint = "Network connection may be down. Trying again.";
                                        }
                                        else if (error.message.startsWith("EFATAL: Error: getaddrinfo ENOTFOUND")) {
                                            hint = "Network connection may be down. Trying again.";
                                        }
                                        else if (error.message.startsWith("EFATAL: Error: SOCKS connection failed. Connection refused.")) {
                                            hint = "Username or password may be be wrong. Aborting.";
                                            stopPolling = true;
                                        }
                                        else if (error.message.startsWith("EFATAL: Error: connect ETIMEDOUT")) {
                                            hint = "Server did not respond. Maybe proxy blocked polling. Trying again.";
                                        }
                                        else {
                                            // unknown error occured... we simply ignore it.
                                            hint = "Unknown error. Trying again.";
                                        }
    
                                        if (stopPolling) {
                                            self.abortBot(error.message, function () {
                                                self.warn("Bot stopped: " + hint);
                                            });
                                        }
                                        else {
                                            // here we simply ignore the bug and continue polling.
                                            // The following line is removed as this would create endless log files
                                            if(self.verbose){
                                                self.warn(hint);
                                            }
                                        }
                                    }
                                });
                            }

                            this.telegramBot.on('error', function (error) {
                                self.warn("Bot error: " + error.message);

                                self.abortBot(error.message, function () {
                                    self.warn("Bot stopped: Fatal Error.");
                                });
                            });
                        }
                    }
                }
            }

            return this.telegramBot;
        }

        this.on('close', function (done) {
            self.abortBot("closing", done);
        });

        this.abortBot = function (hint, done) {

            if (self.telegramBot !== null) {
                if (self.telegramBot._polling){
                    self.telegramBot.stopPolling()
                        .then(function () {
                            self.telegramBot = null;
                            self.status = "disconnected";
                            self.setUsersStatus({ fill: "red", shape: "ring", text: "bot stopped. " + hint })
                            done();
                        });
                }

                if (self.telegramBot._webHook){
                    self.telegramBot.deleteWebHook();
                    self.telegramBot.closeWebHook()
                        .then(function () {
                            self.telegramBot = null;
                            self.status = "disconnected";
                            self.setUsersStatus({ fill: "red", shape: "ring", text: "bot stopped. " + hint })
                            done();
                        });
                }   
            }
            else {
                self.status = "disconnected";
                self.setUsersStatus({ fill: "red", shape: "ring", text: "bot stopped. " + hint })
                done();
            }
        }

        this.isAuthorizedUser = function (user) {
            var isAuthorized = false;
            if (self.usernames.length > 0) {
                if (self.usernames.indexOf(user) >= 0) {
                    isAuthorized = true;
                }
            }

            return isAuthorized;
        }

        this.isAuthorizedChat = function (chatid) {
            var isAuthorized = false;
            var length = self.chatids.length;
            if (length > 0) {
                for (var i = 0; i < length; i++) {
                    var id = self.chatids[i];
                    if (id === chatid) {
                        isAuthorized = true;
                        break;
                    }
                }
            }

            return isAuthorized;
        }

        this.isAuthorized = function (chatid, user) {
            var isAuthorizedUser = self.isAuthorizedUser(user);
            var isAuthorizedChatId = self.isAuthorizedChat(chatid);

            var isAuthorized = false;

            if (isAuthorizedUser || isAuthorizedChatId) {
                isAuthorized = true;
            } else {
                if (self.chatids.length === 0 && self.usernames.length === 0) {
                    isAuthorized = true;
                }
            }

            return isAuthorized;
        }

        this.register = function (node) {	
            var id = node.id;       
            if (self.users.indexOf(id) === -1) {	           
                self.users.push(id);	               
            }	               
            else {	
                if(self.verbose) {
                    // This warnin was removed as it caused confusion: see https://github.com/windkh/node-red-contrib-telegrambot/issues/87
                    // self.warn("Node " + id + " registered twice at the configuration node: ignoring.");	
                }
            }	
        }

        this.setUsersStatus = function (status) {
            self.users.forEach(function (nodeId) {
                var userNode = RED.nodes.getNode(nodeId);
                if(userNode) {
                    userNode.status(status);
                }
            });
        }

    }
    RED.nodes.registerType("telegram bot", TelegramBotNode, {
        credentials: {
            token: { type: "text" }
        }
    });

    // adds the caption of the message into the options.
    function addCaptionToMessageOptions(msg) {
        var options = msg.payload.options;
        if (options === undefined) {
            options = {};
        }

        if (msg.payload.caption !== undefined) {
            options.caption = msg.payload.caption;
        }

        msg.payload.options = options;

        return msg;
    }

    function getPhotoIndexWithHighestResolution(photoArray) {
        var photoIndex = 0;
        var highestResolution = 0;

        photoArray.forEach(function (photo, index, array) {
            var resolution = photo.width * photo.height;
            if (resolution > highestResolution) {
                highestResolution = resolution;
                photoIndex = index;
            }
        });

        return photoIndex;
    }

    // creates the message details object from the original message
    function getMessageDetails(botMsg) {

        // Note that photos and videos can be sent as media group. The bot will receive the contents as single messages.
        // Therefore the photo and video messages can contain a mediaGroupId if so....
        var messageDetails;
        if (botMsg.text) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'message', content: botMsg.text };
        } else if (botMsg.photo) {
            // photos are sent using several resolutions. Therefore photo is an array. We choose the one with the highest resolution in the array.
            var index = getPhotoIndexWithHighestResolution(botMsg.photo);
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'photo', content: botMsg.photo[index].file_id, caption: botMsg.caption, date: botMsg.date, blob: true, photos: botMsg.photo, mediaGroupId: botMsg.media_group_id };
        } else if (botMsg.audio) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'audio', content: botMsg.audio.file_id, caption: botMsg.caption, date: botMsg.date, blob: true };
        } else if (botMsg.document) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'document', content: botMsg.document.file_id, caption: botMsg.caption, date: botMsg.date, blob: true };
        } else if (botMsg.sticker) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'sticker', content: botMsg.sticker.file_id, date: botMsg.date, blob: true };
        } else if (botMsg.video) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'video', content: botMsg.video.file_id, caption: botMsg.caption, date: botMsg.date, blob: true, mediaGroupId: botMsg.media_group_id };
        } else if (botMsg.video_note) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'video_note', content: botMsg.video_note.file_id, caption: botMsg.caption, date: botMsg.date, blob: true };
        } else if (botMsg.voice) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'voice', content: botMsg.voice.file_id, caption: botMsg.caption, date: botMsg.date, blob: true };
        } else if (botMsg.location) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'location', content: botMsg.location, date: botMsg.date };
        } else if (botMsg.venue) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'venue', content: botMsg.venue, date: botMsg.date };
        } else if (botMsg.contact) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'contact', content: botMsg.contact, date: botMsg.date };
        } else if (botMsg.new_chat_title) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'new_chat_title', content: botMsg.new_chat_title, date: botMsg.date };
        } else if (botMsg.new_chat_photo) {
            // photos are sent using several resolutions. Therefore photo is an array. We choose the one with the highest resolution in the array.
            var index = getPhotoIndexWithHighestResolution(botMsg.new_chat_photo);
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'new_chat_photo', content: botMsg.new_chat_photo[index].file_id, date: botMsg.date, blob: true, photos: botMsg.new_chat_photo }; 
        } else if (botMsg.new_chat_members) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'new_chat_members', content: botMsg.new_chat_members, date: botMsg.date, };
        } else if (botMsg.left_chat_member) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'left_chat_member', content: botMsg.left_chat_member, date: botMsg.date, };
        } else if (botMsg.delete_chat_photo) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'delete_chat_photo', content: botMsg.delete_chat_photo, date: botMsg.date };
        } else if (botMsg.pinned_message) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'pinned_message', content: botMsg.pinned_message, date: botMsg.date };
        } else if (botMsg.channel_chat_created) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'channel_chat_created', content: botMsg.channel_chat_created, date: botMsg.date };
        } else if (botMsg.group_chat_created) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'group_chat_created', content: botMsg.group_chat_created, chat: botMsg.chat, date: botMsg.date };
        } else if (botMsg.supergroup_chat_created) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'supergroup_chat_created', content: botMsg.supergroup_chat_created, chat: botMsg.chat, date: botMsg.date };
        } else if (botMsg.migrate_from_chat_id) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'migrate_from_chat_id', content: botMsg.migrate_from_chat_id, chat: botMsg.chat, date: botMsg.date };
        } else if (botMsg.migrate_to_chat_id) {
            messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'migrate_to_chat_id', content: botMsg.migrate_to_chat_id, chat: botMsg.chat, date: botMsg.date };
        } else {
            // unknown type --> no output

            // TODO:
            // 'successful_payment',
            // 'invoice',
            // 'game',
        }

        return messageDetails;
    }

    // --------------------------------------------------------------------------------------------
    // The input node receives messages from the chat.
    // the message details are stored in the payload
    // chatId
    // type
    // content
    // depending on type caption and date is part of the output, too.
    // The original message is stored next to payload.
    //
    // The message ist send to output 1 if the message is from an authorized user
    // and to output2 if the message is not from an authorized user.
    //
    // message : content string
    // photo   : content file_id of first image in array
    // audio   : content file_id
    // document: content file_id of document
    // sticker : content file_id
    // video   : content file_id
    // voice   : content file_id
    // location: content is object with latitude and longitude
    // contact : content is full contact object
    function TelegramInNode(config) {
        RED.nodes.createNode(this, config);
        var node = this;
        this.bot = config.bot;

        this.config = RED.nodes.getNode(this.bot);
        if (this.config) {
            this.config.register(node);

            node.status({ fill: "red", shape: "ring", text: "not connected" });

            node.telegramBot = this.config.getTelegramBot();
            if (node.telegramBot) {
                node.status({ fill: "green", shape: "ring", text: "connected" });

                node.telegramBot.on('message', function (botMsg) {
                    node.status({ fill: "green", shape: "ring", text: "connected" });

                    var username = botMsg.from.username;
                    var chatid = botMsg.chat.id;
                    var messageDetails = getMessageDetails(botMsg);
                    if (messageDetails) {
                        var msg = { payload: messageDetails, originalMessage: botMsg };

                        if (node.config.isAuthorized(chatid, username)) {
                            // downloadable "blob" message?
                            if (messageDetails.blob) {
                                var fileId = msg.payload.content;
                                node.telegramBot.getFileLink(fileId).then(function (weblink) {
                                    msg.payload.weblink = weblink;

                                    // download and provide with path
                                    if (config.saveDataDir) {
                                        node.telegramBot.downloadFile(fileId, config.saveDataDir).then(function (path) {
                                            msg.payload.path = path;
                                            node.send([msg, null]);
                                        });
                                    } else {
                                        node.send([msg, null]);
                                    }
                                });
                                // vanilla message
                            } else {
                                node.send([msg, null]);
                            }
                        } else {
                            if(node.config.verbose){
                                node.warn("Unauthorized incoming call from " + username);
                            }
                            node.send([null, msg]);
                        }
                    }
                });
            } else {
                node.warn("bot not initialized");
                node.status({ fill: "red", shape: "ring", text: "bot not initialized" });
            }
        } else {
            node.warn("config node failed to initialize.");
            node.status({ fill: "red", shape: "ring", text: "config node failed to initialize" });
        }

        this.on("close", function () {
            node.telegramBot.off('message');
            node.status({});
        });
    }
    RED.nodes.registerType("telegram receiver", TelegramInNode);



    // --------------------------------------------------------------------------------------------
    // The input node receives a command from the chat.
    // The message details are stored in the payload
    // chatId
    // type
    // content
    // depending on type caption and date is part of the output, too.
    // The original message is stored next to payload.
    //
    // message : content string
    function TelegramCommandNode(config) {
        RED.nodes.createNode(this, config);
        var node = this;
        var command = config.command;
        var strict = config.strict;
        this.bot = config.bot;

        this.config = RED.nodes.getNode(this.bot);
        if (this.config) {
            this.config.register(node);

            node.status({ fill: "red", shape: "ring", text: "not connected" });

            node.telegramBot = this.config.getTelegramBot();
            node.botname = this.config.botname;
            if (node.telegramBot) {
                node.status({ fill: "green", shape: "ring", text: "connected" });

                node.telegramBot.on('message', function (botMsg) {
                    node.status({ fill: "green", shape: "ring", text: "connected" });

                    var username = botMsg.from.username;
                    var chatid = botMsg.chat.id;
                    if (node.config.isAuthorized(chatid, username)) {
                        var msg;
                        var messageDetails;
                        if (botMsg.text) {
                            var message = botMsg.text;
                            var tokens = message.split(" ");

                            var isChatCommand = tokens[0] === command;
                            var command2 = command + "@" + node.botname;
                            var isDirectCommand = tokens[0] === command2;
                            var isGroupChat = chatid < 0;
                            
                            if (isDirectCommand || 
                                (isChatCommand && !isGroupChat) ||
                                (isChatCommand && isGroupChat && !strict)) {
                                
                                var remainingText;
                                if(isDirectCommand)
                                {
                                    remainingText = message.replace(command2, "");
                                }
                                else
                                {
                                    remainingText = message.replace(command, "");
                                }

                                messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'message', content: remainingText };
                                msg = { payload: messageDetails, originalMessage: botMsg };
                                node.send([msg, null]);
                            } else {
                                messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'message', content: botMsg.text };
                                msg = { payload: messageDetails, originalMessage: botMsg };
                                node.send([null, msg]);
                            }
                        } else {
                            // unknown type --> no output
                        }
                    } else {
                        // ignoring unauthorized calls
                        if(node.config.verbose){
                            node.warn("Unauthorized incoming call from " + username);
                        }
                    }
                });
            } else {
                node.warn("bot not initialized.");
                node.status({ fill: "red", shape: "ring", text: "no bot token found in config" });
            }
        } else {
            node.warn("config node failed to initialize.");
            node.status({ fill: "red", shape: "ring", text: "config node failed to initialize" });
        }

        this.on("close", function () {
            node.telegramBot.off('message');
            node.status({});
        });
    }
    RED.nodes.registerType("telegram command", TelegramCommandNode);



    // --------------------------------------------------------------------------------------------
    // The input node receives an event from the chat.
    // The type of event can be configured:
    // - callback_query
    // - inline_query
    // - edited_message
    // - channel_post
    // - edited_channel_post
    // - edited_message_text
    // The message details are stored in the payload
    // chatId
    // messageId
    // type
    // content
    // depending on type from and date is part of the output, too.
    // The original message is stored next to payload.
    // callback_query : content string
    function TelegramEventNode(config) {
        RED.nodes.createNode(this, config);
        var node = this;
        this.bot = config.bot;
        this.event = config.event;
        this.autoAnswerCallback = config.autoanswer;

        this.config = RED.nodes.getNode(this.bot);
        if (this.config) {
            this.config.register(node);

            node.status({ fill: "red", shape: "ring", text: "not connected" });

            node.telegramBot = this.config.getTelegramBot();
            node.botname = this.config.botname;
            if (node.telegramBot) {
                node.status({ fill: "green", shape: "ring", text: "connected" });

                node.telegramBot.on(this.event, (botMsg) => {
                    node.status({ fill: "green", shape: "ring", text: "connected" });

                    var username;
                    var chatid;
                    if (botMsg.chat) { //channel
                        username = botMsg.chat.username;
                        chatid = botMsg.chat.id;
                    } else if (botMsg.from) {       //private, group, supergroup
                        username = botMsg.from.username;
                        chatid = botMsg.from.id;
                    } else {
                        node.error("username or chatid undefined");
                    }
                    if (node.config.isAuthorized(chatid, username)) {
                        var msg;
                        var messageDetails;

                        switch (this.event) {
                            case 'callback_query':
                                var callbackQueryId = botMsg.id;
                                messageDetails = {
                                    chatId: chatid,
                                    messageId: botMsg.message.message_id,
                                    type: 'callback_query',
                                    content: botMsg.data,
                                    callbackQueryId: callbackQueryId,
                                    from: botMsg.from
                                };
                                if (node.autoAnswer) {
                                    node.telegramBot.answerCallbackQuery(callbackQueryId).then(function (result) {
                                        // Nothing to do here
                                        ;
                                    });
                                }
                                break;

                            // /setinline must be set before in botfather see https://core.telegram.org/bots/inline
                            case 'inline_query':
                                var inlineQueryId = botMsg.id;
                                messageDetails = {
                                    chatId: chatid,
                                    type: 'inline_query',
                                    content: botMsg.query,
                                    inlineQueryId: inlineQueryId,
                                    offset: botMsg.offset,
                                    from: botMsg.from,
                                    location: botMsg.location // location is only available when /setinlinegeo is set in botfather
                                };
                                // Right now this is not supported as a result is required!
                                //if (node.autoAnswer) {
                                //    // result = https://core.telegram.org/bots/api#inlinequeryresult
                                //    node.telegramBot.answerInlineQuery(inlineQueryId, results).then(function (result) {
                                //        // Nothing to do here
                                //        ;
                                //    });
                                //}
                                break;

                            case 'edited_message':
                                messageDetails = {
                                    chatId: chatid,
                                    messageId: botMsg.message_id,
                                    type: "edited_message",
                                    content: botMsg.text,
                                    editDate: botMsg.edit_date,
                                    date: botMsg.date,
                                    from: botMsg.from,
                                    chat: botMsg.chat,
                                    location: botMsg.location // for live location updates
                                };
                                break;

                            // the text of an already sent message.
                            case 'edited_message_text':
                                messageDetails = {
                                    chatId: chatid,
                                    type: "edited_message_text",
                                    messageId: botMsg.message_id,
                                    content: botMsg.text,
                                    editDate: botMsg.edit_date,
                                    date: botMsg.date,
                                    chat: botMsg.chat 
                                };
                                break;

                            // the caption of a document or an image ...
                            case 'edited_message_caption':
                                messageDetails = {
                                    chatId: chatid,
                                    type: "edited_message_caption",
                                    messageId: botMsg.message_id,
                                    content: botMsg.caption,
                                    editDate: botMsg.edit_date,
                                    date: botMsg.date,
                                    chat: botMsg.chat 
                                };
                                break;

                            case 'channel_post':
                                messageDetails = {
                                    chatId: chatid,
                                    messageId: botMsg.message_id,
                                    type: "channel_post",
                                    content: botMsg.text,
                                    date: botMsg.date,
                                    chat: botMsg.chat
                                };
                                break;

                            case 'edited_channel_post':
                                messageDetails = {
                                    chatId: chatid,
                                    type: "edited_channel_post",
                                    messageId: botMsg.message_id,
                                    content: botMsg.text,
                                    editDate: botMsg.edit_date,
                                    date: botMsg.date,
                                    chat: botMsg.chat 
                                };
                                break;

                            case 'edited_channel_post_text':
                                messageDetails = {
                                    chatId: chatid,
                                    type: "edited_channel_post_text",
                                    messageId: botMsg.message_id,
                                    content: botMsg.text,
                                    editDate: botMsg.edit_date,
                                    date: botMsg.date,
                                    chat: botMsg.chat 
                                };
                                break;

                            case 'edited_channel_post_caption':
                                messageDetails = {
                                    chatId: chatid,
                                    type: "edited_channel_post_caption",
                                    messageId: botMsg.message_id,
                                    content: botMsg.caption,
                                    editDate: botMsg.edit_date,
                                    date: botMsg.date,
                                    chat: botMsg.chat 
                                };
                                break;


                            // TODO: implement those
                            // chosen_inline_result, 
                            // shippingQuery, preCheckoutQuery

                            default:
                        }

                        if (messageDetails != null) {
                            msg = {
                                payload: messageDetails,
                                originalMessage: botMsg
                            };
                            node.send(msg);
                        }
                    } else {
                        // ignoring unauthorized calls
                        if(node.config.verbose){
                            node.warn("Unauthorized incoming call from " + username);
                        }
                    }
                });
            } else {
                node.warn("bot not initialized.");
                node.status({ fill: "red", shape: "ring", text: "no bot token found in config" });
            }
        } else {
            node.warn("config node failed to initialize.");
            node.status({ fill: "red", shape: "ring", text: "config node failed to initialize" });
        }

        this.on("close", function () {
            node.telegramBot.off(this.event);
            node.status({});
        });
    }
    RED.nodes.registerType("telegram event", TelegramEventNode);



    // --------------------------------------------------------------------------------------------
    // The output node sends to the chat and passes the msg through.
    // The payload needs three fields
    // chatId  : string destination chat
    // type    : string type of message to send
    // content : message content
    // The type is a string can be any of the following:
    // message     content is String
    // photo       content is String|stream.Stream|Buffer
    // audio       content is String|stream.Stream|Buffer
    // document    content is String|stream.Stream|Buffer
    // sticker     content is String|stream.Stream|Buffer
    // video       content is String|stream.Stream|Buffer
    // voice       content is String|stream.Stream|Buffer
    // location    content is an object that contains latitude and logitude
    // contact     content is full contact object
    // mediaGroup  content is array of mediaObject
    // action      content is one of the following: 
    //                      typing, upload_photo, record_video, upload_video, record_audio, upload_audio,  
    //                      upload_document, find_location, record_video_note, upload_video_note
    function TelegramOutNode(config) {
        RED.nodes.createNode(this, config);
        var node = this;
        this.bot = config.bot;

        this.config = RED.nodes.getNode(this.bot);
        if (this.config) {
            this.config.register(node);

            node.status({ fill: "red", shape: "ring", text: "not connected" });

            node.telegramBot = this.config.getTelegramBot();
            if (node.telegramBot) {
                node.status({ fill: "green", shape: "ring", text: "connected" });
            } else {
                node.warn("bot not initialized.");
                node.status({ fill: "red", shape: "ring", text: "bot not initialized" });
            }
        } else {
            node.warn("config node failed to initialize.");
            node.status({ fill: "red", shape: "ring", text: "config node failed to initialize" });
        }

        this.hasContent = function (msg) {
            var hasContent;
            if (msg.payload.content) {
                hasContent = true;
            }
            else {
                node.warn("msg.payload.content is empty");
                hasContent = false;
            }

            return hasContent;
        }

        this.on('input', function (msg, nodeSend, nodeDone) {
            nodeSend = nodeSend || function() { node.send.apply(node,arguments) };

            node.status({ fill: "green", shape: "ring", text: "connected" });
            
            if (msg.payload) {
                if (msg.payload.chatId) {
                    if (msg.payload.type) {

                        var chatId = msg.payload.chatId;
                        var type = msg.payload.type;
                        addCaptionToMessageOptions(msg);

                        switch (type) {

                            // --------------------------------------------------------------------
                            case 'message':
                                if (this.hasContent(msg)) {
                                    // the maximum message size is 4096 so we must split the message into smaller chunks.
                                    var chunkSize = 4000;
                                    var message = msg.payload.content;

                                    var done = false;
                                    do {
                                        var messageToSend;
                                        if (message.length > chunkSize) {
                                            messageToSend = message.substr(0, chunkSize);
                                            message = message.substr(chunkSize);
                                        } else {
                                            messageToSend = message;
                                            done = true;
                                        }

                                        node.telegramBot.sendMessage(chatId, messageToSend, msg.payload.options).then(function (result) {
                                            msg.payload.content = result;
                                            msg.payload.sentMessageId = result.message_id;
                                            nodeSend(msg);
                                            if (nodeDone) {
                                                nodeDone();
                                            }
                                        }).catch(function (err) {
                                            // markdown error? try plain mode
                                            if (
                                                String(err).includes("can't parse entities in message text:") &&
                                                msg.payload.options && msg.payload.options.parse_mode === 'Markdown'
                                            ) {
                                                delete msg.payload.options.parse_mode;
                                                node.telegramBot.sendMessage(chatId, messageToSend, msg.payload.options).then(function (result) {
                                                    msg.payload.content = result;
                                                    msg.payload.sentMessageId = result.message_id;
                                                    nodeSend(msg);
                                                    if (nodeDone) {
                                                        nodeDone();
                                                    }
                                                });
                                                return;
                                            }
                                            if (nodeDone) {
                                                nodeDone(err);
                                            } else {
                                                throw err;
                                            }
                                        });

                                    } while (!done)
                                }
                                break;
                        
                            case 'photo':
                                if (this.hasContent(msg)) {
                                    node.telegramBot.sendPhoto(chatId, msg.payload.content, msg.payload.options).then(function (result) {
                                        msg.payload.content = result;
                                        msg.payload.sentMessageId = result.message_id;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;
                            case 'mediaGroup':
								if(this.hasContent(msg)) {                                    
									if(Array.isArray(msg.payload.content)) {																				
										for (var i = 0; i < msg.payload.content.length; i++) {
                                            var mediaItem = msg.payload.content[i]; 
											if(typeof mediaItem.type !== "string") {
												node.warn("msg.payload.content[" + i + "].type is not a string it is " + typeof mediaItem.type);
												break;
											}
											if(mediaItem.media === undefined) {
												node.warn("msg.payload.content[" + i + "].media is not defined");
												break;
											}
										}											
										node.telegramBot.sendMediaGroup(chatId, msg.payload.content, msg.payload.options).then(function (result) {
											msg.payload.content = result;
											msg.payload.sentMessageId = result.message_id;
											nodeSend(msg);
											if (nodeDone) {
                          nodeDone();
                      }
										});										
									} else {
										node.warn("msg.payload.content for mediaGroup is not an array of mediaItem");
									}									
                                }
                                break;
                            case 'audio':
                                if (this.hasContent(msg)) {
                                    node.telegramBot.sendAudio(chatId, msg.payload.content, msg.payload.options).then(function (result) {
                                        msg.payload.content = result;
                                        msg.payload.sentMessageId = result.message_id;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;

                            case 'document':
                                if (this.hasContent(msg)) {
                                    node.telegramBot.sendDocument(chatId, msg.payload.content, msg.payload.options).then(function (result) {
                                        msg.payload.content = result;
                                        msg.payload.sentMessageId = result.message_id;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;

                            case 'sticker':
                                if (this.hasContent(msg)) {
                                    node.telegramBot.sendSticker(chatId, msg.payload.content, msg.payload.options).then(function (result) {
                                        msg.payload.content = result;
                                        msg.payload.sentMessageId = result.message_id;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;

                            case 'video':
                                if (this.hasContent(msg)) {
                                    node.telegramBot.sendVideo(chatId, msg.payload.content, msg.payload.options).then(function (result) {
                                        msg.payload.content = result;
                                        msg.payload.sentMessageId = result.message_id;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;

                            case 'video_note':
                                if (this.hasContent(msg)) {
                                    node.telegramBot.sendVideoNote(chatId, msg.payload.content, msg.payload.options).then(function (result) {
                                        msg.payload.content = result;
                                        msg.payload.sentMessageId = result.message_id;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;

                            case 'voice':
                                if (this.hasContent(msg)) {
                                    node.telegramBot.sendVoice(chatId, msg.payload.content, msg.payload.options).then(function (result) {
                                        msg.payload.content = result;
                                        msg.payload.sentMessageId = result.message_id;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;
                            
                            case 'location':
                                if (this.hasContent(msg)) {
                                    node.telegramBot.sendLocation(chatId, msg.payload.content.latitude, msg.payload.content.longitude, msg.payload.options).then(function (result) {
                                        msg.payload.content = result;
                                        msg.payload.sentMessageId = result.message_id;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;

                            case 'venue':
                                if (this.hasContent(msg)) {
                                    node.telegramBot.sendVenue(chatId, msg.payload.content.latitude, msg.payload.content.longitude, msg.payload.content.title, msg.payload.content.address, msg.payload.options).then(function (result) {
                                        msg.payload.content = result;
                                        msg.payload.sentMessageId = result.message_id;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;

                            case 'contact':
                                if (this.hasContent(msg)) {
                                    if (msg.payload.content.last_name) {
                                        if (!msg.payload.options) {
                                            msg.payload.options = {};
                                        }
                                        msg.payload.options.last_name = msg.payload.content.last_name;
                                    }
                                    node.telegramBot.sendContact(chatId, msg.payload.content.phone_number, msg.payload.content.first_name, msg.payload.options).then(function (result) {
                                        msg.payload.content = result;
                                        msg.payload.sentMessageId = result.message_id;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;
                            // --------------------------------------------------------------------
                            
                            case 'editMessageLiveLocation':
                                if (this.hasContent(msg)) {
                                    node.telegramBot.editMessageLiveLocation(msg.payload.content.latitude, msg.payload.content.longitude, msg.payload.options).then(function (result) {
                                        msg.payload.content = result;
                                        msg.payload.sentMessageId = result.message_id;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;

                            case 'stopMessageLiveLocation':
                                // This message requires the options to be set!
                                //if (this.hasContent(msg)) {
                                    node.telegramBot.stopMessageLiveLocation(msg.payload.options).then(function (result) {
                                        msg.payload.content = result;
                                        msg.payload.sentMessageId = result.message_id;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                //}
                                break;

                            case 'editMessageCaption':
                            case 'editMessageText':
                            case 'editMessageReplyMarkup':
                                if (this.hasContent(msg)) {
                                    node.telegramBot[type](msg.payload.content, msg.payload.options).then(function (result) {
                                        msg.payload.content = result;
                                        msg.payload.sentMessageId = result.message_id;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;

                            case 'callback_query':
                                if (this.hasContent(msg)) {
                                    // The new signature expects one object instead of three arguments.
                                    var callbackQueryId = msg.payload.callbackQueryId;
                                    var options = {
                                        callback_query_id: callbackQueryId,
                                        text: msg.payload.content,
                                        show_alert: msg.payload.options
                                    };
                                    node.telegramBot.answerCallbackQuery(callbackQueryId, options).then(function (result) {
                                        msg.payload.content = result; // true if succeeded
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;

                            case 'inline_query':
                                if (this.hasContent(msg)) {
                                    var inlineQueryId = msg.payload.inlineQueryId;
                                    var results = msg.payload.results; // this type requires results to be set: see https://core.telegram.org/bots/api#inlinequeryresult
                                    node.telegramBot.answerInlineQuery(inlineQueryId, results).then(function (result) {
                                        msg.payload.content = result;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;

                            case 'action':
                                if (this.hasContent(msg)) {
                                    node.telegramBot.sendChatAction(chatId, msg.payload.content).then(function (result) {
                                        msg.payload.content = result; // true if succeeded
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;

                            // --------------------------------------------------------------------
                            // Some of the following functions require the bot to be administrator of the chat/channel

                            case 'getChatAdministrators':
                            case 'getChatMembersCount':
                            case 'getChat':
                            case 'leaveChat':
                            case 'exportChatInviteLink':
                            case 'unpinChatMessage':
                            case 'deleteChatPhoto':
                                    node.telegramBot[type](chatId).then(function (result) {
                                        msg.payload.content = result;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                break;

                            case 'kickChatMember':
                            case 'unbanChatMember':
                            case 'restrictChatMember':
                            case 'promoteChatMember':
                            case 'getChatMember':
                                // The userId must be passed in msg.payload.content: note that this is is a number not the username.
                                // Right now there is no way for resolving the user_id by username in the official API.
                                if (this.hasContent(msg)) {
                                    node.telegramBot[type](chatId, msg.payload.content, msg.payload.options).then(function (result) {
                                        msg.payload.content = result;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;

                            // --------------------------------------------------------------------
                            case 'setChatTitle':
                            case 'setChatDescription':
                            case 'pinChatMessage':
                            case 'deleteMessage':            
                                if (this.hasContent(msg)) {
                                    node.telegramBot[type](chatId, msg.payload.content).then(function (result) {
                                        msg.payload.content = result;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;
                            
                            case 'setChatPhoto':
                                if (this.hasContent(msg)) {
                                    node.telegramBot[type](chatId, msg.payload.content, msg.payload.options).then(function (result) {
                                        msg.payload.content = result;
                                        nodeSend(msg);
                                        if (nodeDone) {
                                            nodeDone();
                                        }
                                    });
                                }
                                break;
                           
                            // TODO:                            
                            // getUserProfilePhotos, getFile, 
                            // setChatStickerSet, deleteChatStickerSet
                            // sendGame, setGameScore, getGameHighScores
                            // sendInvoice, answerShippingQuery, answerPreCheckoutQuery
                            // getStickerSet, uploadStickerFile, createNewStickerSet, addStickerToSet, setStickerPositionInSet, deleteStickerFromSet

                            default:
                            // unknown type nothing to send.
                         }
                    } else {
                        node.warn("msg.payload.type is empty");
                    }
                } else {
                    node.warn("msg.payload.chatId is empty");
                }
            } else {
                node.warn("msg.payload is empty");
            }
        });
    }
    RED.nodes.registerType("telegram sender", TelegramOutNode);



    // --------------------------------------------------------------------------------------------
    // The output node receices the reply for a specified message and passes the msg through.
    // The payload needs three fields
    // chatId        : string destination chat
    // sentMessageId : string the id of the message the reply coresponds to.
    // content : message content
    function TelegramReplyNode(config) {
        RED.nodes.createNode(this, config);
        var node = this;
        this.bot = config.bot;

        this.config = RED.nodes.getNode(this.bot);
        if (this.config) {
            this.config.register(node);

            node.status({ fill: "red", shape: "ring", text: "not connected" });

            node.telegramBot = this.config.getTelegramBot();
            if (node.telegramBot) {
                node.status({ fill: "green", shape: "ring", text: "connected" });
            } else {
                node.warn("bot not initialized.");
                node.status({ fill: "red", shape: "ring", text: "bot not initialized" });
            }
        } else {
            node.warn("config node failed to initialize.");
            node.status({ fill: "red", shape: "ring", text: "config node failed to initialize" });
        }

        this.on('input', function (msg, nodeSend, nodeDone) {

            node.status({ fill: "green", shape: "ring", text: "connected" });
            
            if (msg.payload) {
                if (msg.payload.chatId) {
                    if (msg.payload.sentMessageId) {

                        var chatId = msg.payload.chatId;
                        var messageId = msg.payload.sentMessageId;

                        node.telegramBot.onReplyToMessage(chatId, messageId, function (botMsg) {

                            var messageDetails = getMessageDetails(botMsg);
                            if (messageDetails) {
                                var newMsg = { payload: messageDetails, originalMessage: botMsg };
                                nodeSend(newMsg);
                                if (nodeDone) {
                                    nodeDone();
                                }
                            }
                        });

                    } else {
                        node.warn("msg.payload.sentMessageId is empty");
                    }
                } else {
                    node.warn("msg.payload.chatId is empty");
                }
            } else {
                node.warn("msg.payload is empty");
            }
        });
    }
    RED.nodes.registerType("telegram reply", TelegramReplyNode);
}
