/* vim: ts=4:sw=4
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

'use strict';
describe('Protocol', function() {

    describe('Unencrypted PushMessageProto "decrypt"', function() {
        //exclusive
        it('works', function(done) {
            localStorage.clear();

            var text_message = new textsecure.protobuf.PushMessageContent();
            text_message.body = "Hi Mom";
            var server_message = {
                type: 4, // unencrypted
                source: "+19999999999",
                timestamp: 42,
                message: text_message.encode()
            };

            return textsecure.protocol.handleIncomingPushMessageProto(server_message).
                then(function(message) {
                    assert.equal(message.body, text_message.body);
                    assert.equal(message.attachments.length, text_message.attachments.length);
                    assert.equal(text_message.attachments.length, 0);
                }).then(done).catch(done);
        });
    });


    describe("Identity and Pre Key Creation", function() {
        this.timeout(200000);
        before(function() { localStorage.clear(); });
        after(function()  { localStorage.clear(); });
        it ('works', function(done) {
            localStorage.clear();
            return textsecure.protocol.generateKeys().then(function() {
                assert.isDefined(textsecure.storage.getEncrypted("25519KeyidentityKey"));
                assert.isDefined(textsecure.storage.getEncrypted("25519KeysignedKey0"));
                for (var i = 0; i < 100; i++) {
                    assert.isDefined(textsecure.storage.getEncrypted("25519KeypreKey" + i));
                }
                var origIdentityKey = getString(textsecure.storage.getEncrypted("25519KeyidentityKey").privKey);
                return textsecure.protocol.generateKeys().then(function() {
                    assert.isDefined(textsecure.storage.getEncrypted("25519KeyidentityKey"));
                    assert.equal(getString(textsecure.storage.getEncrypted("25519KeyidentityKey").privKey), origIdentityKey);

                    assert.isDefined(textsecure.storage.getEncrypted("25519KeysignedKey0"));
                    assert.isDefined(textsecure.storage.getEncrypted("25519KeysignedKey1"));

                    for (var i = 0; i < 200; i++) {
                        assert.isDefined(textsecure.storage.getEncrypted("25519KeypreKey" + i));
                    }

                    return textsecure.protocol.generateKeys().then(function() {
                        assert.isDefined(textsecure.storage.getEncrypted("25519KeyidentityKey"));
                        assert.equal(getString(textsecure.storage.getEncrypted("25519KeyidentityKey").privKey), origIdentityKey);

                        assert.isUndefined(textsecure.storage.getEncrypted("25519KeysignedKey0"));
                        assert.isDefined(textsecure.storage.getEncrypted("25519KeysignedKey1"));
                        assert.isDefined(textsecure.storage.getEncrypted("25519KeysignedKey2"));

                        for (var i = 0; i < 300; i++) {
                            assert.isDefined(textsecure.storage.getEncrypted("25519KeypreKey" + i));
                        }
                    });
                });
            }).then(done).catch(done);
        });
    });

    describe("Axolotl", function() {
        var runAxolotlTest = function(v) {
            var origCreateNewKeyPair = textsecure.crypto.testing_only.createNewKeyPair;
            var doStep;
            var stepDone;

            stepDone = function(res) {
                if (!res || privKeyQueue.length != 0 || Object.keys(getKeysForNumberMap).length != 0 || Object.keys(messagesSentMap).length != 0) {
                    textsecure.crypto.testing_only.createNewKeyPair = origCreateNewKeyPair;
                    return false;
                } else if (step == v.length) {
                    textsecure.crypto.testing_only.createNewKeyPair = origCreateNewKeyPair;
                    return true;
                } else
                    return doStep().then(stepDone);
            }

            var privKeyQueue = [];
            textsecure.crypto.testing_only.createNewKeyPair = function(isIdentity) {
                if (privKeyQueue.length == 0 || isIdentity)
                    throw new Error('Out of private keys');
                else {
                    var privKey = privKeyQueue.shift();
                    return textsecure.crypto.createKeyPair(privKey).then(function(keyPair) {
                        var a = btoa(getString(keyPair.privKey)); var b = btoa(getString(privKey));
                        if (getString(keyPair.privKey) != getString(privKey))
                            throw new Error('Failed to rederive private key!');
                        else
                            return keyPair;
                    });
                }
            }

            var step = 0;
            var doStep = function() {
                var data = v[step][1];

                switch(v[step++][0]) {
                case "receiveMessage":
                    var postLocalKeySetup = function() {
                        if (data.newEphemeralKey !== undefined)
                            privKeyQueue.push(data.newEphemeralKey);

                        var message = new textsecure.protobuf.IncomingPushMessageSignal();
                        message.type = data.type;
                        message.source = "SNOWDEN";
                        message.message = data.message;
                        message.sourceDevice = 1;
                        try {
                            var proto = textsecure.protobuf.IncomingPushMessageSignal.decode(message.encode());
                            return textsecure.protocol.handleIncomingPushMessageProto(proto).then(function(res) {
                                if (data.expectTerminateSession)
                                    return res.flags == textsecure.protobuf.PushMessageContent.Flags.END_SESSION;
                                return res.body == data.expectedSmsText;
                            }).catch(function(e) {
                                if (data.expectException)
                                    return true;
                                throw e;
                            });
                        } catch(e) {
                            if (data.expectException)
                                return Promise.resolve(true);
                            throw e;
                        }
                    }

                    if (data.ourIdentityKey !== undefined)
                        return textsecure.crypto.createKeyPair(data.ourIdentityKey).then(function(keyPair) {
                            textsecure.storage.putEncrypted("25519KeyidentityKey", keyPair);
                            return textsecure.crypto.createKeyPair(data.ourSignedPreKey).then(function(keyPair) {
                                textsecure.storage.putEncrypted("25519KeysignedKey" + data.signedPreKeyId, keyPair);

                                if (data.ourPreKey !== undefined)
                                    return textsecure.crypto.createKeyPair(data.ourPreKey).then(function(keyPair) {
                                        textsecure.storage.putEncrypted("25519KeypreKey" + data.preKeyId, keyPair);
                                        return postLocalKeySetup();
                                    });
                                else
                                    return postLocalKeySetup();
                            });
                        });
                    else
                        return postLocalKeySetup();

                case "sendMessage":
                    var postLocalKeySetup = function() {
                        if (data.registrationId !== undefined)
                            textsecure.storage.putUnencrypted("registrationId", data.registrationId);

                        if (data.getKeys !== undefined)
                            getKeysForNumberMap["SNOWDEN"] = data.getKeys;

                        var checkMessage = function() {
                            var msg = messagesSentMap["SNOWDEN.1"];
                            delete messagesSentMap["SNOWDEN.1"];
                            //XXX: This should be all we do: isEqual(data.expectedCiphertext, msg.body, false);
                            if (msg.type == 1) {
                                var expected = getString(data.expectedCiphertext);
                                var decoded = textsecure.protobuf.WhisperMessage.decode(expected.substring(1, expected.length - 8), 'binary');
                                var result = getString(msg.body);
                                return getString(decoded.encode()) == result.substring(1, result.length - 8);
                            } else {
                                var decoded = textsecure.protobuf.PreKeyWhisperMessage.decode(getString(data.expectedCiphertext).substr(1), 'binary');
                                var result = getString(msg.body).substring(1);
                                return getString(decoded.encode()) == result;
                            }
                        }

                        if (data.endSession)
                            return textsecure.messaging.closeSession("SNOWDEN").then(checkMessage);
                        else
                            return textsecure.messaging.sendMessageToNumber("SNOWDEN", data.smsText, []).then(checkMessage);
                    }

                    if (data.ourBaseKey !== undefined)
                        privKeyQueue.push(data.ourBaseKey);
                    if (data.ourEphemeralKey !== undefined)
                        privKeyQueue.push(data.ourEphemeralKey);

                    if (data.ourIdentityKey !== undefined)
                        return textsecure.crypto.createKeyPair(data.ourIdentityKey).then(function(keyPair) {
                            textsecure.storage.putEncrypted("25519KeyidentityKey", keyPair);
                            return postLocalKeySetup();
                        });
                    else
                        return postLocalKeySetup();

                default:
                    return Promise.resolve(false);
                }
            }
            return doStep().then(stepDone);
        };

        describe("test vectors", function() {
            _.each(axolotlTestVectors, function(t, i) {
                it(t.name, function(done) {
                    localStorage.clear();
                    return runAxolotlTest(t.vectors).then(function(res) {
                        assert(res);
                    }).then(done).catch(done);
                });
            });
        });
    });
});