/* global describe, it, before, beforeEach, afterEach */
'use strict';

var chai = require('chai'),
    expect = chai.expect,
    sinon = require('sinon'),
    sinonChai = require('sinon-chai'),
    Cache = require('./index').Cache,
    cache = new Cache(),
    clock;

chai.use(sinonChai);

describe('node-cache', function() {
  beforeEach(function() {
    clock = sinon.useFakeTimers();

    cache.clear();
  });

  afterEach(function() {
    clock.restore();
  });

  describe('put()', function() {
    before(function() {
      cache.debug(false);
    });

    it('should allow adding a new item to the cache', function() {
      expect(function() {
        cache.put('key', 'value');
      }).to.not.throw();
    });

    it('should allow adding a new item to the cache with a timeout', function() {
      expect(function() {
        cache.put('key', 'value', 100);
      }).to.not.throw();
    });

    it('should allow adding a new item to the cache with a timeout callback', function() {
      expect(function() {
        cache.put('key', 'value', 100, function() {});
      }).to.not.throw();
    });

    it('should throw an error given a non-numeric timeout', function() {
      expect(function() {
        cache.put('key', 'value', 'foo');
      }).to.throw();
    });

    it('should throw an error given a timeout of NaN', function() {
      expect(function() {
        cache.put('key', 'value', NaN);
      }).to.throw();
    });

    it('should throw an error given a timeout of 0', function() {
      expect(function() {
        cache.put('key', 'value', 0);
      }).to.throw();
    });

    it('should throw an error given a negative timeout', function() {
      expect(function() {
        cache.put('key', 'value', -100);
      }).to.throw();
    });

    it('should throw an error given a non-function timeout callback', function() {
      expect(function() {
        cache.put('key', 'value', 100, 'foo');
      }).to.throw();
    });

    it('should cause the timeout callback to fire once the cache item expires', function() {
      var spy = sinon.spy();
      cache.put('key', 'value', 1000, spy);
      clock.tick(999);
      expect(spy).to.not.have.been.called;
      clock.tick(1);
      expect(spy).to.have.been.calledOnce.and.calledWith('key', 'value');
    });

    it('should override the timeout callback on a new put() with a different timeout callback', function() {
      var spy1 = sinon.spy();
      var spy2 = sinon.spy();
      cache.put('key', 'value', 1000, spy1);
      clock.tick(999);
      cache.put('key', 'value', 1000, spy2)
      clock.tick(1001);
      expect(spy1).to.not.have.been.called;
      expect(spy2).to.have.been.calledOnce.and.calledWith('key', 'value');
    });

    it('should cancel the timeout callback on a new put() without a timeout callback', function() {
      var spy = sinon.spy();
      cache.put('key', 'value', 1000, spy);
      clock.tick(999);
      cache.put('key', 'value');
      clock.tick(1);
      expect(spy).to.not.have.been.called;
    });

    it('should return the cached value', function() {
      expect(cache.put('key', 'value')).to.equal('value');
    });
  });

  describe('del()', function() {
    before(function() {
      cache.debug(false);
    });

    it('should return false given a key for an empty cache', function() {
      expect(cache.del('miss')).to.be.false;
    });

    it('should return false given a key not in a non-empty cache', function() {
      cache.put('key', 'value');
      expect(cache.del('miss')).to.be.false;
    });

    it('should return true given a key in the cache', function() {
      cache.put('key', 'value');
      expect(cache.del('key')).to.be.true;
    });

    it('should remove the provided key from the cache', function() {
      cache.put('key', 'value');
      expect(cache.get('key')).to.equal('value');
      expect(cache.del('key')).to.be.true;
      expect(cache.get('key')).to.be.null;
    });

    it('should decrement the cache size by 1', function() {
      cache.put('key', 'value');
      expect(cache.size()).to.equal(1);
      expect(cache.del('key')).to.be.true;
      expect(cache.size()).to.equal(0);
    });

    it('should not remove other keys in the cache', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      expect(cache.get('key1')).to.equal('value1');
      expect(cache.get('key2')).to.equal('value2');
      expect(cache.get('key3')).to.equal('value3');
      cache.del('key1');
      expect(cache.get('key1')).to.be.null;
      expect(cache.get('key2')).to.equal('value2');
      expect(cache.get('key3')).to.equal('value3');
    });

    it('should only delete a key from the cache once even if called multiple times in a row', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      expect(cache.size()).to.equal(3);
      cache.del('key1');
      cache.del('key1');
      cache.del('key1');
      expect(cache.size()).to.equal(2);
    });

    it('should handle deleting keys which were previously deleted and then re-added to the cache', function() {
      cache.put('key', 'value');
      expect(cache.get('key')).to.equal('value');
      cache.del('key');
      expect(cache.get('key')).to.be.null;
      cache.put('key', 'value');
      expect(cache.get('key')).to.equal('value');
      cache.del('key');
      expect(cache.get('key')).to.be.null;
    });

    it('should return true given an non-expired key', function() {
      cache.put('key', 'value', 1000);
      clock.tick(999);
      expect(cache.del('key')).to.be.true;
    });

    it('should return false given an expired key', function() {
      cache.put('key', 'value', 1000);
      clock.tick(1000);
      expect(cache.del('key')).to.be.false;
    });

    it('should cancel the timeout callback for the deleted key', function() {
      var spy = sinon.spy();
      cache.put('key', 'value', 1000, spy);
      cache.del('key');
      clock.tick(1000);
      expect(spy).to.not.have.been.called;
    });

    it('should handle deletion of many items', function(done) {
      clock.restore();
      var num = 1000;
      for(var i = 0; i < num; i++){
        cache.put('key' + i, i, 1000);
      }
      expect(cache.size()).to.equal(num);
      setTimeout(function(){
        expect(cache.size()).to.equal(0);
        done();
      }, 1000);
    });
  });

  describe('clear()', function() {
    before(function() {
      cache.debug(false);
    });

    it('should have no effect given an empty cache', function() {
      expect(cache.size()).to.equal(0);
      cache.clear();
      expect(cache.size()).to.equal(0);
    });

    it('should remove all existing keys in the cache', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      expect(cache.size()).to.equal(3);
      cache.clear();
      expect(cache.size()).to.equal(0);
    });

    it('should remove the keys in the cache', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      expect(cache.get('key1')).to.equal('value1');
      expect(cache.get('key2')).to.equal('value2');
      expect(cache.get('key3')).to.equal('value3');
      cache.clear();
      expect(cache.get('key1')).to.be.null;
      expect(cache.get('key2')).to.be.null;
      expect(cache.get('key3')).to.be.null;
    });

    it('should reset the cache size to 0', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      expect(cache.size()).to.equal(3);
      cache.clear();
      expect(cache.size()).to.equal(0);
    });

    it('should reset the debug cache hits', function() {
      cache.debug(true);
      cache.put('key', 'value');
      cache.get('key');
      expect(cache.hits()).to.equal(1);
      cache.clear();
      expect(cache.hits()).to.equal(0);
    });

    it('should reset the debug cache misses', function() {
      cache.debug(true);
      cache.put('key', 'value');
      cache.get('miss1');
      expect(cache.misses()).to.equal(1);
      cache.clear();
      expect(cache.misses()).to.equal(0);
    });

    it('should cancel the timeout callbacks for all existing keys', function() {
      var spy1 = sinon.spy();
      var spy2 = sinon.spy();
      var spy3 = sinon.spy();
      cache.put('key1', 'value1', 1000, spy1);
      cache.put('key2', 'value2', 1000, spy2);
      cache.put('key3', 'value3', 1000, spy3);
      cache.clear();
      clock.tick(1000);
      expect(spy1).to.not.have.been.called;
      expect(spy2).to.not.have.been.called;
      expect(spy3).to.not.have.been.called;
    });
  });

  describe('get()', function() {
    before(function() {
      cache.debug(false);
    });

    it('should return null given a key for an empty cache', function() {
      expect(cache.get('miss')).to.be.null;
    });

    it('should return null given a key not in a non-empty cache', function() {
      cache.put('key', 'value');
      expect(cache.get('miss')).to.be.null;
    });

    it('should return the corresponding value of a key in the cache', function() {
      cache.put('key', 'value');
      expect(cache.get('key')).to.equal('value');
    });

    it('should return the latest corresponding value of a key in the cache', function() {
      cache.put('key', 'value1');
      cache.put('key', 'value2');
      cache.put('key', 'value3');
      expect(cache.get('key')).to.equal('value3');
    });

    it('should handle various types of cache keys', function() {
      var keys = [null, undefined, NaN, true, false, 0, 1, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, '', 'a', [], {}, [1, 'a', false], {a:1,b:'a',c:false}, function() {}];
      keys.forEach(function(key, index) {
        var value = 'value' + index;
        cache.put(key, value);
        expect(cache.get(key)).to.deep.equal(value);
      });
    });

    it('should handle various types of cache values', function() {
      var values = [null, undefined, NaN, true, false, 0, 1, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, '', 'a', [], {}, [1, 'a', false], {a:1,b:'a',c:false}, function() {}];
      values.forEach(function(value, index) {
        var key = 'key' + index;
        cache.put(key, value);
        expect(cache.get(key)).to.deep.equal(value);
      });
    });

    it('should not set a timeout given no expiration time', function() {
      cache.put('key', 'value');
      clock.tick(1000);
      expect(cache.get('key')).to.equal('value');
    });

    it('should return the corresponding value of a non-expired key in the cache', function() {
      cache.put('key', 'value', 1000);
      clock.tick(999);
      expect(cache.get('key')).to.equal('value');
    });

    it('should return null given an expired key', function() {
      cache.put('key', 'value', 1000);
      clock.tick(1000);
      expect(cache.get('key')).to.be.null;
    });

    it('should return null given an expired key', function() {
      cache.put('key', 'value', 1000);
      clock.tick(1000);
      expect(cache.get('key')).to.be.null;
    });

    it('should return null given a key which is a property on the Object prototype', function() {
      expect(cache.get('toString')).to.be.null;
    });

    it('should allow reading the value for a key which is a property on the Object prototype', function() {
      cache.put('toString', 'value');
      expect(cache.get('toString')).to.equal('value');
    });
  });

  describe('size()', function() {
    before(function() {
      cache.debug(false);
    });

    it('should return 0 given a fresh cache', function() {
      expect(cache.size()).to.equal(0);
    });

    it('should return 1 after adding a single item to the cache', function() {
      cache.put('key', 'value');
      expect(cache.size()).to.equal(1);
    });

    it('should return 3 after adding three items to the cache', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      expect(cache.size()).to.equal(3);
    });

    it('should not multi-count duplicate items added to the cache', function() {
      cache.put('key', 'value1');
      expect(cache.size()).to.equal(1);
      cache.put('key', 'value2');
      expect(cache.size()).to.equal(1);
    });

    it('should update when a key in the cache expires', function() {
      cache.put('key', 'value', 1000);
      expect(cache.size()).to.equal(1);
      clock.tick(999);
      expect(cache.size()).to.equal(1);
      clock.tick(1);
      expect(cache.size()).to.equal(0);
    });
  });

  describe('memsize()', function() {
    before(function() {
      cache.debug(false);
    });

    it('should return 0 given a fresh cache', function() {
      expect(cache.memsize()).to.equal(0);
    });

    it('should return 1 after adding a single item to the cache', function() {
      cache.put('key', 'value');
      expect(cache.memsize()).to.equal(1);
    });

    it('should return 3 after adding three items to the cache', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      expect(cache.memsize()).to.equal(3);
    });

    it('should not multi-count duplicate items added to the cache', function() {
      cache.put('key', 'value1');
      expect(cache.memsize()).to.equal(1);
      cache.put('key', 'value2');
      expect(cache.memsize()).to.equal(1);
    });

    it('should update when a key in the cache expires', function() {
      cache.put('key', 'value', 1000);
      expect(cache.memsize()).to.equal(1);
      clock.tick(999);
      expect(cache.memsize()).to.equal(1);
      clock.tick(1);
      expect(cache.memsize()).to.equal(0);
    });
  });

  describe('debug()', function() {
    it('should not count cache hits when false', function() {
      cache.debug(false);
      cache.put('key', 'value');
      cache.get('key');
      expect(cache.hits()).to.equal(0);
    });

    it('should not count cache misses when false', function() {
      cache.debug(false);
      cache.put('key', 'value');
      cache.get('miss1');
      expect(cache.misses()).to.equal(0);
    });

    it('should count cache hits when true', function() {
      cache.debug(true);
      cache.put('key', 'value');
      cache.get('key');
      expect(cache.hits()).to.equal(1);
    });

    it('should count cache misses when true', function() {
      cache.debug(true);
      cache.put('key', 'value');
      cache.get('miss1');
      expect(cache.misses()).to.equal(1);
    });
  });

  describe('hits()', function() {
    before(function() {
      cache.debug(true);
    });

    it('should return 0 given an empty cache', function() {
      expect(cache.hits()).to.equal(0);
    });

    it('should return 0 given a non-empty cache which has not been accessed', function() {
      cache.put('key', 'value');
      expect(cache.hits()).to.equal(0);
    });

    it('should return 0 given a non-empty cache which has had only misses', function() {
      cache.put('key', 'value');
      cache.get('miss1');
      cache.get('miss2');
      cache.get('miss3');
      expect(cache.hits()).to.equal(0);
    });

    it('should return 1 given a non-empty cache which has had a single hit', function() {
      cache.put('key', 'value');
      cache.get('key');
      expect(cache.hits()).to.equal(1);
    });

    it('should return 3 given a non-empty cache which has had three hits on the same key', function() {
      cache.put('key', 'value');
      cache.get('key');
      cache.get('key');
      cache.get('key');
      expect(cache.hits()).to.equal(3);
    });

    it('should return 3 given a non-empty cache which has had three hits across many keys', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      cache.get('key1');
      cache.get('key2');
      cache.get('key3');
      expect(cache.hits()).to.equal(3);
    });

    it('should return the correct value after a sequence of hits and misses', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      cache.get('key1');
      cache.get('miss');
      cache.get('key3');
      expect(cache.hits()).to.equal(2);
    });

    it('should not count hits for expired keys', function() {
      cache.put('key', 'value', 1000);
      cache.get('key');
      expect(cache.hits()).to.equal(1);
      clock.tick(999);
      cache.get('key');
      expect(cache.hits()).to.equal(2);
      clock.tick(1);
      cache.get('key');
      expect(cache.hits()).to.equal(2);
    });
  });

  describe('misses()', function() {
    before(function() {
      cache.debug(true);
    });

    it('should return 0 given an empty cache', function() {
      expect(cache.misses()).to.equal(0);
    });

    it('should return 0 given a non-empty cache which has not been accessed', function() {
      cache.put('key', 'value');
      expect(cache.misses()).to.equal(0);
    });

    it('should return 0 given a non-empty cache which has had only hits', function() {
      cache.put('key', 'value');
      cache.get('key');
      cache.get('key');
      cache.get('key');
      expect(cache.misses()).to.equal(0);
    });

    it('should return 1 given a non-empty cache which has had a single miss', function() {
      cache.put('key', 'value');
      cache.get('miss');
      expect(cache.misses()).to.equal(1);
    });

    it('should return 3 given a non-empty cache which has had three misses', function() {
      cache.put('key', 'value');
      cache.get('miss1');
      cache.get('miss2');
      cache.get('miss3');
      expect(cache.misses()).to.equal(3);
    });

    it('should return the correct value after a sequence of hits and misses', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      cache.get('key1');
      cache.get('miss');
      cache.get('key3');
      expect(cache.misses()).to.equal(1);
    });

    it('should count misses for expired keys', function() {
      cache.put('key', 'value', 1000);
      cache.get('key');
      expect(cache.misses()).to.equal(0);
      clock.tick(999);
      cache.get('key');
      expect(cache.misses()).to.equal(0);
      clock.tick(1);
      cache.get('key');
      expect(cache.misses()).to.equal(1);
    });
  });

  describe('keys()', function() {
    before(function() {
      cache.debug(false);
    });

    it('should return an empty array given an empty cache', function() {
      expect(cache.keys()).to.deep.equal([]);
    });

    it('should return a single key after adding a single item to the cache', function() {
      cache.put('key', 'value');
      expect(cache.keys()).to.deep.equal(['key']);
    });

    it('should return 3 keys after adding three items to the cache', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      expect(cache.keys()).to.deep.equal(['key1', 'key2', 'key3']);
    });

    it('should not multi-count duplicate items added to the cache', function() {
      cache.put('key', 'value1');
      expect(cache.keys()).to.deep.equal(['key']);
      cache.put('key', 'value2');
      expect(cache.keys()).to.deep.equal(['key']);
    });

    it('should update when a key in the cache expires', function() {
      cache.put('key', 'value', 1000);
      expect(cache.keys()).to.deep.equal(['key']);
      clock.tick(999);
      expect(cache.keys()).to.deep.equal(['key']);
      clock.tick(1);
      expect(cache.keys()).to.deep.equal([]);
    });
  });

  describe('export()', function() {
    var START_TIME = 10000;

    var BASIC_EXPORT = JSON.stringify({
      key: {
        value: 'value',
        expire: START_TIME + 1000,
      },
    });

    before(function() {
      cache.debug(false);
    });

    beforeEach(function() {
      clock.tick(START_TIME);
    });

    it('should return an empty object given an empty cache', function() {
      expect(cache.exportJson()).to.equal(JSON.stringify({}));
    });

    it('should return a single record after adding a single item to the cache', function() {
      cache.put('key', 'value', 1000);
      expect(cache.exportJson()).to.equal(BASIC_EXPORT);
    });

    it('should return multiple records with expiry', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2', 1000);
      expect(cache.exportJson()).to.equal(JSON.stringify({
        key1: {
          value: 'value1',
          expire: 'NaN',
        },
        key2: {
          value: 'value2',
          expire: START_TIME + 1000,
        },
      }));
    });

    it('should update when a key in the cache expires', function() {
      cache.put('key', 'value', 1000);
      expect(cache.exportJson()).to.equal(BASIC_EXPORT);
      clock.tick(999);
      expect(cache.exportJson()).to.equal(BASIC_EXPORT);
      clock.tick(1);
      expect(cache.exportJson()).to.equal(JSON.stringify({}));
    });
  });

  describe('import()', function() {
    var START_TIME = 10000;

    var BASIC_EXPORT = JSON.stringify({
      key: {
        value: 'value',
        expire: START_TIME + 1000,
      },
    });

    before(function() {
      cache.debug(false);
    });

    beforeEach(function() {
      clock.tick(START_TIME);
    });

    it('should import an empty object into an empty cache', function() {
      var exportedJson = cache.exportJson();

      cache.clear();
      cache.importJson(exportedJson);

      expect(cache.exportJson()).to.equal(JSON.stringify({}));
    });

    it('should import records into an empty cache', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2', 1000);
      var exportedJson = cache.exportJson();

      cache.clear();
      cache.importJson(exportedJson);

      expect(cache.exportJson()).to.equal(JSON.stringify({
        key1: {
          value: 'value1',
          expire: 'NaN',
        },
        key2: {
          value: 'value2',
          expire: START_TIME + 1000,
        },
      }));
    });

    it('should import records into an already-existing cache', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2', 1000);
      var exportedJson = cache.exportJson();

      cache.put('key1', 'changed value', 5000);
      cache.put('key3', 'value3', 500);

      cache.importJson(exportedJson);

      expect(cache.exportJson()).to.equal(JSON.stringify({
        key1: {
          value: 'value1',
          expire: 'NaN',
        },
        key2: {
          value: 'value2',
          expire: START_TIME + 1000,
        },
        key3: {
          value: 'value3',
          expire: START_TIME + 500,
        },
      }));
    });

    it('should import records into an already-existing cache and skip duplicates', function() {
      cache.debug(true);

      cache.put('key1', 'value1');
      cache.put('key2', 'value2', 1000);
      var exportedJson = cache.exportJson();

      cache.clear();
      cache.put('key1', 'changed value', 5000);
      cache.put('key3', 'value3', 500);

      cache.importJson(exportedJson, { skipDuplicates: true });

      expect(cache.exportJson()).to.equal(JSON.stringify({
        key1: {
          value: 'changed value',
          expire: START_TIME + 5000,
        },
        key3: {
          value: 'value3',
          expire: START_TIME + 500,
        },
        key2: {
          value: 'value2',
          expire: START_TIME + 1000,
        },
      }));
    });

    it('should import with updated expire times', function() {
      cache.put('key1', 'value1', 500);
      cache.put('key2', 'value2', 1000);
      var exportedJson = cache.exportJson();

      var tickAmount = 750;
      clock.tick(tickAmount);

      cache.importJson(exportedJson);

      expect(cache.exportJson()).to.equal(JSON.stringify({
        key2: {
          value: 'value2',
          expire: START_TIME + tickAmount + 250,
        },
      }));
    });

    it('should return the new size', function() {
      cache.put('key1', 'value1', 500);
      var exportedJson = cache.exportJson();

      cache.clear();
      cache.put('key2', 'value2', 1000);
      expect(cache.size()).to.equal(1);

      var size = cache.importJson(exportedJson);
      expect(size).to.equal(2);
      expect(cache.size()).to.equal(2);
    });
  });

  describe('Cache()', function() {
    it('should return a new cache instance when called', function() {
      var cache1 = new Cache(),
        cache2 = new Cache();
      cache1.put('key', 'value1');
      expect(cache1.keys()).to.deep.equal(['key']);
      expect(cache2.keys()).to.deep.equal([]);
      cache2.put('key', 'value2');
      expect(cache1.get('key')).to.equal('value1');
      expect(cache2.get('key')).to.equal('value2');
    });
  });

});

