
const
    WebSocket = require('ws'),
    https = require('https'),
    sleep = require("sleep").sleep

    ,SlackData = require("./slackData.js").SlackData
    ,SlackHistory = require("./slackHistory.js").SlackHistory
    ,config = require("../config.js")
    ,httpsRequest = require('./httpsRequest.js').httpsRequest

    ,MeMessage = require('./message.js').MeMessage

    ,MultiChatManager = require('./multichatManager.js').MultiChatManager
;

const SLACK_ENDPOINT = "https://slack.com/api/"
    ,SLACK_HOSTNAME = "slack.com"
    ,SLACK_ENDPOINT_PATH = "/api/"
    ,GETAPI = {
        rtmStart: "rtm.start"
        ,oauth: "oauth.access"
        ,identityEmail: "users.identity"
        ,channelHistory: "channels.history"
        ,directHistory: "im.history"
        ,groupHistory: "groups.history"
        ,starChannel: "stars.add"
        ,unstarChannel: "stars.remove"
        ,starMsg: "stars.add"
        ,unstarMsg: "stars.remove"
        ,postMsg: "chat.postMessage"
        ,postMeMsg: "chat.meMessage"
        ,editMsg: "chat.update"
        ,removeMsg: "chat.delete"
        ,postFile: "files.upload"
        ,setActive: "users.setActive"
        ,setPresence: "users.setPresence"
        ,emojiList: "emoji.list"
        ,slashList: "commands.list"
        ,pinMsg: "pins.add"
        ,unpinMsg: "pins.remove"
        ,listPinned: "pins.list"
        ,listMpims: "mpim.list"
        ,slashExec: "chat.command"
        ,addReaction: "reactions.add"
        ,channelList: "users.counts"
        ,removeReaction: "reactions.remove"
        ,sendAction: "chat.attachmentAction"
        ,read: {
            group: "groups.mark"
            ,im: "im.mark"
            ,group: "mpim.mark"
            ,channel: "channels.mark"
        }
    }
    ,HISTORY_LENGTH = 35
    ,HISTORY_MAX_AGE = 10000// * 60 * 1000

    ,UPDATE_LIVE = [
        "message"
        ,"pin_added"
        ,"pin_removed"
        ,"reaction_added"
        ,"reaction_removed"
        ,"star_added"
        ,"star_removed"
    ] // Message type that affect live history
;

/**
 * @implements {ChatSystem}
**/
function Slack(slackToken, manager) {
    this.token = slackToken;
    this.manager = manager;
    this.rtm = null;
    this.rtmId = 1;
    this.data = new SlackData(this);
    this.history = {};
    this.pendingRtm = {};
    this.pendingMessages = [];
    this.msgIds = {};
    this.pendingPing = false;
    this.connected = false;
    this.closing = false;

    /** @type {array<String>} */
    this.queuedMessages = [];
}

Slack.prototype.getId = function() {
    return this.data.team ? this.data.team.id : null;
};

Slack.prototype.onRequest = function() {
    if (this.connected === false) {
        this.connect();
    }
};

Slack.prototype.sendUnsafe = function(msg) {
    this.rtm.send(msg);
};

Slack.prototype.send = function(msg, priority) {
    try {
        this.sendUnsafe(msg);
    } catch (e) {
        this.connected = false;
        console.error("[SLACK] send failed");
        if (priority)
            this.queuedMessages.push(msg);
        this.connect();
        return false;
    }
    return true;
};

