/**
 * Copyright JS Foundation and other contributors, http://js.foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

var should = require("should");
var batchNode = require("nr-test-utils").require("@node-red/nodes/core/logic/19-batch.js");
var helper = require("node-red-node-test-helper");
var RED = require("nr-test-utils").require("node-red/lib/red.js");

describe('BATCH node', function() {
    this.timeout(8000);

    before(function(done) {
        helper.startServer(done);
    });

    after(function(done) {
        helper.stopServer(done);
    });

    afterEach(function() {
        helper.unload();
        RED.settings.nodeMessageBufferMaxLength = 0;
    });

    it('should be loaded with defaults', function(done) {
        var flow = [{id:"n1", type:"batch", name: "BatchNode", wires:[["n2"]]},
                    {id:"n2", type:"helper"}];
        helper.load(batchNode, flow, function() {
            var n1 = helper.getNode("n1");
            n1.should.have.property('name', 'BatchNode');
            done();
        });
    });

    function check_parts(msg, id, idx, count) {
        msg.should.have.property("parts");
        var parts = msg.parts;
        parts.should.have.property("id", id);
        parts.should.have.property("index", idx);
        parts.should.have.property("count", count);
    }

    function check_data(n1, n2, results, done) {
        var id = undefined;
        var ix0 = 0; // seq no
        var ix1 = 0; // loc. in seq
        var seq = undefined;
        var msgs = [];
        n2.on("input", function(msg) {
            try {
                for (var i = 0; i < msgs.length; i++) {
                    msg.should.not.equal(msgs[i]);
                }
                msgs.push(msg);
                if (seq === undefined) {
                    seq = results[ix0];
                }
                var val = seq[ix1];
                msg.should.have.property("payload", val);
                if (id === undefined) {
                    id = msg.parts.id;
                }
                check_parts(msg, id, ix1, seq.length);
                ix1++;
                if (ix1 === seq.length) {
                    ix0++;
                    ix1 = 0;
                    seq = undefined;
                    id = undefined;
                    if (ix0 === results.length) {
                        done();
                    }
                }
            }
            catch (e) {
                done(e);
            }
        });
    }

    function check_count(flow, results, done) {
        try {
            helper.load(batchNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                check_data(n1, n2, results, done);
                for(var i = 0; i < 6; i++) {
                    n1.receive({payload: i});
                }
            });
        }
        catch (e) {
            done(e);
        }
    }

    function delayed_send(receiver, index, count, delay) {
        if (index < count) {
            setTimeout(function() {
                receiver.receive({payload: index});
                delayed_send(receiver, index+1, count, delay);
            }, delay);
        }
    }

    function check_interval(flow, results, delay, done) {
        helper.load(batchNode, flow, function() {
            var n1 = helper.getNode("n1");
            var n2 = helper.getNode("n2");
            check_data(n1, n2, results, done);
            delayed_send(n1, 0, 4, delay);
        });
    }

    function check_concat(flow, results, inputs, done) {
        try {
            helper.load(batchNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                check_data(n1, n2, results, done);
                for(var data of inputs) {
                    var msg = {
                        topic: data[0],
                        payload: data[1],
                        parts: {
                            id: data[0],
                            index: data[2],
                            count: data[3]
                        }
                    };
                    n1.receive(msg);
                }
            });
        }
        catch (e) {
            done(e);
        }
    }

    describe('mode: count', function() {

        it('should create seq. with count', function(done) {
            var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 2, overlap: 0, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]},
                        {id:"n2", type:"helper"}];
            var results = [
                [0, 1],
                [2, 3],
                [4, 5]
            ];
            check_count(flow, results, done);
        });

        it('should create seq. with count and overlap', function(done) {
            var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 3, overlap: 2, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]},
                        {id:"n2", type:"helper"}];
            var results = [
                [0, 1, 2],
                [1, 2, 3],
                [2, 3, 4],
                [3, 4, 5]
            ];
            check_count(flow, results, done);
        });

        it('should handle too many pending messages', function(done) {
            var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 5, overlap: 0, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]},
                        {id:"n2", type:"helper"}];
            helper.load(batchNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                RED.settings.nodeMessageBufferMaxLength = 2;
                setTimeout(function() {
                    var logEvents = helper.log().args.filter(function (evt) {
                        return evt[0].type == "batch";
                    });
                    var evt = logEvents[0][0];
                    evt.should.have.property('id', "n1");
                    evt.should.have.property('type', "batch");
                    evt.should.have.property('msg', "batch.too-many");
                    done();
                }, 150);
                for(var i = 0; i < 3; i++) {
                    n1.receive({payload: i});
                }
            });
        });

    });

    describe('mode: interval', function() {

        it('should create seq. with interval', function(done) {
            var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [], wires:[["n2"]]},
                        {id:"n2", type:"helper"}];
            var results = [
                [0, 1],
                [2, 3]
            ];
            check_interval(flow, results, 450, done);
        });

        it('should create seq. with interval (in float)', function(done) {
            var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 0.5, allowEmptySequence: false, topics: [], wires:[["n2"]]},
                        {id:"n2", type:"helper"}];
            var results = [
                [0, 1],
                [2, 3]
            ];
            check_interval(flow, results, 225, done);
        });

        it('should create seq. with interval & not send empty seq', function(done) {
            var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [], wires:[["n2"]]},
                        {id:"n2", type:"helper"}];
            var results = [
                // 1300, 2600, 3900, 5200,
                [0], [1], [2], [3]
            ];
            check_interval(flow, results, 1300, done);
        });

        it('should create seq. with interval & send empty seq', function(done) {
            var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 1, allowEmptySequence: true, topics: [], wires:[["n2"]]},
                        {id:"n2", type:"helper"}];
            var results = [
                // 1300, 2600, 3900, 5200,
                [null], [0], [1], [2], [null], [3]
            ];
            check_interval(flow, results, 1300, done);
        });

        it('should handle too many pending messages', function(done) {
            var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [], wires:[["n2"]]},
                        {id:"n2", type:"helper"}];
            helper.load(batchNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                RED.settings.nodeMessageBufferMaxLength = 2;
                setTimeout(function() {
                    var logEvents = helper.log().args.filter(function (evt) {
                        return evt[0].type == "batch";
                    });
                    var evt = logEvents[0][0];
                    evt.should.have.property('id', "n1");
                    evt.should.have.property('type', "batch");
                    evt.should.have.property('msg', "batch.too-many");
                    done();
                }, 150);
                for(var i = 0; i < 3; i++) {
                    n1.receive({payload: i});
                }
            });
        });

    });

    describe('mode: concat', function() {

        it('should concat two seq. (series)', function(done) {
            var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}], wires:[["n2"]]},
                        {id:"n2", type:"helper"}];
            var results = [
                [2, 3, 0, 1]
            ];
            var inputs = [
                ["TB", 0, 0, 2],
                ["TB", 1, 1, 2],
                ["TA", 2, 0, 2],
                ["TA", 3, 1, 2]
            ];
            check_concat(flow, results, inputs, done);
        });

        it('should concat two seq. (mixed)', function(done) {
            var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}], wires:[["n2"]]},
                        {id:"n2", type:"helper"}];
            var results = [
                [2, 3, 0, 1]
            ];
            var inputs = [
                ["TA", 2, 0, 2],
                ["TB", 0, 0, 2],
                ["TA", 3, 1, 2],
                ["TB", 1, 1, 2]
            ];
            check_concat(flow, results, inputs, done);
        });

        it('should concat three seq.', function(done) {
            var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}, {topic: "TC"}], wires:[["n2"]]},
                        {id:"n2", type:"helper"}];
            var results = [
                [2, 3, 0, 1, 4]
            ];
            var inputs = [
                ["TC", 4, 0, 1],
                ["TB", 0, 0, 2],
                ["TB", 1, 1, 2],
                ["TA", 2, 0, 2],
                ["TA", 3, 1, 2]
            ];
            check_concat(flow, results, inputs, done);
        });

        it('should concat same seq.', function(done) {
            var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TA"}], wires:[["n2"]]},
                        {id:"n2", type:"helper"}];
            var results = [
                [9, 8, 9, 8]
            ];
            var inputs = [
                ["TA", 9, 0, 2],
                ["TA", 8, 1, 2]
            ];
            check_concat(flow, results, inputs, done);
        });

        it('should handle too many pending messages', function(done) {
            var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}], wires:[["n2"]]},
                        {id:"n2", type:"helper"}];
            helper.load(batchNode, flow, function() {
                var n1 = helper.getNode("n1");
                var n2 = helper.getNode("n2");
                RED.settings.nodeMessageBufferMaxLength = 2;
                setTimeout(function() {
                    var logEvents = helper.log().args.filter(function (evt) {
                        return evt[0].type == "batch";
                    });
                    var evt = logEvents[0][0];
                    evt.should.have.property('id', "n1");
                    evt.should.have.property('type', "batch");
                    evt.should.have.property('msg', "batch.too-many");
                    done();
                }, 150);
                var C = 3;
                for(var i = 0; i < C; i++) {
                    var parts_a = {index:i, count:C, id:"A"};
                    var parts_b = {index:i, count:C, id:"B"};
                    n1.receive({payload: i, topic: "TA", parts:parts_a});
                    n1.receive({payload: i, topic: "TB", parts:parts_b});
                }
            });
        });

    });

});