/* global describe, it, before, beforeEach, afterEach */
'use strict';

var chai = require('chai'),
    expect = chai.expect,
    sinon = require('sinon'),
    sinonChai = require('sinon-chai'),
    Cache = require('./index').Cache,
    cache = new Cache(),
    clock;

chai.use(sinonChai);

describe('node-cache', function() {
  beforeEach(function() {
    clock = sinon.useFakeTimers();

    cache.clear();
  });

  afterEach(function() {
    clock.restore();
  });

  describe('put()', function() {
    before(function() {
      cache.debug(false);
    });

    it('should allow adding a new item to the cache', function() {
      expect(function() {
        cache.put('key', 'value');
      }).to.not.throw();
    });

    it('should allow adding a new item to the cache with a timeout', function() {
      expect(function() {
        cache.put('key', 'value', 100);
      }).to.not.throw();
    });

    it('should allow adding a new item to the cache with a timeout callback', function() {
      expect(function() {
        cache.put('key', 'value', 100, function() {});
      }).to.not.throw();
    });

    it('should throw an error given a non-numeric timeout', function() {
      expect(function() {
        cache.put('key', 'value', 'foo');
      }).to.throw();
    });

    it('should throw an error given a timeout of NaN', function() {
      expect(function() {
        cache.put('key', 'value', NaN);
      }).to.throw();
    });

    it('should throw an error given a timeout of 0', function() {
      expect(function() {
        cache.put('key', 'value', 0);
      }).to.throw();
    });

    it('should throw an error given a negative timeout', function() {
      expect(function() {
        cache.put('key', 'value', -100);
      }).to.throw();
    });

    it('should throw an error given a timeout that is too large', function() {
      expect(function() {
        cache.put('key', 'value', 2147483648);
      }).to.throw();
    });

    it('should throw an error given a non-function timeout callback', function() {
      expect(function() {
        cache.put('key', 'value', 100, 'foo');
      }).to.throw();
    });

    it('should cause the timeout callback to fire once the cache item expires', function() {
      var spy = sinon.spy();
      cache.put('key', 'value', 1000, spy);
      clock.tick(999);
      expect(spy).to.not.have.been.called;
      clock.tick(1);
      expect(spy).to.have.been.calledOnce.and.calledWith('key', 'value');
    });

    it('should override the timeout callback on a new put() with a different timeout callback', function() {
      var spy1 = sinon.spy();
      var spy2 = sinon.spy();
      cache.put('key', 'value', 1000, spy1);
      clock.tick(999);
      cache.put('key', 'value', 1000, spy2)
      clock.tick(1001);
      expect(spy1).to.not.have.been.called;
      expect(spy2).to.have.been.calledOnce.and.calledWith('key', 'value');
    });

    it('should cancel the timeout callback on a new put() without a timeout callback', function() {
      var spy = sinon.spy();
      cache.put('key', 'value', 1000, spy);
      clock.tick(999);
      cache.put('key', 'value');
      clock.tick(1);
      expect(spy).to.not.have.been.called;
    });

    it('should return the cached value', function() {
      expect(cache.put('key', 'value')).to.equal('value');
    });
  });

  describe('del()', function() {
    before(function() {
      cache.debug(false);
    });

    it('should return false given a key for an empty cache', function() {
      expect(cache.del('miss')).to.be.false;
    });

    it('should return false given a key not in a non-empty cache', function() {
      cache.put('key', 'value');
      expect(cache.del('miss')).to.be.false;
    });

    it('should return true given a key in the cache', function() {
      cache.put('key', 'value');
      expect(cache.del('key')).to.be.true;
    });

    it('should remove the provided key from the cache', function() {
      cache.put('key', 'value');
      expect(cache.get('key')).to.equal('value');
      expect(cache.del('key')).to.be.true;
      expect(cache.get('key')).to.be.null;
    });

    it('should decrement the cache size by 1', function() {
      cache.put('key', 'value');
      expect(cache.size()).to.equal(1);
      expect(cache.del('key')).to.be.true;
      expect(cache.size()).to.equal(0);
    });

    it('should not remove other keys in the cache', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      expect(cache.get('key1')).to.equal('value1');
      expect(cache.get('key2')).to.equal('value2');
      expect(cache.get('key3')).to.equal('value3');
      cache.del('key1');
      expect(cache.get('key1')).to.be.null;
      expect(cache.get('key2')).to.equal('value2');
      expect(cache.get('key3')).to.equal('value3');
    });

    it('should only delete a key from the cache once even if called multiple times in a row', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      expect(cache.size()).to.equal(3);
      cache.del('key1');
      cache.del('key1');
      cache.del('key1');
      expect(cache.size()).to.equal(2);
    });

    it('should handle deleting keys which were previously deleted and then re-added to the cache', function() {
      cache.put('key', 'value');
      expect(cache.get('key')).to.equal('value');
      cache.del('key');
      expect(cache.get('key')).to.be.null;
      cache.put('key', 'value');
      expect(cache.get('key')).to.equal('value');
      cache.del('key');
      expect(cache.get('key')).to.be.null;
    });

    it('should return true given an non-expired key', function() {
      cache.put('key', 'value', 1000);
      clock.tick(999);
      expect(cache.del('key')).to.be.true;
    });

    it('should return false given an expired key', function() {
      cache.put('key', 'value', 1000);
      clock.tick(1000);
      expect(cache.del('key')).to.be.false;
    });

    it('should cancel the timeout callback for the deleted key', function() {
      var spy = sinon.spy();
      cache.put('key', 'value', 1000, spy);
      cache.del('key');
      clock.tick(1000);
      expect(spy).to.not.have.been.called;
    });

    it('should handle deletion of many items', function(done) {
      clock.restore();
      var num = 1000;
      for(var i = 0; i < num; i++){
        cache.put('key' + i, i, 1000);
      }
      expect(cache.size()).to.equal(num);
      setTimeout(function(){
        expect(cache.size()).to.equal(0);
        done();
      }, 1000);
    });
  });

  describe('clear()', function() {
    before(function() {
      cache.debug(false);
    });

    it('should have no effect given an empty cache', function() {
      expect(cache.size()).to.equal(0);
      cache.clear();
      expect(cache.size()).to.equal(0);
    });

    it('should remove all existing keys in the cache', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      expect(cache.size()).to.equal(3);
      cache.clear();
      expect(cache.size()).to.equal(0);
    });

    it('should remove the keys in the cache', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      expect(cache.get('key1')).to.equal('value1');
      expect(cache.get('key2')).to.equal('value2');
      expect(cache.get('key3')).to.equal('value3');
      cache.clear();
      expect(cache.get('key1')).to.be.null;
      expect(cache.get('key2')).to.be.null;
      expect(cache.get('key3')).to.be.null;
    });

    it('should reset the cache size to 0', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      expect(cache.size()).to.equal(3);
      cache.clear();
      expect(cache.size()).to.equal(0);
    });

    it('should reset the debug cache hits', function() {
      cache.debug(true);
      cache.put('key', 'value');
      cache.get('key');
      expect(cache.hits()).to.equal(1);
      cache.clear();
      expect(cache.hits()).to.equal(0);
    });

    it('should reset the debug cache misses', function() {
      cache.debug(true);
      cache.put('key', 'value');
      cache.get('miss1');
      expect(cache.misses()).to.equal(1);
      cache.clear();
      expect(cache.misses()).to.equal(0);
    });

    it('should cancel the timeout callbacks for all existing keys', function() {
      var spy1 = sinon.spy();
      var spy2 = sinon.spy();
      var spy3 = sinon.spy();
      cache.put('key1', 'value1', 1000, spy1);
      cache.put('key2', 'value2', 1000, spy2);
      cache.put('key3', 'value3', 1000, spy3);
      cache.clear();
      clock.tick(1000);
      expect(spy1).to.not.have.been.called;
      expect(spy2).to.not.have.been.called;
      expect(spy3).to.not.have.been.called;
    });
  });

  describe('get()', function() {
    before(function() {
      cache.debug(false);
    });

    it('should return null given a key for an empty cache', function() {
      expect(cache.get('miss')).to.be.null;
    });

    it('should return null given a key not in a non-empty cache', function() {
      cache.put('key', 'value');
      expect(cache.get('miss')).to.be.null;
    });

    it('should return the corresponding value of a key in the cache', function() {
      cache.put('key', 'value');
      expect(cache.get('key')).to.equal('value');
    });

    it('should return the latest corresponding value of a key in the cache', function() {
      cache.put('key', 'value1');
      cache.put('key', 'value2');
      cache.put('key', 'value3');
      expect(cache.get('key')).to.equal('value3');
    });

    it('should handle various types of cache keys', function() {
      var keys = [null, undefined, NaN, true, false, 0, 1, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, '', 'a', [], {}, [1, 'a', false], {a:1,b:'a',c:false}, function() {}];
      keys.forEach(function(key, index) {
        var value = 'value' + index;
        cache.put(key, value);
        expect(cache.get(key)).to.deep.equal(value);
      });
    });

    it('should handle various types of cache values', function() {
      var values = [null, undefined, NaN, true, false, 0, 1, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, '', 'a', [], {}, [1, 'a', false], {a:1,b:'a',c:false}, function() {}];
      values.forEach(function(value, index) {
        var key = 'key' + index;
        cache.put(key, value);
        expect(cache.get(key)).to.deep.equal(value);
      });
    });

    it('should not set a timeout given no expiration time', function() {
      cache.put('key', 'value');
      clock.tick(1000);
      expect(cache.get('key')).to.equal('value');
    });

    it('should return the corresponding value of a non-expired key in the cache', function() {
      cache.put('key', 'value', 1000);
      clock.tick(999);
      expect(cache.get('key')).to.equal('value');
    });

    it('should return null given an expired key', function() {
      cache.put('key', 'value', 1000);
      clock.tick(1000);
      expect(cache.get('key')).to.be.null;
    });

    it('should return null given an expired key', function() {
      cache.put('key', 'value', 1000);
      clock.tick(1000);
      expect(cache.get('key')).to.be.null;
    });

    it('should return null given a key which is a property on the Object prototype', function() {
      expect(cache.get('toString')).to.be.null;
    });

    it('should allow reading the value for a key which is a property on the Object prototype', function() {
      cache.put('toString', 'value');
      expect(cache.get('toString')).to.equal('value');
    });
  });

  describe('size()', function() {
    before(function() {
      cache.debug(false);
    });

    it('should return 0 given a fresh cache', function() {
      expect(cache.size()).to.equal(0);
    });

    it('should return 1 after adding a single item to the cache', function() {
      cache.put('key', 'value');
      expect(cache.size()).to.equal(1);
    });

    it('should return 3 after adding three items to the cache', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      expect(cache.size()).to.equal(3);
    });

    it('should not multi-count duplicate items added to the cache', function() {
      cache.put('key', 'value1');
      expect(cache.size()).to.equal(1);
      cache.put('key', 'value2');
      expect(cache.size()).to.equal(1);
    });

    it('should update when a key in the cache expires', function() {
      cache.put('key', 'value', 1000);
      expect(cache.size()).to.equal(1);
      clock.tick(999);
      expect(cache.size()).to.equal(1);
      clock.tick(1);
      expect(cache.size()).to.equal(0);
    });
  });

  describe('memsize()', function() {
    before(function() {
      cache.debug(false);
    });

    it('should return 0 given a fresh cache', function() {
      expect(cache.memsize()).to.equal(0);
    });

    it('should return 1 after adding a single item to the cache', function() {
      cache.put('key', 'value');
      expect(cache.memsize()).to.equal(1);
    });

    it('should return 3 after adding three items to the cache', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      expect(cache.memsize()).to.equal(3);
    });

    it('should not multi-count duplicate items added to the cache', function() {
      cache.put('key', 'value1');
      expect(cache.memsize()).to.equal(1);
      cache.put('key', 'value2');
      expect(cache.memsize()).to.equal(1);
    });

    it('should update when a key in the cache expires', function() {
      cache.put('key', 'value', 1000);
      expect(cache.memsize()).to.equal(1);
      clock.tick(999);
      expect(cache.memsize()).to.equal(1);
      clock.tick(1);
      expect(cache.memsize()).to.equal(0);
    });
  });

  describe('debug()', function() {
    it('should not count cache hits when false', function() {
      cache.debug(false);
      cache.put('key', 'value');
      cache.get('key');
      expect(cache.hits()).to.equal(0);
    });

    it('should not count cache misses when false', function() {
      cache.debug(false);
      cache.put('key', 'value');
      cache.get('miss1');
      expect(cache.misses()).to.equal(0);
    });

    it('should count cache hits when true', function() {
      cache.debug(true);
      cache.put('key', 'value');
      cache.get('key');
      expect(cache.hits()).to.equal(1);
    });

    it('should count cache misses when true', function() {
      cache.debug(true);
      cache.put('key', 'value');
      cache.get('miss1');
      expect(cache.misses()).to.equal(1);
    });
  });

  describe('hits()', function() {
    before(function() {
      cache.debug(true);
    });

    it('should return 0 given an empty cache', function() {
      expect(cache.hits()).to.equal(0);
    });

    it('should return 0 given a non-empty cache which has not been accessed', function() {
      cache.put('key', 'value');
      expect(cache.hits()).to.equal(0);
    });

    it('should return 0 given a non-empty cache which has had only misses', function() {
      cache.put('key', 'value');
      cache.get('miss1');
      cache.get('miss2');
      cache.get('miss3');
      expect(cache.hits()).to.equal(0);
    });

    it('should return 1 given a non-empty cache which has had a single hit', function() {
      cache.put('key', 'value');
      cache.get('key');
      expect(cache.hits()).to.equal(1);
    });

    it('should return 3 given a non-empty cache which has had three hits on the same key', function() {
      cache.put('key', 'value');
      cache.get('key');
      cache.get('key');
      cache.get('key');
      expect(cache.hits()).to.equal(3);
    });

    it('should return 3 given a non-empty cache which has had three hits across many keys', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      cache.get('key1');
      cache.get('key2');
      cache.get('key3');
      expect(cache.hits()).to.equal(3);
    });

    it('should return the correct value after a sequence of hits and misses', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      cache.get('key1');
      cache.get('miss');
      cache.get('key3');
      expect(cache.hits()).to.equal(2);
    });

    it('should not count hits for expired keys', function() {
      cache.put('key', 'value', 1000);
      cache.get('key');
      expect(cache.hits()).to.equal(1);
      clock.tick(999);
      cache.get('key');
      expect(cache.hits()).to.equal(2);
      clock.tick(1);
      cache.get('key');
      expect(cache.hits()).to.equal(2);
    });
  });

  describe('misses()', function() {
    before(function() {
      cache.debug(true);
    });

    it('should return 0 given an empty cache', function() {
      expect(cache.misses()).to.equal(0);
    });

    it('should return 0 given a non-empty cache which has not been accessed', function() {
      cache.put('key', 'value');
      expect(cache.misses()).to.equal(0);
    });

    it('should return 0 given a non-empty cache which has had only hits', function() {
      cache.put('key', 'value');
      cache.get('key');
      cache.get('key');
      cache.get('key');
      expect(cache.misses()).to.equal(0);
    });

    it('should return 1 given a non-empty cache which has had a single miss', function() {
      cache.put('key', 'value');
      cache.get('miss');
      expect(cache.misses()).to.equal(1);
    });

    it('should return 3 given a non-empty cache which has had three misses', function() {
      cache.put('key', 'value');
      cache.get('miss1');
      cache.get('miss2');
      cache.get('miss3');
      expect(cache.misses()).to.equal(3);
    });

    it('should return the correct value after a sequence of hits and misses', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      cache.get('key1');
      cache.get('miss');
      cache.get('key3');
      expect(cache.misses()).to.equal(1);
    });

    it('should count misses for expired keys', function() {
      cache.put('key', 'value', 1000);
      cache.get('key');
      expect(cache.misses()).to.equal(0);
      clock.tick(999);
      cache.get('key');
      expect(cache.misses()).to.equal(0);
      clock.tick(1);
      cache.get('key');
      expect(cache.misses()).to.equal(1);
    });
  });

  describe('keys()', function() {
    before(function() {
      cache.debug(false);
    });

    it('should return an empty array given an empty cache', function() {
      expect(cache.keys()).to.deep.equal([]);
    });

    it('should return a single key after adding a single item to the cache', function() {
      cache.put('key', 'value');
      expect(cache.keys()).to.deep.equal(['key']);
    });

    it('should return 3 keys after adding three items to the cache', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2');
      cache.put('key3', 'value3');
      expect(cache.keys()).to.deep.equal(['key1', 'key2', 'key3']);
    });

    it('should not multi-count duplicate items added to the cache', function() {
      cache.put('key', 'value1');
      expect(cache.keys()).to.deep.equal(['key']);
      cache.put('key', 'value2');
      expect(cache.keys()).to.deep.equal(['key']);
    });

    it('should update when a key in the cache expires', function() {
      cache.put('key', 'value', 1000);
      expect(cache.keys()).to.deep.equal(['key']);
      clock.tick(999);
      expect(cache.keys()).to.deep.equal(['key']);
      clock.tick(1);
      expect(cache.keys()).to.deep.equal([]);
    });
  });

  describe('export()', function() {
    var START_TIME = 10000;

    var BASIC_EXPORT = JSON.stringify({
      key: {
        value: 'value',
        expire: START_TIME + 1000,
      },
    });

    before(function() {
      cache.debug(false);
    });

    beforeEach(function() {
      clock.tick(START_TIME);
    });

    it('should return an empty object given an empty cache', function() {
      expect(cache.exportJson()).to.equal(JSON.stringify({}));
    });

    it('should return a single record after adding a single item to the cache', function() {
      cache.put('key', 'value', 1000);
      expect(cache.exportJson()).to.equal(BASIC_EXPORT);
    });

    it('should return multiple records with expiry', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2', 1000);
      expect(cache.exportJson()).to.equal(JSON.stringify({
        key1: {
          value: 'value1',
          expire: 'NaN',
        },
        key2: {
          value: 'value2',
          expire: START_TIME + 1000,
        },
      }));
    });

    it('should update when a key in the cache expires', function() {
      cache.put('key', 'value', 1000);
      expect(cache.exportJson()).to.equal(BASIC_EXPORT);
      clock.tick(999);
      expect(cache.exportJson()).to.equal(BASIC_EXPORT);
      clock.tick(1);
      expect(cache.exportJson()).to.equal(JSON.stringify({}));
    });
  });

  describe('import()', function() {
    var START_TIME = 10000;

    var BASIC_EXPORT = JSON.stringify({
      key: {
        value: 'value',
        expire: START_TIME + 1000,
      },
    });

    before(function() {
      cache.debug(false);
    });

    beforeEach(function() {
      clock.tick(START_TIME);
    });

    it('should import an empty object into an empty cache', function() {
      var exportedJson = cache.exportJson();

      cache.clear();
      cache.importJson(exportedJson);

      expect(cache.exportJson()).to.equal(JSON.stringify({}));
    });

    it('should import records into an empty cache', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2', 1000);
      var exportedJson = cache.exportJson();

      cache.clear();
      cache.importJson(exportedJson);

      expect(cache.exportJson()).to.equal(JSON.stringify({
        key1: {
          value: 'value1',
          expire: 'NaN',
        },
        key2: {
          value: 'value2',
          expire: START_TIME + 1000,
        },
      }));
    });

    it('should import records into an already-existing cache', function() {
      cache.put('key1', 'value1');
      cache.put('key2', 'value2', 1000);
      var exportedJson = cache.exportJson();

      cache.put('key1', 'changed value', 5000);
      cache.put('key3', 'value3', 500);

      cache.importJson(exportedJson);

      expect(cache.exportJson()).to.equal(JSON.stringify({
        key1: {
          value: 'value1',
          expire: 'NaN',
        },
        key2: {
          value: 'value2',
          expire: START_TIME + 1000,
        },
        key3: {
          value: 'value3',
          expire: START_TIME + 500,
        },
      }));
    });

    it('should import records into an already-existing cache and skip duplicates', function() {
      cache.debug(true);

      cache.put('key1', 'value1');
      cache.put('key2', 'value2', 1000);
      var exportedJson = cache.exportJson();

      cache.clear();
      cache.put('key1', 'changed value', 5000);
      cache.put('key3', 'value3', 500);

      cache.importJson(exportedJson, { skipDuplicates: true });

      expect(cache.exportJson()).to.equal(JSON.stringify({
        key1: {
          value: 'changed value',
          expire: START_TIME + 5000,
        },
        key3: {
          value: 'value3',
          expire: START_TIME + 500,
        },
        key2: {
          value: 'value2',
          expire: START_TIME + 1000,
        },
      }));
    });

    it('should import with updated expire times', function() {
      cache.put('key1', 'value1', 500);
      cache.put('key2', 'value2', 1000);
      var exportedJson = cache.exportJson();

      var tickAmount = 750;
      clock.tick(tickAmount);

      cache.importJson(exportedJson);

      expect(cache.exportJson()).to.equal(JSON.stringify({
        key2: {
          value: 'value2',
          expire: START_TIME + tickAmount + 250,
        },
      }));
    });

    it('should return the new size', function() {
      cache.put('key1', 'value1', 500);
      var exportedJson = cache.exportJson();

      cache.clear();
      cache.put('key2', 'value2', 1000);
      expect(cache.size()).to.equal(1);

      var size = cache.importJson(exportedJson);
      expect(size).to.equal(2);
      expect(cache.size()).to.equal(2);
    });
  });

  describe('Cache()', function() {
    it('should return a new cache instance when called', function() {
      var cache1 = new Cache(),
        cache2 = new Cache();
      cache1.put('key', 'value1');
      expect(cache1.keys()).to.deep.equal(['key']);
      expect(cache2.keys()).to.deep.equal([]);
      cache2.put('key', 'value2');
      expect(cache1.get('key')).to.equal('value1');
      expect(cache2.get('key')).to.equal('value2');
    });
  });

});


