text: var iota = require("iota-array")
  var isBuffer = require("is-buffer")
  
  var hasTypedArrays  = ((typeof Float64Array) !== "undefined")
  
  function compare1st(a, b) {
    return a[0] - b[0]
  }
  
  function order() {
    var stride = this.stride
    var terms = new Array(stride.length)
    var i
    for(i=0; i<terms.length; ++i) {
      terms[i] = [Math.abs(stride[i]), i]
    }
    terms.sort(compare1st)
    var result = new Array(terms.length)
    for(i=0; i<result.length; ++i) {
      result[i] = terms[i][1]
    }
    return result
  }
  
  function compileConstructor(dtype, dimension) {
    var className = ["View", dimension, "d", dtype].join("")
    if(dimension < 0) {
      className = "View_Nil" + dtype
    }
    var useGetters = (dtype === "generic")
  
    if(dimension === -1) {
      //Special case for trivial arrays
      var code =
        "function "+className+"(a){this.data=a;};\
  var proto="+className+".prototype;\
  proto.dtype='"+dtype+"';\
  proto.index=function(){return -1};\
  proto.size=0;\
  proto.dimension=-1;\
  proto.shape=proto.stride=proto.order=[];\
  proto.lo=proto.hi=proto.transpose=proto.step=\
  function(){return new "+className+"(this.data);};\
  proto.get=proto.set=function(){};\
  proto.pick=function(){return null};\
  return function construct_"+className+"(a){return new "+className+"(a);}"
      var procedure = new Function(code)
      return procedure()
    } else if(dimension === 0) {
      //Special case for 0d arrays
      var code =
        "function "+className+"(a,d) {\
  this.data = a;\
  this.offset = d\
  };\
  var proto="+className+".prototype;\
  proto.dtype='"+dtype+"';\
  proto.index=function(){return this.offset};\
  proto.dimension=0;\
  proto.size=1;\
  proto.shape=\
  proto.stride=\
  proto.order=[];\
  proto.lo=\
  proto.hi=\
  proto.transpose=\
  proto.step=function "+className+"_copy() {\
  return new "+className+"(this.data,this.offset)\
  };\
  proto.pick=function "+className+"_pick(){\
  return TrivialArray(this.data);\
  };\
  proto.valueOf=proto.get=function "+className+"_get(){\
  return "+(useGetters ? "this.data.get(this.offset)" : "this.data[this.offset]")+
  "};\
  proto.set=function "+className+"_set(v){\
  return "+(useGetters ? "this.data.set(this.offset,v)" : "this.data[this.offset]=v")+"\
  };\
  return function construct_"+className+"(a,b,c,d){return new "+className+"(a,d)}"
      var procedure = new Function("TrivialArray", code)
      return procedure(CACHED_CONSTRUCTORS[dtype][0])
    }
  
    var code = ["'use strict'"]
  
    //Create constructor for view
    var indices = iota(dimension)
    var args = indices.map(function(i) { return "i"+i })
    var index_str = "this.offset+" + indices.map(function(i) {
          return "this.stride[" + i + "]*i" + i
        }).join("+")
    var shapeArg = indices.map(function(i) {
        return "b"+i
      }).join(",")
    var strideArg = indices.map(function(i) {
        return "c"+i
      }).join(",")
    code.push(
      "function "+className+"(a," + shapeArg + "," + strideArg + ",d){this.data=a",
        "this.shape=[" + shapeArg + "]",
        "this.stride=[" + strideArg + "]",
        "this.offset=d|0}",
      "var proto="+className+".prototype",
      "proto.dtype='"+dtype+"'",
      "proto.dimension="+dimension)
  
    //view.size:
    code.push("Object.defineProperty(proto,'size',{get:function "+className+"_size(){\
  return "+indices.map(function(i) { return "this.shape["+i+"]" }).join("*"),
  "}})")
  
    //view.order:
    if(dimension === 1) {
      code.push("proto.order=[0]")
    } else {
      code.push("Object.defineProperty(proto,'order',{get:")
      if(dimension < 4) {
        code.push("function "+className+"_order(){")
        if(dimension === 2) {
          code.push("return (Math.abs(this.stride[0])>Math.abs(this.stride[1]))?[1,0]:[0,1]}})")
        } else if(dimension === 3) {
          code.push(
  "var s0=Math.abs(this.stride[0]),s1=Math.abs(this.stride[1]),s2=Math.abs(this.stride[2]);\
  if(s0>s1){\
  if(s1>s2){\
  return [2,1,0];\
  }else if(s0>s2){\
  return [1,2,0];\
  }else{\
  return [1,0,2];\
  }\
  }else if(s0>s2){\
  return [2,0,1];\
  }else if(s2>s1){\
  return [0,1,2];\
  }else{\
  return [0,2,1];\
  }}})")
        }
      } else {
        code.push("ORDER})")
      }
    }
  
    //view.set(i0, ..., v):
    code.push(
  "proto.set=function "+className+"_set("+args.join(",")+",v){")
    if(useGetters) {
      code.push("return this.data.set("+index_str+",v)}")
    } else {
      code.push("return this.data["+index_str+"]=v}")
    }
  
    //view.get(i0, ...):
    code.push("proto.get=function "+className+"_get("+args.join(",")+"){")
    if(useGetters) {
      code.push("return this.data.get("+index_str+")}")
    } else {
      code.push("return this.data["+index_str+"]}")
    }
  
    //view.index:
    code.push(
      "proto.index=function "+className+"_index(", args.join(), "){return "+index_str+"}")
  
    //view.hi():
    code.push("proto.hi=function "+className+"_hi("+args.join(",")+"){return new "+className+"(this.data,"+
      indices.map(function(i) {
        return ["(typeof i",i,"!=='number'||i",i,"<0)?this.shape[", i, "]:i", i,"|0"].join("")
      }).join(",")+","+
      indices.map(function(i) {
        return "this.stride["+i + "]"
      }).join(",")+",this.offset)}")
  
    //view.lo():
    var a_vars = indices.map(function(i) { return "a"+i+"=this.shape["+i+"]" })
    var c_vars = indices.map(function(i) { return "c"+i+"=this.stride["+i+"]" })
    code.push("proto.lo=function "+className+"_lo("+args.join(",")+"){var b=this.offset,d=0,"+a_vars.join(",")+","+c_vars.join(","))
    for(var i=0; i<dimension; ++i) {
      code.push(
  "if(typeof i"+i+"==='number'&&i"+i+">=0){\
  d=i"+i+"|0;\
  b+=c"+i+"*d;\
  a"+i+"-=d}")
    }
    code.push("return new "+className+"(this.data,"+
      indices.map(function(i) {
        return "a"+i
      }).join(",")+","+
      indices.map(function(i) {
        return "c"+i
      }).join(",")+",b)}")
  
    //view.step():
    code.push("proto.step=function "+className+"_step("+args.join(",")+"){var "+
      indices.map(function(i) {
        return "a"+i+"=this.shape["+i+"]"
      }).join(",")+","+
      indices.map(function(i) {
        return "b"+i+"=this.stride["+i+"]"
      }).join(",")+",c=this.offset,d=0,ceil=Math.ceil")
    for(var i=0; i<dimension; ++i) {
      code.push(
  "if(typeof i"+i+"==='number'){\
  d=i"+i+"|0;\
  if(d<0){\
  c+=b"+i+"*(a"+i+"-1);\
  a"+i+"=ceil(-a"+i+"/d)\
  }else{\
  a"+i+"=ceil(a"+i+"/d)\
  }\
  b"+i+"*=d\
  }")
    }
    code.push("return new "+className+"(this.data,"+
      indices.map(function(i) {
        return "a" + i
      }).join(",")+","+
      indices.map(function(i) {
        return "b" + i
      }).join(",")+",c)}")
  
    //view.transpose():
    var tShape = new Array(dimension)
    var tStride = new Array(dimension)
    for(var i=0; i<dimension; ++i) {
      tShape[i] = "a[i"+i+"]"
      tStride[i] = "b[i"+i+"]"
    }
    code.push("proto.transpose=function "+className+"_transpose("+args+"){"+
      args.map(function(n,idx) { return n + "=(" + n + "===undefined?" + idx + ":" + n + "|0)"}).join(";"),
      "var a=this.shape,b=this.stride;return new "+className+"(this.data,"+tShape.join(",")+","+tStride.join(",")+",this.offset)}")
  
    //view.pick():
    code.push("proto.pick=function "+className+"_pick("+args+"){var a=[],b=[],c=this.offset")
    for(var i=0; i<dimension; ++i) {
      code.push("if(typeof i"+i+"==='number'&&i"+i+">=0){c=(c+this.stride["+i+"]*i"+i+")|0}else{a.push(this.shape["+i+"]);b.push(this.stride["+i+"])}")
    }
    code.push("var ctor=CTOR_LIST[a.length+1];return ctor(this.data,a,b,c)}")
  
    //Add return statement
    code.push("return function construct_"+className+"(data,shape,stride,offset){return new "+className+"(data,"+
      indices.map(function(i) {
        return "shape["+i+"]"
      }).join(",")+","+
      indices.map(function(i) {
        return "stride["+i+"]"
      }).join(",")+",offset)}")
  
    //Compile procedure
    var procedure = new Function("CTOR_LIST", "ORDER", code.join("\n"))
    return procedure(CACHED_CONSTRUCTORS[dtype], order)
  }
  
  function arrayDType(data) {
    if(isBuffer(data)) {
      return "buffer"
    }
    if(hasTypedArrays) {
      switch(Object.prototype.toString.call(data)) {
        case "[object Float64Array]":
          return "float64"
        case "[object Float32Array]":
          return "float32"
        case "[object Int8Array]":
          return "int8"
        case "[object Int16Array]":
          return "int16"
        case "[object Int32Array]":
          return "int32"
        case "[object Uint8Array]":
          return "uint8"
        case "[object Uint16Array]":
          return "uint16"
        case "[object Uint32Array]":
          return "uint32"
        case "[object Uint8ClampedArray]":
          return "uint8_clamped"
      }
    }
    if(Array.isArray(data)) {
      return "array"
    }
    return "generic"
  }
  
  var CACHED_CONSTRUCTORS = {
    "float32":[],
    "float64":[],
    "int8":[],
    "int16":[],
    "int32":[],
    "uint8":[],
    "uint16":[],
    "uint32":[],
    "array":[],
    "uint8_clamped":[],
    "buffer":[],
    "generic":[]
  }
  
  ;(function() {
    for(var id in CACHED_CONSTRUCTORS) {
      CACHED_CONSTRUCTORS[id].push(compileConstructor(id, -1))
    }
  });
  
  function wrappedNDArrayCtor(data, shape, stride, offset) {
    if(data === undefined) {
      var ctor = CACHED_CONSTRUCTORS.array[0]
      return ctor([])
    } else if(typeof data === "number") {
      data = [data]
    }
    if(shape === undefined) {
      shape = [ data.length ]
    }
    var d = shape.length
    if(stride === undefined) {
      stride = new Array(d)
      for(var i=d-1, sz=1; i>=0; --i) {
        stride[i] = sz
        sz *= shape[i]
      }
    }
    if(offset === undefined) {
      offset = 0
      for(var i=0; i<d; ++i) {
        if(stride[i] < 0) {
          offset -= (shape[i]-1)*stride[i]
        }
      }
    }
    var dtype = arrayDType(data)
    var ctor_list = CACHED_CONSTRUCTORS[dtype]
    while(ctor_list.length <= d+1) {
      ctor_list.push(compileConstructor(dtype, ctor_list.length-1))
    }
    var ctor = ctor_list[d+1]
    return ctor(data, shape, stride, offset)
  }
  
  module.exports = wrappedNDArrayCtor
  