Slack.prototype.connect = function(cb) {
    var _this = this;

    this.connected = undefined;
    httpsRequest(SLACK_ENDPOINT +GETAPI.rtmStart +"?batch_presence_aware=1&token=" +this.token, (status, body) => {
        if (!body || !body.ok) {
            _this.error = "Slack API error";
            _this.connected = false;
            _this.pendingPing = false;
            console.error("Slack api responded " +status +" with body " +JSON.stringify(body));
            cb && cb(_this);
        } else if (status !== 200) {
            _this.error = body.error;
            _this.connected = false;
            _this.pendingPing = false;
            console.error("Slack api responded " +status);
            cb && cb(_this);
        } else {
            httpsRequest(SLACK_ENDPOINT +GETAPI.channelList +"?mpim_aware=true" /*+"&include_threads=true"*/ +"&token=" +this.token, (status, channels) => {
                if (!channels || !channels.ok) {
                    _this.error = "Slack API error";
                    _this.connected = false;
                    _this.pendingPing = false;
                    console.error("Slack api responded " +status +" with body " +JSON.stringify(body));
                    cb && cb(_this);
                } else if (status !== 200) {
                    _this.error = body.error;
                    _this.connected = false;
                    _this.pendingPing = false;
                    console.error("Slack api responded " +status);
                    cb && cb(_this);
                } else {
                    this.data = new SlackData(this);

                    // Merge body.channels (missing unread stuff) and channels data (missing members)
                    body["channels"].forEach(function(channelData) {
                        for (var i =0, nbChans = channels["channels"].length; i < nbChans; i++) {
                            if (channels["channels"][i]["id"] === channelData["id"]) {
                                channelData["unread_count"] = channels["channels"][i]["unread_count"];
                                break;
                            }
                        }
                    });
                    body["groups"].forEach(function(channelData) {
                        for (var i =0, nbChans = channels["groups"].length; i < nbChans; i++) {
                            if (channels["groups"][i]["id"] === channelData["id"]) {
                                channelData["unread_count"] = channels["groups"][i]["unread_count"];
                                break;
                            }
                        }
                    });

                    // TODO deal with channels["threads"]

                    _this.data.updateStatic({
                        team: body["team"],
                        users: body["users"],
                        bots: body["bots"],
                        self: body["self"],
                        channels: body["channels"],
                        groups: body["groups"],
                        ims: channels["ims"],
                        mpims: channels["mpims"]
                    }, Date.now());
                    _this.connectRtm(body.url);
                }
            });
    }
    });
};

Slack.prototype.sendCommand = function(room, cmd, arg) {
    httpsRequest(
        SLACK_ENDPOINT
        +GETAPI.slashExec
        +"?token=" +this.token
        +"&command=" +encodeURIComponent(cmd.name)
        +"&disp=" +encodeURIComponent(cmd.name)
        +"&channel=" +room.remoteId
        +"&text=" +encodeURIComponent(arg));
}

Slack.prototype.sendTyping = function(room) {
    this.send('{"id":' +this.rtmId++ +',"type":"typing","channel":"' +room.remoteId +'"}');
}

Slack.prototype.getSlashCommands = function(cb) {
    httpsRequest(SLACK_ENDPOINT +GETAPI.slashList +"?token=" +this.token, (status, body) => {
        if (!status || !body || !body.ok)
            cb(null);
        else
            cb(body.commands || {});
    });
};

Slack.prototype.getEmojis = function(cb) {
    httpsRequest(SLACK_ENDPOINT +GETAPI.emojiList +"?token=" +this.token, (status, body) => {
        if (!status || !body || !body.ok)
            cb(null);
        else
            cb(body.emoji || {});
    });
};

Slack.prototype.poll = function(knownVersion, now, withTyping) {
    if (this.connected) {
        var updatedCtx = this.data.getUpdates(knownVersion)
            ,updatedTyping = withTyping ? this.data.getWhoIsTyping(knownVersion, now) : undefined
            ,updatedLive = this.getLiveUpdates(knownVersion);

        if (updatedCtx || updatedLive || updatedTyping) {
            return {
                "static": updatedCtx,
                "live": updatedLive,
                "typing": updatedTyping,
                "v": Math.max(this.data.liveV, this.data.staticV, this.data.typingVersion)
            };
        }
    }
};