/*
	Stars2 Application by Jessica Seibert
	1/31/09/

	A second version of my prior Stars application.  This time, the stars
	are drawn with a more platform independent SDL_FillRect, and
	is encapsulated into a single header file.  This header is developed
	here independently from my Blocks application that utilizes it.

	Built to by architecturally flexible and modular for future games.

      _._
    .    ` .
   .       `.
   .________.
    `""``""'
      `  `
	:  :	We are pillars of society. You can't run your computers, your fashion houses,
       :  :		your publishing houses, your damn magazines, you can't do anything
       ;  ;		in culture without psychedelic people in key positions.
      .  .		- Terence McKenna
     '  .
    '  .
   .  .
   ;  :
  .    .
 '      ' mh

*/

//The headers
#include <iostream>

#include "SDL/SDL.h"

#include <string>
#include <math.h>

//Local includes
#include "timer.h"
#include "SDLwindow.h"
#include "stars.h"

//Global types and constants
#define WANT_TASTY_MUSHROOM 1 //Always

//The screen sttributes
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int SCREEN_BPP = 32;

//Initialize whatever you must
bool GameInitialize()
{

	//Initialize all SDL subsystems
	if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 )
	{
		return false;
	}

	//If everything initialized fine
	return true;
}

//❤`•.¸¸.•´´¯`••.¸¸.•´´¯`•´❤`•.¸¸.•´´¯`••.¸¸.•´´¯`•´❤`•.¸¸.•´´¯`••.¸¸.•´´¯`•´❤
//function:  main
//
//The land of tasty mushrooms.  Accepts no arguments.
//❤`•.¸¸.•´´¯`••.¸¸.•´´¯`•´❤`•.¸¸.•´´¯`••.¸¸.•´´¯`•´❤`•.¸¸.•´´¯`••.¸¸.•´´¯`•´❤
int main( int argc, char* args[] )
{
	SDL_Event event;		//The event structure
    	Timer delta;    		//Keeps track of time
	bool quit = false; 		//Quit flag
	int msy, msx;
	int ticks = 0;			//Save the ticks!
	cStarList* myStars;
	myStars = new cStarList(100, 640, 480);

	delta.start();

  	Window myWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, "Stars!");

	//Initialize SDL and stuff
	if( GameInitialize() == false)
	{
		fprintf(stderr, "GameInitialize() failed: %s\n", SDL_GetError());
		return 1;
	}

	msy = msx = 0;
	srand(delta.get_ticks());
	delta.start();

	//While the user hasn't quit
	while(WANT_TASTY_MUSHROOM && !quit)
	{
		//While there's events to handle
		while( SDL_PollEvent( &event ) )
		{
			//Handle window events
			myWindow.handle_events(event);
			//Check for ESCAPE key...  oh yeah, touch it
			if( event.key.keysym.sym == SDLK_ESCAPE)
			{
				  quit = true;
			}
			//OS-level quit signal, deal with it!
			if( event.type == SDL_QUIT )
			{
				quit = true; //Quit the program
			}

			//If the mouse moved
			if( event.type == SDL_MOUSEMOTION )
			{
				//Get the mouse offsets
				msx = event.motion.x;
				msy = event.motion.y;
			}

			if( event.type == SDL_MOUSEBUTTONDOWN)
			{
				//msdown = 1;
			}

		}

		myStars->MoveStars(msx, msy, ticks);

		SDL_FillRect( myWindow.screen, & myWindow.screen->clip_rect, SDL_MapRGB( myWindow.screen->format, 0x00, 0x00, 0x00 ) );
		SDL_FillRect( myWindow.screen, & myWindow.screen->clip_rect, SDL_MapRGB( myWindow.screen->format, 0x00, 0x00, 0x00 ) );
		myStars->DrawStars( myWindow.screen);

		//Update the screen
		if( SDL_Flip( myWindow.screen ) == -1 )
		{
			fprintf(stderr, "SDL_Flip() failed: %s\n", SDL_GetError());
			return 1;
		}

		ticks = delta.get_ticks();
		delta.start();
	} //Farewell, game!

	//myWindow.CleanUp();

	//Clean up
	delete myStars;

	 SDL_Quit();
	fprintf(stdout, "Normal Quit: %s\n",  SDL_GetError());

	return 0;
}

