// -*- mode: js2; indent-tabs-mode: nil; -*-

// A RectArray<X,k> is a subclass of Array with 'width', 'height',
// and 'payload' properties (where payload holds 'k').
// properties, and 'width*height*k' elements (each an X) in the array.
//
// A RectTypedByteArray<k> is a subclass of ArrayBuffer with 'width'
// and 'height' properties, and 'width*height*k' bytes in the buffer.
//
// The 'payload' property is initialized with the value of 'k'.
//
// (Felix would have used Array or ArrayView, but for the following bug: 
//   Bug 695438 - TypedArrays don't support new named properties
//   https://bugzilla.mozilla.org/show_bug.cgi?id=695438
//  and so he is resorting to extending ArrayBuffer instead.)
//
// Both classes add a .get(x,y[,k]) method that eases access to the
// contents, and a set(x,y[,k],value) method that eases modifying
// their entries.
//
// In addition, for those who prefer functional-style,
// RectArray.build,
// and RectByteTypedArray.build

var RectArray, RectByteTypedArray;

// This is a variant of RectArray that supports the same interface,
// but instead of attempting to extend Array (a practice fraught with
// peril), it instead makes a wrapper around another array.

var WrapArray, WrapByteTypedArray;

(function (){

   function defineReadOnly(x, name, value) {
     Object.defineProperty(x, name, {
                             value: value,
                             writable: false,
                             enumerable: true,
                             configurable: true });
   }

   RectArray = function RectArray(w,h,k) {
     if (k === undefined)
       k = 1;
     this.length = w*h*k;
     defineReadOnly(this, "width", w);
     defineReadOnly(this, "height", h);
     defineReadOnly(this, "payload", k);
   };

   RectByteTypedArray = function RectByteTypedArray(w,h,k) {
     if (k === undefined)
       k = 1;
     ArrayBuffer.call(this, w*h*k);
     defineReadOnly(this, "width", w);
     defineReadOnly(this, "height", h);
     defineReadOnly(this, "payload", k);
   };

   WrapArray = function WrapArray(w,h,k) {
     if (k === undefined)
       k = 1;
     this.backingArray = new Array(w*h*k);
     defineReadOnly(this, "width", w);
     defineReadOnly(this, "height", h);
     defineReadOnly(this, "payload", k);
   };

   WrapByteTypedArray = function WrapByteTypedArray(w,h,k) {
     if (k === undefined)
       k = 1;
     this.backingArray = new Uint8Array(new ArrayBuffer(w*h*k));
     defineReadOnly(this, "width", w);
     defineReadOnly(this, "height", h);
     defineReadOnly(this, "payload", k);
   };

   WrapArray.prototype.slice = function(a,b) this.backingArray.slice(a,b);
   WrapArray.prototype.join  = function(a) this.backingArray.join(a);

   RectArray.prototype = new Array();
   RectByteTypedArray.prototype = new ArrayBuffer();

   RectArray.prototype.get = function get(x,y,j) {
     if (j === undefined) j = 0;
     return this[(y*this.width+x)*this.payload+j];
   };

   RectArray.prototype.set = function set(x,y,j,value) {
     if (value === undefined) {
       value = j;
       j = 0;
     }
     this[(y*this.width+x)*this.payload+j] = value;
   };

   RectByteTypedArray.prototype.get = function get(x,y,j) {
     if (j === undefined) j = 0;
     return (new Uint8Array(this))[(y*this.width+x)*this.payload+j];
   };

   RectByteTypedArray.prototype.set = function set(x,y,j,value) {
     if (value === undefined) {
       value = j;
       j = 0;
     }
     (new Uint8Array(this))[(y*this.width+x)*this.payload+j] = value;
   };

   WrapArray.prototype.get = function get(x,y,j) {
     if (j === undefined) j = 0;
     return this.backingArray[(y*this.width+x)*this.payload+j];
   };

   WrapArray.prototype.set = function set(x,y,j,value) {
     if (value === undefined) {
       value = j;
       j = 0;
     }
     this.backingArray[(y*this.width+x)*this.payload+j] = value;
   };

   WrapByteTypedArray.prototype.get = function get(x,y,j) {
     if (j === undefined) j = 0;
     return this.backingArray[(y*this.width+x)*this.payload+j];
   };

   WrapByteTypedArray.prototype.set = function set(x,y,j,value) {
     if (value === undefined) {
       value = j;
       j = 0;
     }
     this.backingArray[(y*this.width+x)*this.payload+j] = value;
   };

   function viewToSource(view, width, height, payload) {
     var ret = "[";
     var i=0;
     var matrixNeedsNewline = false;
     for (var row=0; row < height; row++) {
       if (matrixNeedsNewline)
         ret += ",\n ";
       ret += "[";
       var rowNeedsComma = false;
       for (var x=0; x < width; x++) {
         if (rowNeedsComma)
           ret += ", ";
         if (payload == 1) {
           if (view[i] !== undefined)
             ret += view[i];
           i++;
         } else {
           var entryNeedsComma = false;
           ret += "(";
           for (var k=0; k < payload; k++) {
             // Might be inefficient (does JavaScript have
             // StringBuffers?, or use them internally, like Tamarin?)
             if (entryNeedsComma)
               ret += ", ";
             if (view[i] !== undefined)
               ret += view[i];
             entryNeedsComma = true;
             i++;
           }
           ret += ")";
         }
         rowNeedsComma = true;
       }
       ret += "]";
       matrixNeedsNewline = true;
     }
     ret += "]";
     return ret;
   }

   RectArray.prototype.toSource = function toSource() {
     return viewToSource(this,
                         this.width, this.height, this.payload);
   };

   RectByteTypedArray.prototype.toSource = function toSource() {
     return viewToSource(new Uint8Array(this),
                         this.width, this.height, this.payload);
   };

   WrapArray.prototype.toSource = function toSource() {
     return viewToSource(this.backingArray,
                         this.width, this.height, this.payload);
   };

   WrapByteTypedArray.prototype.toSource = function toSource() {
     return viewToSource(this.backingArray,
                         this.width, this.height, this.payload);
   };

   RectArray.prototype.map = function map(f) {
     var ret = Array.map(this, f);
     ret.__proto__ = RectArray.prototype;
     defineReadOnly(ret, "width", this.width);
     defineReadOnly(ret, "height", this.height);
     defineReadOnly(ret, "payload", this.payload);
     return ret;
   };

   WrapArray.prototype.map = function map(f) {
     var ret = new WrapArray(this.width, this.height, this.payload);
     ret.backingArray = this.backingArray.map(f);
     return ret;
   };

   // (Array<X>|ArrayView<X>) Nat Nat Nat (Nat Nat Nat -> X) -> void
   function fillArrayView(view, width, height, k, fill) {
     var i = 0;
     for (var y=0; y < height; y++) {
       for (var x=0; x < width; x++) {
         for (var j=0; j < k; j++) {
           view[i++] = fill(x, y, j);
         }
       }
     }
   }


   // Nat Nat (Nat Nat [Nat] -> X) -> RectArray<X,1>
   RectArray.build =
     function buildRectArray1(width, height, fill) {
       var a = new RectArray(width, height, 1);
       fillArrayView(a, width, height, 1, fill);
       return a;
     };

   RectArray.buildA =
     function buildRectArrayA(width, height, fill) {
       var a = new Array(width*height);
       fillArrayView(a, width, height, 1, fill);
       a.width = width;
       a.height = height;
       a.payload = 1;
       function F() { }
       F.prototype = RectArray.prototype;
       a.__proto__ = new F();
       return a;
     };

   // Nat Nat (Nat Nat Nat -> X) -> RectArray<X,4>
   RectArray.build4 =
     function buildRectArray4(width, height, fill) {
       var a = new RectArray(width, height, 4);
       fillArrayView(a, width, height, 4, fill);
       return a;
     };

   // Nat Nat (Nat Nat Nat -> X) -> RectArray<X,1>
   RectArray.buildN =
     function buildRectArrayN(width, height, n, fill) {
       var a = new RectArray(width, height, n);
       fillArrayView(a, width, height, n, fill);
       return a;
     };


   // Nat Nat (Nat Nat [Nat] -> Byte) -> RectTypedByteArray<4>
   RectByteTypedArray.build =
     function buildRectByteTypedArray1(width, height, fill) {
       var buf = new RectByteTypedArray(width, height, 1);
       fillArrayView(new Uint8Array(buf), width, height, 1, fill);
       return buf;
     };

   // Nat Nat (Nat Nat Nat -> Byte) -> RectTypedByteArray<4>
   RectByteTypedArray.build4 =
     function buildRectByteTypedArray4(width, height, fill) {
       var buf = new RectByteTypedArray(width, height, 4);
       fillArrayView(new Uint8Array(buf), width, height, 4, fill);
       return buf;
     };

   // Nat Nat (Nat Nat Nat -> Byte) -> RectTypedByteArray<4>
   RectByteTypedArray.buildN =
     function buildRectByteTypedArray4(width, height, n, fill) {
       var buf = new RectByteTypedArray(width, height, n);
       fillArrayView(new Uint8Array(buf), width, height, n, fill);
       return buf;
     };

   WrapArray.build =
     function buildWrapArray1(width, height, fill) {
       var a = new WrapArray(width, height, 1);
       fillArrayView(a, width, height, 1, fill);
       return a;
     };

   WrapArray.build =
     function buildWrapArray1(width, height, fill) {
       var a = new WrapArray(width, height, 1);
       fillArrayView(a.backingArray, width, height, 1, fill);
       return a;
     };

   WrapArray.build4 =
     function buildWrapArray1(width, height, fill) {
       var a = new WrapArray(width, height, 4);
       fillArrayView(a.backingArray, width, height, 4, fill);
       return a;
     };

   WrapArray.buildN =
     function buildWrapArray1(width, height, n, fill) {
       var a = new WrapArray(width, height, n);
       fillArrayView(a.backingArray, width, height, n, fill);
       return a;
     };

 })();