/** @return {Object|undefined} */
Slack.prototype.getLiveUpdates = function(knownVersion) {
    var result = {};

    for (var roomId in this.history) {
        var history = this.history[roomId];
        if (history.isNew) {
            result[roomId] = history.toStatic(0);
            history.isNew = false;
        }
        else {
            var roomData = history.toStatic(knownVersion);
            if (roomData.length)
                result[roomId] = roomData;
        }
    }
    for (var roomId in result) {
        return result;
    }
    return undefined;
};

Slack.prototype.unstackPendingMessages = function() {
    for (var i = this.pendingMessages.length -1; i >= 0; i--) {
        this.onMessage(this.pendingMessages[0], Date.now());
        this.pendingMessages.shift();
    }
    while (this.queuedMessages.length) {
        if (this.send(this.queuedMessages[0])) {
            this.queuedMessages.shift();
        } else {
            break;
        }
    }
};

Slack.prototype.resetVersions = function(v) {
    this.data.team.version = v;
    for (var i in this.data.channels)
        this.data.channels[i].version = v;
    for (var i in this.data.users)
        this.data.users[i].version = v;
    if (this.data.self && this.data.self.prefs)
        this.data.self.prefs.version = v;
    this.data.staticV = v;
};

Slack.prototype.onMessage = function(msg, t) {
    if (msg["reply_to"] !== undefined) {
        let rtmId = msg["reply_to"];
        if (this.pendingRtm[rtmId]) {
            /*
            let ts = msg["ts"]
            msg = this.pendingRtm[rtmId];
            msg["ts"] = ts;
            msg["pendingId"] = this.data.team.id +'_' +rtmId;
            */
            for (var i in this.pendingRtm[rtmId])
                if (msg[i] === undefined)
                    msg[i] = this.pendingRtm[rtmId][i];
            msg["pendingId"] = this.data.team.id +'_' +rtmId;
            delete this.pendingRtm[rtmId];
        } else {
            delete msg["reply_to"];
        }
    }
    if (msg["type"] === "hello" && msg["start"] && msg["start"]["rtm_start"]) {
        var _this = this;
        _this.getRawMpims((mpims) => {
            _this.getEmojis((emojis) => {
                _this.getSlashCommands((commands) => {
                    var msgContent = msg.start.rtm_start;
                    var now = Date.now();
                    msgContent.self = msg.self;
                    msgContent.emojis = emojis;
                    msgContent.commands = commands;
                    msgContent.mpims = mpims;

                    _this.resetVersions(now);
                    _this.data.updateStatic(msgContent, now);
                    _this.connected = true;
                    _this.unstackPendingMessages();
                    _this.ping();
                    MultiChatManager.onCtxUpdated(_this);
                    _this.getPresenceForAll();
                });
            });
        });
    } else if (this.connected) {
        var updated = this.data.onMessage(msg, t);
        if ((msg["channel"] || msg["channel_id"] || (msg["item"] && msg["item"]["channel"])) && msg["type"] && UPDATE_LIVE.indexOf(msg["type"]) !== -1) {
            var channelId = this.data.team.id +'|' +(msg["channel"] || msg["channel_id"] || msg["item"]["channel"])
                ,channel = this.data.channels[channelId]
                ,histo = this.lazyHistory(channel)
                ,msgRef = {};
            var lastMsg = histo.push(msg, t, msgRef);
            if (lastMsg) {
                this.data.liveV = t;
                if (msgRef.msg && msgRef.msg.userId) {
                    this.data.stopTyping(channelId, msgRef.msg.userId, t);
                    if (!msgRef.msg.pendingId) {
                        this.removeMsgFromPending(channel, msgRef.msg);
                    }
                }
            }
            histo.resort();
            if (channel)
                channel.setLastMsg(lastMsg, t);
            updated = true;
        }
        if (updated)
            MultiChatManager.onCtxUpdated(this);
    } else {
        this.pendingMessages.push(msg);
    }
};