/*
	Stars2 Application by Jessica Seibert
	1/31/09/

	A second version of my prior Stars application.  This time, the stars
	are drawn with a more platform independent SDL_FillRect, and
	is encapsulated into a single header file.

      _._
    .    ` .
   .       `.
   .________.
    `""``""'
      `  `
 	 :  :
       :  :
       ;  ;
      .  .
     '  .
    '  .
   .  .
   ;  :
  .    .
 '      ' mh

*/

//The headers
#include <iostream>

#include "SDL/SDL.h"

#include <string>
#include <math.h>

//Local includes
#include "timer.h"
#include "SDLwindow.h"
#include "StarList.h"
#ifndef StarData
	#include "StarData.h"
#endif

//Global types and constants
#define WANT_TASTY_MUSHROOM 1 //Always

//The screen sttributes
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int SCREEN_BPP = 32;

//Initialize whatever you must
bool GameInitialize()
{

	//Initialize all SDL subsystems
	if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 )
	{
		return false;
	}

	//If everything initialized fine
	return true;
}

//❤`•.¸¸.•´´¯`••.¸¸.•´´¯`•´❤`•.¸¸.•´´¯`••.¸¸.•´´¯`•´❤`•.¸¸.•´´¯`••.¸¸.•´´¯`•´❤
//function:  main
//
//The land of tasty mushrooms.  Accepts no arguments.
//❤`•.¸¸.•´´¯`••.¸¸.•´´¯`•´❤`•.¸¸.•´´¯`••.¸¸.•´´¯`•´❤`•.¸¸.•´´¯`••.¸¸.•´´¯`•´❤
int main( int argc, char* args[] )
{
	SDL_Event event;		//The event structure
    	Timer delta;    		//Keeps track of time
	bool quit = false; 		//Quit flag
	int msy, msx;
	int ticks = 0;			//Save the ticks!
	cStarList* myStars;
	myStars = new cStarList(100, 640, 480);

	delta.start();

  	Window myWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, "Stars!");

	//Initialize SDL and stuff
	if( GameInitialize() == false)
	{
		fprintf(stderr, "GameInitialize() failed: %s\n", SDL_GetError());
		return 1;
	}

	msy = msx = 0;
	srand(delta.get_ticks());
	delta.start();

	//While the user hasn't quit
	while(WANT_TASTY_MUSHROOM && !quit)
	{
		//While there are events to handle
		while( SDL_PollEvent( &event ) )
		{
			//Handle window events
			myWindow.handle_events(event);
			//Check for ESCAPE key...  oh yeah, touch it
			if( event.key.keysym.sym == SDLK_ESCAPE)
			{
				  quit = true;
			}
			//OS-level quit signal, deal with it!
			if( event.type == SDL_QUIT )
			{
				quit = true; //Quit the program
			}

			//If the mouse moved
			if( event.type == SDL_MOUSEMOTION )
			{
				//Get the mouse offsets
				msx = event.motion.x;
				msy = event.motion.y;
			}

		}

		myStars->MoveStars(msx, msy, ticks);

		SDL_FillRect( myWindow.screen, & myWindow.screen->clip_rect, SDL_MapRGB( myWindow.screen->format, 0x00, 0x00, 0x00 ) );
		SDL_FillRect( myWindow.screen, & myWindow.screen->clip_rect, SDL_MapRGB( myWindow.screen->format, 0x00, 0x00, 0x00 ) );
		myStars->DrawStars( myWindow.screen);

		//Update the screen
		if( SDL_Flip( myWindow.screen ) == -1 )
		{
			fprintf(stderr, "SDL_Flip() failed: %s\n", SDL_GetError());
			return 1;
		}

		ticks = delta.get_ticks();
		delta.start();
	} //Farewell, game!

	//myWindow.CleanUp();

	//Clean up
	delete myStars;

	 SDL_Quit();
	fprintf(stdout, "Normal Quit: %s\n",  SDL_GetError());

	return 0;
}


