/**
 * Copyright 2017 Bart Butenaers
 *
 * 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.
 **/
module.exports = function(RED) {
    "use strict";
    var CircularBuffer = require("circular-buffer");
    var Math = require("mathjs");

    function speed(config) {
        RED.nodes.createNode(this, config);
        
        this.estimationStartup = config.estimation || false;
        this.ignoreStartup = config.ignore || false;

        // The buffer size depends on the frequency
        switch(config.frequency) {
            case 'sec':
                this.bufferSize = 1; 
                break;
            case 'min':
                this.bufferSize = 60;
                break;
            case 'hour':
                this.bufferSize = 60 * 60;
                break;
            default:
                this.bufferSize = 1; // Default 'sec'
        }
		
        this.circularBuffer = new CircularBuffer(this.bufferSize);
        
        this.frequency = config.frequency || 'sec';
        this.msgCount = 0;
        this.prevStartup = false;
        this.prevTotalMsgCount = 0;
        this.totalMsgCount = 0;
        this.startTime = null; // When the first (unprocessed) message has arrived
        this.endTime = null; // When the last (unprocessed) message has arrived
        this.timer = null;

        var analyse = function() {
            if (node.startTime == null) {
                node.startTime = new Date().getTime();
            } 
            
            // Register the time when the last message has arrived (or when the last timer was called, when no message has arrived)
            node.endTime = new Date().getTime();
                        
            // Calculate the time interval (in seconds) since the first (unprocessed) message has arrived
            var seconds = (node.endTime - node.startTime) / 1000;
            var remainder = (seconds - Math.floor(seconds)) * 1000;
            seconds = Math.floor(seconds);
            
            // Correct the end time with the remainder, since the time interval since the last message (until now) is skipped in the current calculation.
            // Otherwise timeslices will get behind, and the curve would have some overshoot (at startup) period before reaching the final speed.
            node.endTime -= remainder;
            
            //console.log(seconds + " seconds between " + new Date(node.startTime).toISOString().slice(11, 23) + " and " + new Date(node.endTime).toISOString().slice(11, 23));
            
            // Store the message count (of the previous second) in the circular buffer.  However the timer can be
            // delayed, so make sure this is done for EVERY second since the last time we got here ...
            // 10 images/2,5 seconds = 240 images/second           
            for(var i = 0; i < seconds; i++) {
                var original = node.totalMsgCount;
                // In the first second we store (the count of) all received messages, except the last one (that has been received in the next second).
                // In the next second we store that remaining (single) last message.  In all later seconds (of this loop) we will store 0.
                var added = (i == 0) ? Math.max(0, node.msgCount - 1) : Math.max(0, node.msgCount);

                // Check the content of the tail buffer cell (before it is being removed by inserting a new cell at the head), if available already
                var removed = (node.circularBuffer.size() >= node.bufferSize) ? node.circularBuffer.get(node.bufferSize-1) : 0;
                
                // The total msg count is the sum of all message counts in the circular buffer.  Instead of summing all those
                // buffer cells continiously (over and over again), we will update the sum together with the buffer content.
                // Sum = previous sum + message count of last second (which is going to be added to the buffer) 
                //                    - message count of the first second (which is going to be removed from the buffer).
                node.totalMsgCount = original + added - removed;
                //console.log(node.totalMsgCount + " = " + original + " + " + added + " - " + removed);
                
                // Store the new count in the circular buffer (which will also trigger deletion of the oldest cell at the buffer trail)
                node.circularBuffer.enq(added); 
                
                var totalMsgCount = node.totalMsgCount;
                var startup = false;
                
                // Do a linear interpolation if required (only relevant in the startup period)
                if (node.circularBuffer.size() < node.circularBuffer.capacity()) {
                     startup = true;

                     if (node.estimationStartup == true && node.circularBuffer.size() > 0) {
                        totalMsgCount = Math.floor(totalMsgCount * node.circularBuffer.capacity() / node.circularBuffer.size());
                    }
                }
                
                // Update the status in the editor with the last message count (only if it has changed), or when switching between startup and real
                if (node.prevTotalMsgCount != node.totalMsgCount || node.prevStartup != startup) {
                    // Show startup speed values in orange, and real values in green
                    if (startup == true) {
                        if (node.ignoreStartup == true) {
                            node.status({fill:"yellow",shape:"ring",text:" start ignored" });
                        }
                        else {
                            node.status({fill:"yellow",shape:"ring",text:" " + totalMsgCount + "/" + node.frequency });
                        }
                    }
                    else {
                        node.status({fill:"green",shape:"dot",text:" " + totalMsgCount + "/" + node.frequency });
                    }
                    node.prevTotalMsgCount = totalMsgCount;
                }
                
                // Send a message on the first output port, when not ignored during the startup period
                if (node.ignoreStartup == false || startup == false) {
                    node.send([{ payload: totalMsgCount, frequency: node.frequency }, null]);
                }
                
                node.prevStartup = startup;
                
                // The message count that has already been added, shouldn't be added again the next second
                node.msgCount = Math.max(0, node.msgCount - added);
            }
            
            if (seconds > 0) {
                // Our new second starts at the end of the previous second
                node.startTime = node.endTime;
            }
        }

        var node = this;

        this.on("input", function(msg) {
            if (node.timer) {
                // An msg has arrived during the specified (timeout) interval, so remove the (timeout) timer.
                clearInterval(node.timer);
            }           

            node.msgCount += 1;
            //console.log("New msg arrived resulting in a message count of " + node.msgCount );
            
            analyse();

            // Register a new timer (with a timeout interval of 1 second), in case no msg should arrive during the next second.
            node.timer = setInterval(function() {
                //console.log("Timer called.  node.msgCount  = " + node.msgCount );
                
                // Seems no msg has arrived during the last second, so register a zero count
                analyse();
            }, 1000);
            
            // Send the original message on the second output port
            node.send([null, msg]);
        });
        
        this.on("close",function() {   
            if (node.timer) {
                // Stop the previous timer, to avoid having multiple timers running in parallel after a redeploy.
                clearInterval(node.timer);
            }          
            node.status({});
        });
    }

    RED.nodes.registerType("msg-speed", speed);
};