Slack.prototype.getPresenceForAll = function() {
    var userIds = {};
    for (var i in this.data.users)
        userIds[this.data.users[i].remoteId] = null;
    this.send('{"id":' +this.rtmId++ +',"type":"presence_sub","ids":' +JSON.stringify(Object.keys(userIds))+'}');
};

Slack.prototype.removeExpiredMsgs = function(msg) {
    // TODO retry
};

Slack.prototype.removeMsgFromPending = function(channel, msg) {
    let isMe = msg instanceof MeMessage;
    for (var i in this.msgIds) {
        let j = this.msgIds[i];
        if (j.isMe === isMe && j.text === msg.text && j.channel === channel.id) {
            msg.pendingId = i;
            delete this.msgIds[i];
            return;
        }
    }
};

Slack.prototype.getRawMpims = function(cb) {
    httpsRequest(SLACK_ENDPOINT +GETAPI.listMpims
        +"?token=" +this.token, (status, content) => {
        if (status === 200 && content.ok)
            cb(content.groups);
        else
            cb(undefined);
    });
};

/**
 * @param {SlackChan|SlackGroup|SlackIms} chan
 * @param {string} id
 * @param {number} ts
**/
Slack.prototype.markRead = function(chan, id, ts) {
    var apiURI;

    if (chan.remoteId[0] === 'C')
        apiURI = SLACK_ENDPOINT+GETAPI.read.channel;
    else if (chan.remoteId[0] === 'G')
        apiURI = SLACK_ENDPOINT+GETAPI.read.group;
    else if (chan.remoteId[0] === 'D')
        apiURI = SLACK_ENDPOINT+GETAPI.read.im;

    httpsRequest(apiURI
        +"?token=" +this.token
        +"&channel="+chan.remoteId
        +"&ts="+id);
};

Slack.prototype.connectRtm = function(url, cb) {
    var _this = this;

    this.rtmId = 1;
    var protocol = url.substr(0, url.indexOf('://') +3);
    url = url.substr(protocol.length);
    url = protocol +url.substr(0, url.indexOf('/'))+
        "/?flannel=1&token=" +this.token+
        "&start_args=" +encodeURIComponent("?simple_latest=true&batch_presence_aware=1&canonical_avatars=true")
    this.rtm = new WebSocket(url);
    this.rtm.on("message", function(msg) {
        if (!_this.connected && cb) {
            cb();
        }
        try {
            msg = JSON.parse(msg);
        } catch (ex) {
            console.error("WTF invalid JSON ", msg);
        }
        _this.onMessage(msg, Date.now());
    });
    this.rtm.once("error", function(e) {
        _this.connected = false;
        _this.pendingPing = false;
        console.error(e);
        _this.close();
    });
    this.rtm.once("end", function() {
        console.error("RTM hang up");
        _this.onClose();
    });
};

Slack.prototype.onClose = function() {
    this.manager.suicide(this);
};

Slack.prototype.ping = function() {
    httpsRequest(SLACK_ENDPOINT+GETAPI.setActive
        +"?token=" +this.token);
};

Slack.prototype.rtmPing = function() {
    if (this.connected) {
        if (this.pendingPing && this.pendingRtm[this.pendingPing]) {
            // We could use reconect_url, but we could have missed something important,
            // so we restart all over again
            this.connected = false;
            this.pendingPing = false;
            console.error("[SLACK] Ping timeout");
            this.connect();
        } else {
            var rtmId = this.rtmId++;
            try {
                this.sendUnsafe('{"id":' +rtmId +',"type":"ping"}');
                this.pendingRtm[rtmId] = { type: 'ping' };
                this.pendingPing = rtmId;
            } catch (e) {
                this.connected = false;
                this.pendingPing = false;
                console.error("[SLACK] Ping timeout");
                this.connect();
            }
        }
    }
};