class Article < ActiveRecord::Base
 attr_accessible :title, :body, :author
end


/*
 * screen.cpp
 *
 * Project: OpenSonATA
 * Version: 1.0
 * Author:  Jon Richards (current maintainer)
 *          The OpenSonATA code is the result of many programmers over many
 *          years.
 *
 * Manages the curses screen.
 *
 * Copyright 2010 The SETI Institute
 *
 * 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.
 *
 * Attribution must be: “Licensed through SETI” in all published
 * uses of the software including analytics based on the software,
 * combined and merged software, papers, articles, books, reports,
 * web pages, etc.
 */

/**
 * @file screen.cpp
 * Manages the curses screen.
 */

#include "screen.h"
#include "utils.h"

bool Screen::m_resizeEventOccurred = false;
int Screen::m_newRows = -1;
int Screen::m_newCols = -1;

/** Define the bottom menu area to have no height. In future
 * versions we will add a bottom menu, increase this to "1".
 */
#define BOTTOM_MENU_HEIGHT 0

/* Constructor. */
Screen::Screen()
{
    Screen::m_resizeEventOccurred = false;
    Screen::m_newRows = -1;
    Screen::m_newCols = -1;
    m_screenMode = screen_mode_components;
    m_page = 1;
    m_isNewMode = false;
    m_rows = m_newRows;
    m_cols = m_newCols;
    (void) signal(SIGINT, Screen::finish);         /* arrange interrupts to terminate */
    (void) signal(SIGWINCH, Screen::screenResize); /* Catch the resize signal */
    m_rows = m_newRows;
    m_cols = m_newCols;

}

/* Destructor. */
Screen::~Screen()
{
}

/*
 * Initialize the curses screen.
 */
