function CircularBuffer(capacity) { var readPoint = 0; var writePoint = 0; var buffer = new Array(capacity); return { read: function() { if (isBufferEmpty()) { throw new BufferEmptyException(); } var data = buffer[readPoint]; buffer[readPoint] = null; updateReadPoint(); return data; }, write: function(data) { updateBuffer(data, function() { if (isBufferFull()) { throw new BufferFullException(); } buffer[writePoint] = data; }); }, forceWrite: function(data) { updateBuffer(data, function(){ buffer[writePoint] = data; if (isBufferFull()) { updateReadPoint(); } }); }, clear: function() { readPoint = 0; writePoint = 0; buffer = new Array(capacity); }, isFull: function() { return isBufferFull(); }, isEmpty: function() { return isBufferEmpty(); } }; function isBufferEmpty() { return buffer.every(isEmpty); }; function isBufferFull() { return buffer.filter(isFull).length === capacity; }; function updateBuffer(data, callback) { if (isEmpty(data)) { return; } callback(); updateWritePoint(); }; function updateWritePoint() { writePoint = (writePoint + 1) % capacity; }; function updateReadPoint() { readPoint = (readPoint + 1) % capacity; }; function isFull(data) { return !isEmpty(data); }; function isEmpty(data) { return data === null || data === undefined; }; }; function BufferEmptyException() { this.name = 'BufferEmptyException'; this.message = 'Buffer is empty.'; }; function BufferFullException() { this.name = 'BufferFullException'; this.message = 'Buffer is full.'; }; module.exports = { circularBuffer: function(capacity) { return new CircularBuffer(capacity); }, bufferEmptyException: function() { return new BufferEmptyException(); }, bufferFullException: function() { return new BufferFullException(); } };