Slack.prototype.close = function() {
    if (!this.closing) {
        this.closing = true;
        if (this.rtm)
            this.rtm.close();
        this.onClose();
    }
};

Slack.getUserId = function(code, redirectUri, cb) {
    Slack.getOauthToken(code, redirectUri, (teamName, userId, token) => {
        httpsRequest(SLACK_ENDPOINT+GETAPI.identityEmail +"?token="+token,
            (status, resp) => {
                if (status === 200 && resp.ok && resp.user && resp.user.email) {
                    cb(resp.user.id +'_' +resp.team.id);
                } else {
                    cb(null);
                }
            });
    });
};

Slack.getOauthToken = function(code, redirectUri, cb) {
    httpsRequest(SLACK_ENDPOINT+GETAPI.oauth
        +"?client_id=" +config.services.Slack.clientId
        +"&client_secret=" +config.services.Slack.clientSecret
        +"&redirect_uri=" +encodeURIComponent(redirectUri)
        +"&code=" +code,
    (status, resp) => {
        if (status === 200 && resp.ok) {
            cb(resp["team_name"], resp["team_id"] +resp["user_id"], resp["access_token"]);
        } else {
            cb(null);
        }
    });
};

/**
 * @param {SlackChan|SlackGroup|SlackIms} channel
 * @param {string} contentType
 * @param {function(string|null)} callback
**/
Slack.prototype.openUploadFileStream = function(channel, contentType, callback) {
    var req = https.request({
        hostname: SLACK_HOSTNAME
        ,method: 'POST'
        ,path: SLACK_ENDPOINT_PATH +GETAPI.postFile
            +"?token=" +this.token
            +"&channels=" +channel.remoteId
        ,headers: {
            "Content-Type": contentType
        }
    }, (res) => {
        var errorJson;
        res.on("data", (chunk) => {
            errorJson = errorJson ? Buffer.concat([errorJson, chunk], errorJson.length +chunk.length) : Buffer.from(chunk);
        });
        res.once("end", () => {
            if (res.statusCode === 200) {
                callback(null);
            } else {
                try {
                    errorJson = JSON.parse(errorJson.toString());
                } catch(e) {
                    callback("error");
                    return;
                }
                callback(errorJson["error"] || "error");
            }
        });
    });
    return req;
};

function findBoundary() {
    const prefix = '-'.repeat(15)
        ,alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
        ,doYouKnowWhoManyTheyAre = alphabet.length // 26 letters in da alphabet
        ,nbArg = arguments.length;

    for (let i =0; i < doYouKnowWhoManyTheyAre; i++)
        bLoop: for (let j =0; j < doYouKnowWhoManyTheyAre; j++) {
            const boundary = prefix +alphabet[i] +alphabet[j];
            for (let argIndex =0; argIndex < nbArg; argIndex++) {
                if (arguments[argIndex].indexOf(boundary) >= 0)
                    continue bLoop;
            }
            return boundary;
        }
}

function encodeWithBoundary(boundary, data) {
    var resp = "";
    for (var k in data) {
        resp += '--' +boundary +'\r\n';
        resp += 'Content-Disposition: form-data; name="' +k +'"\r\n\r\n'
            +data[k]
            +'\r\n';
    };
    return resp +'--' +boundary +'--\r\n';
}