void Screen::init()
{
    initscr();      /* initialize the curses library */
    keypad(stdscr, TRUE);  /* enable keyboard mapping */
    (void) nonl();         /* tell curses not to do NL->CR/NL on output */
    (void) cbreak();       /* take input chars one at a time, no wait for \n */
    (void) noecho();
    timeout(1);

    if (has_colors())
    {
        start_color();

        /*
         * Simple color assignment, often all we need.  Color pair 0 cannot
         * be redefined.  This example uses the same value for the color
         * pair as for the foreground color, though of course that is not
         * necessary:
         */
        init_pair(1, COLOR_RED,     COLOR_BLACK);
        init_pair(2, COLOR_GREEN,   COLOR_BLACK);
        init_pair(3, COLOR_YELLOW,  COLOR_BLACK);
        init_pair(4, COLOR_BLUE,    COLOR_BLACK);
        init_pair(5, COLOR_CYAN,    COLOR_BLACK);
        init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
        init_pair(7, COLOR_WHITE,   COLOR_BLACK);
        init_pair(8, COLOR_RED,   COLOR_RED);
        init_pair(9, COLOR_CYAN,   COLOR_CYAN);
        init_pair(10, COLOR_GREEN,   COLOR_GREEN);
        init_pair(11, COLOR_BLACK,   COLOR_BLACK);
        init_pair(12, COLOR_BLACK,   COLOR_YELLOW);
    }

    drawBottomMenu();

}

/*
 * Handles the screen rezise, which comes in as the result
 * of a SIGWINCH signal.
 * Executes the program "resize" to read in the terminal size.
 *
 * @param sig the signal.
 */
void Screen::screenResize(int sig)
{
    Screen::m_resizeEventOccurred = true;
    FILE *fp = popen("resize", "r");
    if(fp)
    {
        char line[64];
        memset(line, 0, 64);
        while(fgets(line, sizeof(line)-1, fp))
        {
            if(strstr(line, "COLUMNS"))
            {
                Screen::m_newCols = atol(line + 16);
            }
            else if(strstr(line, "LINES"))
            {
                Screen::m_newRows = atol(line + 14);
            }
            memset(line, 0, 64);
        }

        fclose(fp);
    }
}

/*
 * Catch the control-c to quit screen mode then exit.
 *
 * @param sig the signal.
 */
void Screen::finish(int sig)
{
    endwin();
    exit(0);
}

/*
 * Draws the bottom of the screen. The Function keys.
 */
void Screen::drawBottomMenu()
{

    /** @todo Multiple report screens need to be implemented. Until this is working
     * the bottom menu should not be displayed.
     */

    /*
       int centerPos = 0;
       int width = (m_cols/(screen_mode_last - screen_mode_components +1));

       for(int i = screen_mode_components; i<screen_mode_last; i++)
       {
       centerPos = (i+1)*width;
       if(m_screenMode == i) attrset(COLOR_PAIR(6)); //magenta
       else attrset(COLOR_PAIR(5)); //cyan

       if(i == screen_mode_components)
       {
       move(m_rows-1, centerPos - (int)strlen("1-Status")/2);
       addstr("F1 - Status");
       }
       if(i == screen_mode_signals)
       {
       move(m_rows-1, centerPos - (int)strlen("2-Signals")/2);
       addstr("F2 - Signals");
       }
       if(i == screen_mode_errors)
       {
       move(m_rows-1, centerPos - (int)strlen("2-Errors")/2);
       addstr("F3 - Errors");
       }
       if(i == screen_mode_summary)
       {
       move(m_rows-1, centerPos - (int)strlen("4-Summary")/2);
       addstr("F4 - Summary");
       }

       attrset(COLOR_PAIR(0));
       }
     */
    return;

}

/*
 * paint the screen.
 *
 * @param details the instance of the object containing the
 * information to display on the screen.
 */
void Screen::paint(Details *details)
{
    bool shouldRefresh = false;
    string line;

    if(Screen::m_resizeEventOccurred == true)
    {
        Screen::m_resizeEventOccurred = false;
        m_rows = m_newRows;
        m_cols = m_newCols;
        shouldRefresh = true;
        endwin();
        init();
        clear();
        drawBottomMenu();
    }

    if(m_screenMode == details->getType())
    {
        if(m_page > details->getNumPages(m_rows-BOTTOM_MENU_HEIGHT))
            m_page = details->getNumPages(m_rows-BOTTOM_MENU_HEIGHT);

        if(m_isNewMode == true)
        {
            m_page = details->getCurrentPageNumber();
            m_isNewMode = false;
            clear();
            shouldRefresh = true;
        }
        if(details->paint(m_page, m_cols, m_rows-BOTTOM_MENU_HEIGHT))
        {
            shouldRefresh = true;
        }
    }

    //Print the page
    line = "Page " + Utils::itos(m_page) + " of " +
        Utils::itos(details->getNumPages(m_rows-BOTTOM_MENU_HEIGHT));
    move(0, m_cols-line.size());
    addstr(line.c_str());

    if(details->getNumPages(m_rows-BOTTOM_MENU_HEIGHT) == m_page)
        line = "8=PgDn";
    else if(m_page == 1)
        line = "9=PgUp";
    else
        line = "8=PgDn, 9=PgUp";
    move(1, m_cols-line.size());
    line += "     ";
    addstr(line.c_str() );

    //Park cursor
    move(m_rows-1, m_cols-1);

    if(shouldRefresh == true)
    {
        refresh();
    }

}

/*
 * Process key presses.
 *
 * @param details the Details object to redraw of a key affects
 * the screen.
 */
void Screen::processKey(Details *details)
{
    char key[5];

    int ch = getch();

    //Print the keypress in the lower left corner
    //For debugging keypresses
    /*
       if(ch != -1)
       {
       sprintf(key, "%d", ch);
       move(m_rows-BOTTOM_MENU_HEIGHT, m_cols-4 );
       addstr("    ");
       move(m_rows-BOTTOM_MENU_HEIGHT, m_cols-4 );
       addstr(key);
       }
     */

    /** @todo Add multiple screens that change based on key presses. */
    /*
    if(ch == 49)
    {
    m_screenMode = screen_mode_components;
    m_isNewMode = true;
    drawBottomMenu();
    refresh();
    }
    if(ch == 50)
    {
    m_screenMode = screen_mode_signals;
    m_isNewMode = true;
    drawBottomMenu();
    refresh();
    }
    if(ch == 51)
    {
    m_screenMode = screen_mode_errors;
    m_isNewMode = true;
    drawBottomMenu();
    refresh();
    }
    if(ch == 52)
    {
    m_screenMode = screen_mode_summary;
    m_isNewMode = true;
    drawBottomMenu();
    refresh();
    }
     */

    //Decrement the page if the '8' key is pressed.
    if(ch == 56)
    {
        m_page--;
        if(m_page <1 ) m_page = 1;
        drawBottomMenu();
        paint(details);
        refresh();
    }

    //Increment the page if the '9' key is pressed.
    if(ch == 57)
    {
        m_page++;
        drawBottomMenu();
        paint(details);
        refresh();
    }

}

/*
 * screen.cpp
 *
 * Project: OpenSonATA
 * Version: 1.0
 * Author:  Jon Richards (current maintainer)
 *          The OpenSonATA code is the result of many programmers over many
 *          years.
 *
 * Manages the curses screen.
 *
 * Copyright 2010 The SETI Institute
 *
 * 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.
 *
 * Attribution must be: “Licensed through SETI” in all published
 * uses of the software including analytics based on the software,
 * combined and merged software, papers, articles, books, reports,
 * web pages, etc.
 */

/**
 * @file screen.cpp
 * Manages the curses screen.
 */

#include "screen.h"
#include "utils.h"

bool Screen::m_resizeEventOccurred = false;
int Screen::m_newRows = -1;
int Screen::m_newCols = -1;

/** Define the bottom menu area to have no height. In future
 * versions we will add a bottom menu, increase this to "1".
 */
#define BOTTOM_MENU_HEIGHT 0

/* Constructor. */
Screen::Screen()
{
    Screen::m_resizeEventOccurred = false;
    Screen::m_newRows = -1;
    Screen::m_newCols = -1;
    m_screenMode = screen_mode_components;
    m_page = 1;
    m_isNewMode = false;
    m_rows = m_newRows;
    m_cols = m_newCols;
    (void) signal(SIGINT, Screen::finish);         /* arrange interrupts to terminate */
    (void) signal(SIGWINCH, Screen::screenResize); /* Catch the resize signal */
    m_rows = m_newRows;
    m_cols = m_newCols;

}

/* Destructor. */
Screen::~Screen()
{
}

/*
 * Initialize the curses screen.
 */
void Screen::init()
{
    initscr();      /* initialize the curses library */
    keypad(stdscr, TRUE);  /* enable keyboard mapping */
    (void) nonl();         /* tell curses not to do NL->CR/NL on output */
    (void) cbreak();       /* take input chars one at a time, no wait for \n */
    (void) noecho();
    timeout(1);

    if (has_colors())
    {
        start_color();

        /*
         * Simple color assignment, often all we need.  Color pair 0 cannot
         * be redefined.  This example uses the same value for the color
         * pair as for the foreground color, though of course that is not
         * necessary:
         */
        init_pair(1, COLOR_RED,     COLOR_BLACK);
        init_pair(2, COLOR_GREEN,   COLOR_BLACK);
        init_pair(3, COLOR_YELLOW,  COLOR_BLACK);
        init_pair(4, COLOR_BLUE,    COLOR_BLACK);
        init_pair(5, COLOR_CYAN,    COLOR_BLACK);
        init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
        init_pair(7, COLOR_WHITE,   COLOR_BLACK);
        init_pair(8, COLOR_RED,   COLOR_RED);
        init_pair(9, COLOR_CYAN,   COLOR_CYAN);
        init_pair(10, COLOR_GREEN,   COLOR_GREEN);
        init_pair(11, COLOR_BLACK,   COLOR_BLACK);
        init_pair(12, COLOR_BLACK,   COLOR_YELLOW);
    }

    drawBottomMenu();

}

/*
 * Handles the screen rezise, which comes in as the result
 * of a SIGWINCH signal.
 * Uses ioctl(TIOCGWINSZ) to obtain the terminal size.
 *
 * @param sig the signal.
 */
void Screen::screenResize(int sig)
{
    struct winsize size;
    Screen::m_resizeEventOccurred = true;

    if (ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0)
    {
        Screen::m_newCols = size.ws_col;
        Screen::m_newRows = size.ws_row;
    }
}

/*
 * Catch the control-c to quit screen mode then exit.
 *
 * @param sig the signal.
 */
void Screen::finish(int sig)
{
    endwin();
    exit(0);
}

/*
 * Draws the bottom of the screen. The Function keys.
 */
