'use strict';

var util = require('util');
var EventEmitter = require('events').EventEmitter;
var debug = {
  server: require('debug')('spdy:window:server'),
  client: require('debug')('spdy:window:client')
};

function Side(window, name, options) {
  EventEmitter.call(this);

  this.name = name;
  this.window = window;
  this.current = options.size;
  this.max = options.size;
  this.limit = options.max;
  this.lowWaterMark = options.lowWaterMark === undefined ?
      this.max / 2 :
      options.lowWaterMark;

  this._refilling = false;
  this._refillQueue = [];
}
util.inherits(Side, EventEmitter);

Side.prototype.setMax = function setMax(max) {
  this.window.debug('id=%d side=%s setMax=%d',
                    this.window.id,
                    this.name,
                    max);
  this.max = max;
  this.lowWaterMark = this.max / 2;
};

Side.prototype.updateMax = function updateMax(max) {
  var delta = max - this.max;
  this.window.debug('id=%d side=%s updateMax=%d delta=%d',
                    this.window.id,
                    this.name,
                    max,
                    delta);

  this.max = max;
  this.lowWaterMark = max / 2;

  this.update(delta);
};

Side.prototype.setLowWaterMark = function setLowWaterMark(lwm) {
  this.lowWaterMark = lwm;
};

Side.prototype.update = function update(size, callback) {
  // Not enough space for the update, wait for refill
  if (size <= 0 && callback && this.isEmpty()) {
    this.window.debug('id=%d side=%s wait for refill=%d [%d/%d]',
                      this.window.id,
                      this.name,
                      -size,
                      this.current,
                      this.max);
    this._refillQueue.push({
      size: size,
      callback: callback
    });
    return;
  }

  this.current += size;

  if (this.current > this.limit) {
    this.emit('overflow');
    return;
  }

  this.window.debug('id=%d side=%s update by=%d [%d/%d]',
                    this.window.id,
                    this.name,
                    size,
                    this.current,
                    this.max);

  // Time to send WINDOW_UPDATE
  if (size < 0 && this.isDraining()) {
    this.window.debug('id=%d side=%s drained', this.window.id, this.name);
    this.emit('drain');
  }

  // Time to write
  if (size > 0 && this.current > 0 && this.current <= size) {
    this.window.debug('id=%d side=%s full', this.window.id, this.name);
    this.emit('full');
  }

  this._processRefillQueue();

  if (callback)
    process.nextTick(callback);
};

Side.prototype.getCurrent = function getCurrent() {
  return this.current;
};

Side.prototype.getMax = function getMax() {
  return this.max;
};

Side.prototype.getDelta = function getDelta() {
  return this.max - this.current;
};

Side.prototype.isDraining = function isDraining() {
  return this.current <= this.lowWaterMark;
};

Side.prototype.isEmpty = function isEmpty() {
  return this.current <= 0;
};

// Private

Side.prototype._processRefillQueue = function _processRefillQueue() {
  // Prevent recursion
  if (this._refilling)
    return;
  this._refilling = true;

  while (this._refillQueue.length > 0) {
    var item = this._refillQueue[0];

    if (this.isEmpty())
      break;

    this.window.debug('id=%d side=%s refilled for size=%d',
                      this.window.id,
                      this.name,
                      -item.size);

    this._refillQueue.shift();
    this.update(item.size, item.callback);
  }

  this._refilling = false;
};

function Window(options) {
  this.id = options.id;
  this.isServer = options.isServer;
  this.debug = this.isServer ? debug.server : debug.client;

  this.recv = new Side(this, 'recv', options.recv);
  this.send = new Side(this, 'send', options.send);
}
module.exports = Window;

Window.prototype.clone = function clone(id) {
  return new Window({
    id: id,
    isServer: this.isServer,
    recv: {
      size: this.recv.max,
      max: this.recv.limit,
      lowWaterMark: this.recv.lowWaterMark
    },
    send: {
      size: this.send.max,
      max: this.send.limit,
      lowWaterMark: this.send.lowWaterMark
    }
  });
};