/**
 * @param {string} serviceId
 * @param {Object} payload
 * @param {function(string|null)=} callback
**/
Slack.prototype.sendAction = function(serviceId, payload, callback) {
    var channel = this.data.channels[payload["channel_id"]]
        ,service = this.data.users[serviceId];

    if (channel && service) {
        payload["channel_id"] = channel.remoteId;
        var payloadString = JSON.stringify(payload)
            ,boundary = findBoundary(service.remoteId, payloadString)
            ,body = encodeWithBoundary(boundary, {
                "service_id": service.remoteId
                ,"payload": payloadString
            });

        var req = https.request({
            hostname: SLACK_HOSTNAME
            ,method: 'POST'
            ,path: SLACK_ENDPOINT_PATH +GETAPI.sendAction +"?token=" +this.token
            ,headers: {
                "Content-Type": "multipart/form-data; boundary=" +boundary,
                "Content-Length": body.length
            }
        }, (res) => {
            if (callback) {
                var resp = [];
                res.on("data", (chunk) => { resp.push(chunk); });
                res.once("end", () => {
                    resp = Buffer.concat(resp).toString();
                    try {
                        resp = JSON.parse(resp);
                    } catch (e) {
                        resp = null;
                    }
                    callback(resp && resp.ok ? resp : false);
                });
            }
        });
        req.end(body);
        return true;
    }
    return false;
};

/**
 * @param {SlackChan|SlackGroup|SlackIms} channel
 * @param {string} contentType
 * @param {function(string|null)} callback
**/
Slack.prototype.openUploadFileStream = function(channel, contentType, callback) {
    var req = https.request({
        hostname: SLACK_HOSTNAME
        ,method: 'POST'
        ,path: SLACK_ENDPOINT_PATH +GETAPI.postFile
            +"?token=" +this.token
            +"&channels=" +channel.remoteId
        ,headers: {
            "Content-Type": contentType
        }
    }, (res) => {
        var errorJson;
        res.on("data", (chunk) => {
            errorJson = errorJson ? Buffer.concat([errorJson, chunk], errorJson.length +chunk.length) : Buffer.from(chunk);
        });
        res.once("end", () => {
            if (res.statusCode === 200) {
                callback(null);
            } else {
                try {
                    errorJson = JSON.parse(errorJson.toString());
                } catch(e) {
                    callback("error");
                    return;
                }
                callback(errorJson["error"] || "error");
            }
        });
    });
    return req;
};

/**
 * @param {SlackChan|SlackGroup|SlackIms} channel
 * @param {string} msgId
 * @param {string} reaction
**/
Slack.prototype.addReaction = function(channel, msgId, reaction) {
    httpsRequest(SLACK_ENDPOINT +GETAPI.addReaction
        +"?token=" +this.token
        +"&name=" +encodeURIComponent(reaction)
        +"&channel="+channel.remoteId
        +"&timestamp="+msgId);
}

/**
 * @param {SlackChan|SlackGroup|SlackIms} channel
 * @param {string} msgId
 * @param {string} reaction
**/
Slack.prototype.removeReaction = function(channel, msgId, reaction) {
    httpsRequest(SLACK_ENDPOINT +GETAPI.removeReaction
        +"?token=" +this.token
        +"&name=" +reaction
        +"&channel="+channel.remoteId
        +"&timestamp="+msgId);
}

/**
 * @param {SlackChan|SlackGroup|SlackIms} channel
 * @param {Array.<string>} text
 * @return {string} sent msg
**/
Slack.prototype.sendMeMsg = function(channel, text) {
    var text = idify(this.data, text.join("\n"));
    httpsRequest(SLACK_ENDPOINT +GETAPI.postMeMsg
        +"?token=" +this.token
        +"&channel=" +channel.remoteId
        +"&text=" +encodeURIComponent(text)
        +"&as_user=true");
    let now = Date.now(),
        id = channel.id +'_' +text +'_' +now;
    this.msgIds[id] = {
        channel: channel.id,
        isMe: true,
        text: text,
        retry: 0,
        t: now
    };
    return id;
};

/**
 * @param {SlackChan|SlackGroup|SlackIms} channel
**/
Slack.prototype.starChannel = function(channel) {
    httpsRequest(SLACK_ENDPOINT +GETAPI.starChannel
        +"?token=" +this.token
        +"&channel=" +channel.remoteId);
};