void Screen::drawBottomMenu()
{

    /** @todo Multiple report screens need to be implemented. Until this is working
     * the bottom menu should not be displayed.
     */

    /*
       int centerPos = 0;
       int width = (m_cols/(screen_mode_last - screen_mode_components +1));

       for(int i = screen_mode_components; i<screen_mode_last; i++)
       {
       centerPos = (i+1)*width;
       if(m_screenMode == i) attrset(COLOR_PAIR(6)); //magenta
       else attrset(COLOR_PAIR(5)); //cyan

       if(i == screen_mode_components)
       {
       move(m_rows-1, centerPos - (int)strlen("1-Status")/2);
       addstr("F1 - Status");
       }
       if(i == screen_mode_signals)
       {
       move(m_rows-1, centerPos - (int)strlen("2-Signals")/2);
       addstr("F2 - Signals");
       }
       if(i == screen_mode_errors)
       {
       move(m_rows-1, centerPos - (int)strlen("2-Errors")/2);
       addstr("F3 - Errors");
       }
       if(i == screen_mode_summary)
       {
       move(m_rows-1, centerPos - (int)strlen("4-Summary")/2);
       addstr("F4 - Summary");
       }

       attrset(COLOR_PAIR(0));
       }
     */
    return;

}

/*
 * paint the screen.
 *
 * @param details the instance of the object containing the
 * information to display on the screen.
 */
void Screen::paint(Details *details)
{
    bool shouldRefresh = false;
    string line;

    if(Screen::m_resizeEventOccurred == true)
    {
        Screen::m_resizeEventOccurred = false;
        m_rows = m_newRows;
        m_cols = m_newCols;
        shouldRefresh = true;
        endwin();
        init();
        clear();
        drawBottomMenu();
    }

    if(m_screenMode == details->getType())
    {
        if(m_page > details->getNumPages(m_rows-BOTTOM_MENU_HEIGHT))
            m_page = details->getNumPages(m_rows-BOTTOM_MENU_HEIGHT);

        if(m_isNewMode == true)
        {
            m_page = details->getCurrentPageNumber();
            m_isNewMode = false;
            clear();
            shouldRefresh = true;
        }
        if(details->paint(m_page, m_cols, m_rows-BOTTOM_MENU_HEIGHT))
        {
            shouldRefresh = true;
        }
    }

    //Print the page
    line = "Page " + Utils::itos(m_page) + " of " +
        Utils::itos(details->getNumPages(m_rows-BOTTOM_MENU_HEIGHT));
    move(0, m_cols-line.size());
    addstr(line.c_str());

    if(details->getNumPages(m_rows-BOTTOM_MENU_HEIGHT) == m_page)
        line = "8=PgDn";
    else if(m_page == 1)
        line = "9=PgUp";
    else
        line = "8=PgDn, 9=PgUp";
    move(1, m_cols-line.size());
    line += "     ";
    addstr(line.c_str() );

    //Park cursor
    move(m_rows-1, m_cols-1);

    if(shouldRefresh == true)
    {
        refresh();
    }

}

/*
 * Process key presses.
 *
 * @param details the Details object to redraw of a key affects
 * the screen.
 */
void Screen::processKey(Details *details)
{
    char key[5];

    int ch = getch();

    //Print the keypress in the lower left corner
    //For debugging keypresses
    /*
       if(ch != -1)
       {
       sprintf(key, "%d", ch);
       move(m_rows-BOTTOM_MENU_HEIGHT, m_cols-4 );
       addstr("    ");
       move(m_rows-BOTTOM_MENU_HEIGHT, m_cols-4 );
       addstr(key);
       }
     */

    /** @todo Add multiple screens that change based on key presses. */
    /*
    if(ch == 49)
    {
    m_screenMode = screen_mode_components;
    m_isNewMode = true;
    drawBottomMenu();
    refresh();
    }
    if(ch == 50)
    {
    m_screenMode = screen_mode_signals;
    m_isNewMode = true;
    drawBottomMenu();
    refresh();
    }
    if(ch == 51)
    {
    m_screenMode = screen_mode_errors;
    m_isNewMode = true;
    drawBottomMenu();
    refresh();
    }
    if(ch == 52)
    {
    m_screenMode = screen_mode_summary;
    m_isNewMode = true;
    drawBottomMenu();
    refresh();
    }
     */

    //Decrement the page if the '8' key is pressed.
    if(ch == 56)
    {
        m_page--;
        if(m_page <1 ) m_page = 1;
        drawBottomMenu();
        paint(details);
        refresh();
    }

    //Increment the page if the '9' key is pressed.
    if(ch == 57)
    {
        m_page++;
        drawBottomMenu();
        paint(details);
        refresh();
    }

}


module Sinatra
  module RespondTo
    Version = '0.9.0'
  end
end

module Sinatra
  module RespondTo
    Version = '0.9.1'
  end
end


<?php
/**
 * Wysiwyg is a helper for outputting .
 * This helper REQUIRES the installation files for the wysiwyg helpers you will use
 *
 * @package       cake
 * @subpackage    cake.app.plugins.wysiwyg.views.helpers
 * @author:       Jose Diaz-Gonzalez
 * @version:      0.1
 * @email:        support@savant.be
 * @site:         http://josediazgonzalez.com
 */
App::uses('WysiwygAppHelper', 'Wysiwyg.View/Helper');

class WysiwygHelper extends WysiwygAppHelper {

/**
 * Helper dependencies
 *
 * @public array
 */
	public $helpers = array();

/**
 * Default Helper to use
 *
 * @public string
 **/
	public $helper = '';

/**
 * Array of whether a certain helper has been imported yet
 *
 */
	public $importedHelpers = array(
		'Form' => false,
		'Fck' => false,
		'Jwysiwyg' => false,
		'Nicedit' => false,
		'Markitup' => false,
		'Tinymce' => false
	);

/**
 * Array of defaults configuration for editors, specified when
 * importing Wysiwyg in your controller. For example:
 *
 * public $helpers = array(
 *     'editor' =>  'Tinymce',
 *     'editorDefaults' => array(
 *         'theme_advanced_toolbar_align' => 'right',
 *         )
 *     );
 */
	protected $_editorDefaults = array();

/**
 * Sets the $this->helper to the helper configured in the session
 *
 * @return void
 * @author Jose Diaz-Gonzalez
 **/
	public function __construct(View $View, $options) {
		$this->_View = $View;
		$this->request = $View->request;
		$options = array_merge(array('editor' => 'tinymce'), $options);
		if (isset($options['editorDefaults'])) {
			$this->_editorDefaults = $options['editorDefaults'];
		}
		$this->changeEditor($options['editor']);
	}

/**
 * Changes the editor on the fly
 *
 * @param string $editor String name of editor, excluding the word 'Helper'
 * @return void
 * @author Jose Diaz-Gonzalez
 **/
	public function changeEditor($editor) {
		$this->helper = ucfirst($editor);
		$prefix = '';
		if ($editor !== 'Form') {
			$prefix = 'Wysiwyg.';
		}
		if (!$this->importedHelpers[$this->helper]) {
			$this->importedHelpers[$this->helper] = true;
			$this->helpers[] = $prefix . $this->helper;
			$this->_helperMap = ObjectCollection::normalizeObjectArray($this->helpers);
		}
	}

/**
 * Returns the appropriate input field element
 *
 * @param string $field - used to build input name for views,
 * @param array $options Array of HTML attributes.
 * @param array $editorOptions Array of editor attributes for this input field
 * @return string
 * @author Jose Diaz-Gonzalez
 */
	public function input($field, $options = array(), $editorOptions = array()) {
		$editorHelper = $this->helper;
		$editorOptions = Set::merge($this->_editorDefaults, $editorOptions);

		return $this->$editorHelper->input($field, $options, $editorOptions);
	}

/**
 * Returns the appropriate textarea element
 *
 * @param string $field - used to build input name for views,
 * @param array $options Array of HTML attributes.
 * @param array $editorOptions Array of editor attributes for this textarea
 * @return string
 * @author Jose Diaz-Gonzalez
 */
	public function textarea($field, $options = array(), $editorOptions = array()) {
		$editorHelper = $this->helper;
		$editorOptions = Set::merge($this->_editorDefaults, $editorOptions);

		return $this->$editorHelper->textarea($field, $options, $editorOptions);
	}

}

<?php
/**
 * Wysiwyg is a helper for outputting .
 * This helper REQUIRES the installation files for the wysiwyg helpers you will use
 *
 * @package       cake
 * @subpackage    cake.app.plugins.wysiwyg.views.helpers
 * @author:       Jose Diaz-Gonzalez
 * @version:      0.1
 * @email:        support@savant.be
 * @site:         http://josediazgonzalez.com
 */
App::uses('WysiwygAppHelper', 'Wysiwyg.View/Helper');

class WysiwygHelper extends WysiwygAppHelper {

/**
 * Helper dependencies
 *
 * @public array
 */
	public $helpers = array();

/**
 * Default Helper to use
 *
 * @public string
 **/
	public $helper = '';

/**
 * Array of whether a certain helper has been imported yet
 *
 */
	public $importedHelpers = array(
		'Form' => false,
		'Fck' => false,
		'Jwysiwyg' => false,
		'Nicedit' => false,
		'Markitup' => false,
		'Tinymce' => false
	);

/**
 * Array of defaults configuration for editors, specified when
 * importing Wysiwyg in your controller. For example:
 *
 * public $helpers = array(
 *     'editor' =>  'Tinymce',
 *     'editorDefaults' => array(
 *         'theme_advanced_toolbar_align' => 'right',
 *         )
 *     );
 */
	protected $_editorDefaults = array();

/**
 * Sets the $this->helper to the helper configured in the session
 *
 * @return void
 * @author Jose Diaz-Gonzalez
 **/
	public function __construct(View $View, $options) {
		$this->_View = $View;
		$this->request = $View->request;
		$options = array_merge(array('editor' => 'tinymce'), $options);
		if (isset($options['editorDefaults'])) {
			$this->_editorDefaults = $options['editorDefaults'];
		}
		$this->changeEditor($options['editor']);
	}

/**
 * Changes the editor on the fly
 *
 * @param string $editor String name of editor, excluding the word 'Helper'
 * @return void
 * @author Jose Diaz-Gonzalez
 **/
	public function changeEditor($editor) {
		$this->helper = ucfirst($editor);
		$prefix = '';
		if ($editor !== 'Form') {
			$prefix = 'Wysiwyg.';
		}
		if (!$this->importedHelpers[$this->helper]) {
			$this->importedHelpers[$this->helper] = true;
			$this->helpers[] = $prefix . $this->helper;
			$this->_helperMap = ObjectCollection::normalizeObjectArray($this->helpers);
		}
	}

/**
 * Returns the appropriate input field element
 *
 * @param string $field - used to build input name for views,
 * @param array $options Array of HTML attributes.
 * @param array $editorOptions Array of editor attributes for this input field
 * @return string
 * @author Jose Diaz-Gonzalez
 */
	public function input($field = null, $options = array(), $editorOptions = array()) {
		$editorHelper = $this->helper;
		$editorOptions = Set::merge($this->_editorDefaults, $editorOptions);

		return $this->$editorHelper->input($field, $options, $editorOptions);
	}

/**
 * Returns the appropriate textarea element
 *
 * @param string $field - used to build input name for views,
 * @param array $options Array of HTML attributes.
 * @param array $editorOptions Array of editor attributes for this textarea
 * @return string
 * @author Jose Diaz-Gonzalez
 */
	public function textarea($field = null, $options = array(), $editorOptions = array()) {
		$editorHelper = $this->helper;
		$editorOptions = Set::merge($this->_editorDefaults, $editorOptions);

		return $this->$editorHelper->textarea($field, $options, $editorOptions);
	}

}


