function ImageLoader()
{
  this.images = [];
  this.imagesByUrl = {};
  this.operationsByUrl = {};
}

ImageLoader.prototype = 
{
  getImage: function( url )
  {
    return ( this.isLoaded( url ) ? this.imagesByUrl[ url ] : null );
  },
  
  getImages: function()
  {
    return this.images;
  },

  getOperation: function( url )
  {
    return ( this.isLoading( url ) ? this.operationsByUrl[ url ] : null );
  },
  
  isLoaded: function( url )
  {
    return ( undefined !== this.imagesByUrl[ url ] );
  },
    
  isLoading: function( url )
  {
    return ( undefined !== this.operationsByUrl[ url ] );
  },
  
  // returns operation
  load: function( url )
  {
    return this.createImageLoadOperation( url );
  },
  
  // returns operation
  loadMultiple: function( urls )
  {
    var i;
    var operations = [];
    
    for ( i = 0; i < urls.length; ++i )
    {
      operations.push( this.createImageLoadOperation( urls[ i ] ) );
    }
    
    return new CompositeOperation( operations );
  },
  
  createImageLoadOperation: function( url )
  {
    var operation = new ImageLoadOperation( url );
    this.operationsByUrl[ url ] = operation;
    operation.completed.attach( this.imageLoadOperationCompleted.bind( this ) );
    return operation;
  },
  
  imageLoadOperationCompleted: function( operation )
  {
    this.images.push( operation.image );
    this.imagesByUrl[ operation.url ] = operation.image;
    this.operationsByUrl[ operation.url ] = undefined;
  }
};


function CompositeOperation( operations )
{
  this.operations = operations;
  this.incompleteOperations = 0;
  this.completed = new Event();
  this.pending = false;
}

CompositeOperation.prototype = 
{
  start: function()
  {
    var i;
    var operation;

    if ( !this.pending )
    {
      if ( this.operations.length < 1 )
      {
        this.onCompleted();
      }
      else
      {
        this.pending = true;
        this.incompleteOperations = this.operations.length;

        for ( i = 0; i < this.operations.length; ++i )
        {
          operation = this.operations[ i ];
          operation.completed.attach( this.operationCompleted.bind( this ) );
          operation.start();
        }
      }
    }
    
    return this;
  },
  
  operationCompleted: function()
  {
    if ( --this.incompleteOperations == 0 )
    {
      this.onCompleted();
    }
  },
  
  onCompleted: function()
  {
    this.pending = false;
    this.completed.fire( this );
  }
};

function ImageLoadOperation( url )
{
  this.url = url;
  this.image = null;
  this.pending = false
  this.complete = false;
  
  this.completed = new Event();
}

ImageLoadOperation.prototype =
{
  start: function()
  {
    if ( !this.started )
    {
      this.pending = true;
      this.complete = false;
      
      var image = this.image = new Image();
      image.onload = this.onImageLoaded.bind( this );
      
      image.src = this.url;
    }
    
    return this;
  },
  
  onImageLoaded: function()
  {
    this.pending = false;
    this.complete = true;
    this.completed.fire( this );
  }
};