/**
 * @param {SlackChan|SlackGroup|SlackIms} channel
**/
Slack.prototype.unstarChannel = function(channel) {
    httpsRequest(SLACK_ENDPOINT +GETAPI.unstarChannel
        +"?token=" +this.token
        +"&channel=" +channel.remoteId);
};

/**
 * @param {SlackChan|SlackGroup|SlackIms} channel
 * @param {string} msgId
**/
Slack.prototype.starMsg = function(channel, msgId) {
    httpsRequest(SLACK_ENDPOINT +GETAPI.starChannel
        +"?token=" +this.token
        +"&channel=" +channel.remoteId
        +"&timestamp=" +msgId);
};

/**
 * @param {SlackChan|SlackGroup|SlackIms} channel
 * @param {string} msgId
**/
Slack.prototype.unstarMsg = function(channel, msgId) {
    httpsRequest(SLACK_ENDPOINT +GETAPI.unstarChannel
        +"?token=" +this.token
        +"&channel=" +channel.remoteId
        +"&timestamp=" +msgId);
};

/**
 * @param {SlackChan|SlackGroup|SlackIms} channel
 * @param {string} msgId
**/
Slack.prototype.pinMsg = function(channel, msgId) {
    httpsRequest(SLACK_ENDPOINT +GETAPI.pinMsg
        +"?token=" +this.token
        +"&channel=" +channel.remoteId
        +"&timestamp=" +msgId);
};

/**
 * @param {SlackChan|SlackGroup|SlackIms} channel
 * @param {string} msgId
**/
Slack.prototype.unpinMsg = function(channel, msgId) {
    httpsRequest(SLACK_ENDPOINT +GETAPI.unpinMsg
        +"?token=" +this.token
        +"&channel=" +channel.remoteId
        +"&timestamp=" +msgId);
};

function idify(data, str) {
    return str.replace(/(^|\s)@(\S+)/g, function(match, p1, userName) {
        for (var i in data.users) {
            if (data.users[i].getName() === userName) {
                return p1 +"<@" +data.users[i].remoteId +'|' +userName +'>';
            }
        }
        return match;
    }).replace(/(^|\s)#(\S+)/g, function(match, p1, chanName) {
        for (var i in data.channels) {
            if (data.channels[i].name === chanName) {
                return p1 +"<#" +data.channels[i].remoteId +'|' +chanName +'>';
            }
        }
        return match;
    });
}

/**
 * @param {SlackChan|SlackGroup|SlackIms} channel
 * @param {Array.<string>} text
 * @param {Array.<Object>=} attachments
 * @return {string} sent message
**/
Slack.prototype.sendMsg = function(channel, text, attachments) {
    if (attachments) {
        attachments.forEach((attachmentObj) => {
            if (attachmentObj["ts"])
                attachmentObj["ts"] /= 1000;
        });
        var decodedText = idify(this.data, text.join("\n"));

        httpsRequest(SLACK_ENDPOINT +GETAPI.postMsg
            +"?token=" +this.token
            +"&channel=" +channel.remoteId
            +"&text=" +decodedText
            +"&link_names=true"
            + (attachments ? ("&attachments=" +encodeURIComponent(JSON.stringify(attachments))) : "")
            +"&as_user=true");
        let now = Date.now(),
            msgId = channel.id +'_' +decodedText +'_' +now;
        this.msgIds[id] = {
            channel: channel.id,
            isMe: false,
            text: decodedText,
            retry: 0,
            t: now
        };
        return msgId;
    } else {
        var fullDecodedText = idify(this.data, text.join("\n")),
            rtmId = this.rtmId++;
        this.pendingRtm[rtmId] = {
            type: 'message',
            channel: channel.remoteId,
            user: this.data.self.remoteId,
            text: fullDecodedText
        };
        this.send('{"id":' +rtmId +',"type":"message","channel":"' +channel.remoteId +'", "text":' +JSON.stringify(fullDecodedText) +'}', true);
        return this.data.team.id +'_' +rtmId;
    }
};