require 'rack'
require 'erb'
require 'toadhopper'

module Rack
  # Catches all exceptions raised from the app it wraps and
  # posts the results to hoptoad.
  class Hoptoad
    VERSION = '0.1.6'

    class Error < StandardError; end

    attr_accessor :api_key, :environment_filters, :report_under, :rack_environment, :notifier_class, :failsafe

    def initialize(app, api_key = nil, rack_environment = 'RACK_ENV')
      @app                 = app
      @api_key             = api_key
      @report_under        = %w(staging production)
      @rack_environment    = rack_environment
      @environment_filters = %w(AWS_ACCESS_KEY  AWS_SECRET_ACCESS_KEY AWS_ACCOUNT SSH_AUTH_SOCK)
      @notifier_class      = Toadhopper
      @failsafe            = $stderr
      yield self if block_given?
    end

    def call(env)
      status, headers, body =
        begin
          @app.call(env)
        rescue StandardError, LoadError, SyntaxError => boom
          notified = send_notification boom, env
          env['hoptoad.notified'] = notified
          raise
        end
      send_notification env['rack.exception'], env if env['rack.exception']
      [status, headers, body]
    end

    def environment_filter_keys
      @environment_filters.flatten
    end

    def environment_filter_regexps
      environment_filter_keys.map do |key|
        "^#{Regexp.escape(wrapped_key_for(key))}$"
      end
    end
  private
    def report?
      report_under.include?(rack_env)
    end

    def send_notification(exception, env)
      return true unless report?
      request      = Rack::Request.new(env)

      options = {
        :api_key           => api_key,
        :url               => "#{request.scheme}://#{request.host}#{request.path}",
        :params            => request.params,
        :framework_env     => rack_env,
        :notifier_name     => 'Rack::Hoptoad',
        :notifier_version  => VERSION,
        :environment       => environment_data_for(env),
        :session           => env['rack.session']
      }

      if result = toadhopper.post!(exception, options, {'X-Hoptoad-Client-Name' => 'Rack::Hoptoad'})
        if result.errors.empty?
          true
        else
          raise Error, "Status: #{result.status} #{result.errors.inspect}"
        end
      else
        raise Error, "No response from Toadhopper"
      end
    rescue Exception => e
      return unless @failsafe
      @failsafe.puts "Fail safe error caught: #{e.class}: #{e.message}"
      @failsafe.puts e.backtrace
      @failsafe.puts "Exception is #{exception.class}: #{exception.message}"
      @failsafe.puts exception.backtrace
      false
    end

    def rack_env
      ENV[rack_environment] || 'development'
    end

    def toadhopper
      toad         = @notifier_class.new(api_key)
      toad.filters = environment_filter_regexps
      toad
    end

    def environment_data_for(env)
      data = {}
      ENV.each do |key,value|
        data[wrapped_key_for(key)] = value.inspect
      end
      env.each do |key,value|
        data["rack[#{key.inspect}]"] = value.inspect
      end
      data
    end

    def wrapped_key_for(key)
      "ENV[#{key.inspect}]"
    end

    def extract_body(env)
      if io = env['rack.input']
        io.rewind if io.respond_to?(:rewind)
        io.read
      end
    end
  end
end

require 'rack/hoptoad_version'

require 'rack'
require 'erb'
require 'toadhopper'

module Rack
  # Catches all exceptions raised from the app it wraps and
  # posts the results to hoptoad.
  class Hoptoad
    class Error < StandardError; end

    attr_accessor :api_key, :environment_filters, :report_under, :rack_environment, :notifier_class, :failsafe

    def initialize(app, api_key = nil, rack_environment = 'RACK_ENV')
      @app                 = app
      @api_key             = api_key
      @report_under        = %w(staging production)
      @rack_environment    = rack_environment
      @environment_filters = %w(AWS_ACCESS_KEY  AWS_SECRET_ACCESS_KEY AWS_ACCOUNT SSH_AUTH_SOCK)
      @notifier_class      = Toadhopper
      @failsafe            = $stderr
      yield self if block_given?
    end

    def call(env)
      status, headers, body =
        begin
          @app.call(env)
        rescue StandardError, LoadError, SyntaxError => boom
          notified = send_notification boom, env
          env['hoptoad.notified'] = notified
          raise
        end
      send_notification env['rack.exception'], env if env['rack.exception']
      [status, headers, body]
    end

    def environment_filter_keys
      @environment_filters.flatten
    end

    def environment_filter_regexps
      environment_filter_keys.map do |key|
        "^#{Regexp.escape(wrapped_key_for(key))}$"
      end
    end
  private
    def report?
      report_under.include?(rack_env)
    end

    def send_notification(exception, env)
      return true unless report?
      request      = Rack::Request.new(env)

      options = {
        :api_key           => api_key,
        :url               => "#{request.scheme}://#{request.host}#{request.path}",
        :params            => request.params,
        :framework_env     => rack_env,
        :notifier_name     => 'Rack::Hoptoad',
        :notifier_version  => VERSION,
        :environment       => environment_data_for(env),
        :session           => env['rack.session']
      }

      if result = toadhopper.post!(exception, options, {'X-Hoptoad-Client-Name' => 'Rack::Hoptoad'})
        if result.errors.empty?
          true
        else
          raise Error, "Status: #{result.status} #{result.errors.inspect}"
        end
      else
        raise Error, "No response from Toadhopper"
      end
    rescue Exception => e
      return unless @failsafe
      @failsafe.puts "Fail safe error caught: #{e.class}: #{e.message}"
      @failsafe.puts e.backtrace
      @failsafe.puts "Exception is #{exception.class}: #{exception.message}"
      @failsafe.puts exception.backtrace
      false
    end

    def rack_env
      ENV[rack_environment] || 'development'
    end

    def toadhopper
      toad         = @notifier_class.new(api_key)
      toad.filters = environment_filter_regexps
      toad
    end

    def environment_data_for(env)
      data = {}
      ENV.each do |key,value|
        data[wrapped_key_for(key)] = value.inspect
      end
      env.each do |key,value|
        data["rack[#{key.inspect}]"] = value.inspect
      end
      data
    end

    def wrapped_key_for(key)
      "ENV[#{key.inspect}]"
    end

    def extract_body(env)
      if io = env['rack.input']
        io.rewind if io.respond_to?(:rewind)
        io.read
      end
    end
  end
end


<title>Docs
</title><meta http-equiv: 'Content-Type' content="text/html; charset=utf-8" />

Documentation
SproutCore Reference

ScrollView Touch Support

SproutCore's ScrollView comes with built-in support for touch-based
scrolling, including momentum and bouncing. In addition, it has (somewhat experimental)
support for scaling.

For many cases, just putting a view inside a ScrollView will "just work". Still, you may want
to set some settings.

Bouncing

By default, ScrollView will always bounce when scrolling vertically, regardless of the
content's height, but only bounce horizontally if the content is wider than the ScrollView.
This is controlled by two properties:

alwaysBounceHorizontal, which defaults to NO.

alwaysBounceVertical, which defaults to YES.

Scaling

ScrollView has support for scaling, which you can use through a few properties:

canScale. Specifies whether the content may be scaled. If YES, using two fingers
(in that classic "pinch gesture") will zoom the content.

minimumScale: The minimum scale value. Default: 0.25.

maximumScale: The maximum scale value. Default: 2.0.

touch.js


 /*
 * jquery.behavior JavaScript Library v0.1
 * http://rodpetrovic.com/jquery/behavior
 *
 * Copyright (c) 2009 Rodoljub Petrović
 * Licensed under the MIT
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Date: 2009-12-13
 */
(function ($) {
    $.fn.behavior = function () {
      var element = this;

      var attach = function (cls, config) {
        $(element).each(function () {
          this.behavior = new cls(this, config);
        });

        return $(element);
      }

      var get = function (index) {
        return $(element).get(index || 0).behavior;
      }

      var map = function (method, attributes) {
        $(element).each(function () {
          var obj = this.behavior;
          if (method in obj) {
            if (typeof obj[method] == 'function') {
              obj[method].apply(obj, attributes);
            } else {
              obj[method] = attributes;
            }
          }
        });

        return $(element);
      }

      if (arguments.length > 0 && typeof arguments[0] == 'function') {
        return attach(arguments[0], arguments.length > 1 ? arguments[1] : {});
      } else if (arguments.length > 0 && typeof arguments[0] == 'string') {
        return map(arguments[0], arguments.length > 1 ? arguments[1] : []);
      } else if (arguments.length > 0 && typeof arguments[0] == 'number') {
        return get(arguments[0]);
      } else {
        return get();
      }
    }
})(jQuery);

/*
 * jquery.behavior JavaScript Library v2.0
 * http://rodpetrovic.com/jquery/behavior
 *
 * Copyright 2010, Rodoljub Petrović
 * Licensed under the MIT
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Contributors:
 *  - Matjaž Lipuš
 *
 * Date: 2011-05-15
 */
/*jslint white: true, onevar: true, undef: true, nomen: true, regexp: true, plusplus: true, bitwise: true, newcap: true, strict: true, maxerr: 50, indent: 4 */
/*global jQuery */
(function ($, undef) {
    "use strict";

    function attach($jq, Behavior, config) {
        $jq.each(function () {
            if (!this.behavior) {
                this.behavior = {};
            }
            $.extend(this.behavior, new Behavior(this, config));
        });
    }

    function each($jq, property, attributes) {
        $jq.each(function () {
            var behavior = this.behavior;
            if (behavior && behavior[property] !== undef) {
                if (typeof behavior[property] === "function") {
                    behavior[property].apply(behavior, attributes || []);
                } else {
                    behavior[property] = attributes;
                }
            }
        });
    }

    $.fn.behavior = function (a, b) {
        var type = typeof a;

        if (type === "function") {
            attach(this, a, b || {});
            return this;
        }
        if (type === "string") {
            each(this, a, b);
            return this;
        }
        return this.get(a || 0).behavior;
    };
}(jQuery));