/**
 * @param {SlackChan|SlackGroup|SlackIms} channel
 * @param {string} msgId
**/
Slack.prototype.removeMsg = function(channel, msgId) {
    httpsRequest(SLACK_ENDPOINT +GETAPI.removeMsg
        +"?token=" +this.token
        +"&channel=" +channel.remoteId
        +"&ts=" +msgId
        +"&as_user=true");
};

/**
 * @param {SlackChan|SlackGroup|SlackIms} channel
 * @param {string} msgId
 * @param {string} text
**/
Slack.prototype.editMsg = function(channel, msgId, text) {
    httpsRequest(SLACK_ENDPOINT +GETAPI.editMsg
        +"?token=" +this.token
        +"&channel=" +channel.remoteId
        +"&ts=" +msgId
        +"&text=" +encodeURIComponent(idify(this.data, text.join("\n")))
        +"&as_user=true");
};

/**
 * @param {SlackChan|SlackGroup|SlackIms} target
**/
Slack.prototype.lazyHistory = function(target) {
    var histo = this.history[target.id];
    if (!histo) {
        histo = this.history[target.id] = new SlackHistory(this, target.remoteId, target.id, this.data.team.id +'|', HISTORY_LENGTH, HISTORY_MAX_AGE);
        histo.isNew = true;
    }
    return histo;
};

/**
 * @param {SlackChan|SlackGroup|SlackIms} target
**/
Slack.prototype.fetchPinned = function(target) {
    var _this = this;

    httpsRequest(SLACK_ENDPOINT +GETAPI.listPinned
        +"?token="+this.token
        +"&channel=" +target.remoteId,
    (status, resp) => {
        if (status === 200 && resp && resp.ok) {
            var msgs = [],
                histo = _this.lazyHistory(target);
                now = Date.now();

            resp.items.forEach(function(msg) {
                if (msg.message)
                    msgs.push(histo.messageFactory(msg.message, now));
                // Else file
            });
            target.pins = msgs;
            target.version = Math.max(target.version, now);
            _this.data.staticV = Math.max(_this.data.staticV, now);
        }
    });
};

/**
 * @param {SlackChan|SlackGroup|SlackIms} target
**/
Slack.prototype.fetchHistory = function(target, cb, count, firstMsgId) {
    var _this = this
        ,baseUrl = ""
        ,targetId = target.remoteId;

    if (targetId[0] === 'D') {
        baseUrl = SLACK_ENDPOINT +GETAPI.directHistory;
    } else if (targetId[0] === 'C') {
        baseUrl = SLACK_ENDPOINT +GETAPI.channelHistory;
    } else if (targetId[0] === 'G') {
        baseUrl = SLACK_ENDPOINT +GETAPI.groupHistory;
    }
    httpsRequest(baseUrl
        +"?token="+this.token
        +"&channel=" +targetId
        +"&include_pin_count=true"
        +(firstMsgId ? ("&inclusive=true&latest=" +firstMsgId) : "")
        +"&count=" +(count || 100),
    (status, resp) => {
        var history = [];
        if (status === 200 && resp && resp.ok) {
            var histo = this.lazyHistory(target);

            resp.messages.forEach((respMsg) => {
                respMsg["id"] = respMsg["ts"];
                history.push(histo.messageFactory(histo.prepareMessage(respMsg)));
            });
            if (!target.pins || target.pins.length !== resp["pin_count"]) {
                if (!resp["pin_count"]) {
                    target.pins = [];
                    target.version = Math.max(target.version, Date.now());
                    _this.data.staticV = Math.max(_this.data.staticV, target.version);
                } else {
                    _this.fetchPinned(target);
                }
            }
        }
        cb(history);
    });
};

Slack.prototype.getChatContext = function() {
    return this.data;
};

module.exports.Slack = Slack;

