// See http://jsperf.com/fastest-canvas-drawing/2
// See http://jsperf.com/canvas-drawimage-vs-putimagedata/3
// See http://jsperf.com/canvas-drawimage-vs-webgl-drawarrays


// -------------------------------------------------------------------------------------------------


function CanvasRender(canvasElement, options) {
  this.options = options
  this.context = canvasElement.getContext('2d')
}

CanvasRender.prototype.draw = function (image) {
  this.context.drawImage(image, 0, 0)
}

CanvasRender.prototype.clear = function () {
  this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height)
}


// -------------------------------------------------------------------------------------------------

/*
Based on http://www-cs-students.stanford.edu/~eparker/files/crunch/renderer.js
*/

/**
 * Constructs a renderer object.
 * @param {WebGLRenderingContext} gl The GL context.
 * @constructor
 */
var Renderer = function (gl) {
  /**
   * The GL context.
   * @type {WebGLRenderingContext}
   * @private
   */
  this.gl_ = gl;

  /**
   * The WebGLProgram.
   * @type {WebGLProgram}
   * @private
   */
  this.program_ = gl.createProgram();

  /**
   * @type {WebGLShader}
   * @private
   */
  this.vertexShader_ = this.compileShader_(
    Renderer.vertexShaderSource_, gl.VERTEX_SHADER);

  /**
   * @type {WebGLShader}
   * @private
   */
  this.fragmentShader_ = this.compileShader_(
    Renderer.fragmentShaderSource_, gl.FRAGMENT_SHADER);

  /**
   * Cached uniform locations.
   * @type {Object.<string, WebGLUniformLocation>}
   * @private
   */
  this.uniformLocations_ = {};

  /**
   * Cached attribute locations.
   * @type {Object.<string, WebGLActiveInfo>}
   * @private
   */
  this.attribLocations_ = {};

  /**
   * A vertex buffer containing a single quad with xy coordinates from [-1,-1]
   * to [1,1] and uv coordinates from [0,0] to [1,1].
   * @private
   */
  this.quadVertexBuffer_ = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer_);
  var vertices = new Float32Array(
    [-1.0, -1.0, 0.0, 1.0,
      +1.0, -1.0, 1.0, 1.0,
      -1.0, +1.0, 0.0, 0.0,
      1.0, +1.0, 1.0, 0.0]);
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);


  // Init shaders
  gl.attachShader(this.program_, this.vertexShader_);
  gl.attachShader(this.program_, this.fragmentShader_);
  gl.bindAttribLocation(this.program_, 0, 'vert');
  gl.linkProgram(this.program_);
  gl.useProgram(this.program_);
  gl.enableVertexAttribArray(0);

  gl.enable(gl.DEPTH_TEST);
  gl.disable(gl.CULL_FACE);

  var count = gl.getProgramParameter(this.program_, gl.ACTIVE_UNIFORMS);
  for (var i = 0; i < /** @type {number} */(count); i++) {
    var infoU = gl.getActiveUniform(this.program_, i);
    this.uniformLocations_[infoU.name] =
      gl.getUniformLocation(this.program_, infoU.name);
  }

  count = gl.getProgramParameter(this.program_, gl.ACTIVE_ATTRIBUTES);
  for (var j = 0; j < /** @type {number} */(count); j++) {
    var infoA = gl.getActiveAttrib(this.program_, j);
    this.attribLocations_[infoA.name] =
      gl.getAttribLocation(this.program_, infoA.name);
  }
};


Renderer.prototype.finishInit = function () {
  this.draw();
};


Renderer.prototype.createDxtTexture = function (
  dxtData
, width
, height
, format
) {
  var gl = this.gl_;
  var tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  gl.compressedTexImage2D(
    gl.TEXTURE_2D,
    0,
    format,
    width,
    height,
    0,
    dxtData);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  //gl.generateMipmap(gl.TEXTURE_2D)
  gl.bindTexture(gl.TEXTURE_2D, null);
  return tex;
};


Renderer.prototype.createRgb565Texture = function (rgb565Data, width, height) {
  var gl = this.gl_;
  var tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  gl.texImage2D(
    gl.TEXTURE_2D,
    0,
    gl.RGB,
    width,
    height,
    0,
    gl.RGB,
    gl.UNSIGNED_SHORT_5_6_5,
    rgb565Data);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  //gl.generateMipmap(gl.TEXTURE_2D)
  gl.bindTexture(gl.TEXTURE_2D, null);
  return tex;
};


Renderer.prototype.drawTexture = function (texture, width, height) {
  var gl = this.gl_;
  // draw scene
  gl.clearColor(0, 0, 0, 1);
  gl.clearDepth(1.0);
  gl.viewport(0, 0, width, height);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.uniform1i(this.uniformLocations_.texSampler, 0);

  gl.enableVertexAttribArray(this.attribLocations_.vert);
  gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer_);
  gl.vertexAttribPointer(this.attribLocations_.vert, 4, gl.FLOAT,
    false, 0, 0);
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
};


/**
 * Compiles a GLSL shader and returns a WebGLShader.
 * @param {string} shaderSource The shader source code string.
 * @param {number} type Either VERTEX_SHADER or FRAGMENT_SHADER.
 * @return {WebGLShader} The new WebGLShader.
 * @private
 */
Renderer.prototype.compileShader_ = function (shaderSource, type) {
  var gl = this.gl_;
  var shader = gl.createShader(type);
  gl.shaderSource(shader, shaderSource);
  gl.compileShader(shader);
  return shader;
};


/**
 * @type {string}
 * @private
 */
Renderer.vertexShaderSource_ = [
  'attribute vec4 vert;',
  'varying vec2 v_texCoord;',
  'void main() {',
  '  gl_Position = vec4(vert.xy, 0.0, 1.0);',
  '  v_texCoord = vert.zw;',
  '}'
].join('\n');


/**
 * @type {string}
 * @private
 */
Renderer.fragmentShaderSource_ = [
  'precision highp float;',
  'uniform sampler2D texSampler;',
  'varying vec2 v_texCoord;',
  'void main() {',
  '  gl_FragColor = texture2D(texSampler, v_texCoord);',
  '}'
].join('\n');

// -------------------------------------------------------------------------------------------------


function WebGLRender(canvasElement) {
  this.options = {
//    alpha: this.transparent,
//    antialias: !!antialias,
//    premultipliedAlpha: !!transparent,
//    stencil: true
  }

  try {
    this.ctx = canvasElement.getContext('experimental-webgl', this.options)
  } catch (e) {
    try {
      this.ctx = canvasElement.getContext('webgl', this.options)
    } catch (e2) {
      // fail, not able to get a context
      throw new Error('This browser does not support webGL. Try using the' +
        'canvas renderer' + this)
    }
  }

  if (!this.ctx.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc')) {
    this.dxtSupported = false
  }

  this.renderer = new Renderer(this.ctx)


  this.contextLost = false

//  gl.useProgram(this.shaderManager.defaultShader.program)

  //this.ctx.disable(this.ctx.DEPTH_TEST)
  //this.ctx.disable(this.ctx.CULL_FACE)

  //this.setup()
}

WebGLRender.prototype.setup = function () {
  // create shaders
  var vertexShaderSrc =
    'attribute vec2 aVertex;' +
    'attribute vec2 aUV;' +
    'varying vec2 vTex;' +
    'void main(void) {' +
    '  gl_Position = vec4(aVertex, 0.0, 1.0);' +
    '  vTex = aUV;' +
    '}';

  var fragmentShaderSrc =
    'precision highp float;' +
    'varying vec2 vTex;' +
    'uniform sampler2D sampler0;' +
    'void main(void){' +
    '  gl_FragColor = texture2D(sampler0, vTex);' +
    '}';

  var vertShaderObj = this.ctx.createShader(this.ctx.VERTEX_SHADER)
  var fragShaderObj = this.ctx.createShader(this.ctx.FRAGMENT_SHADER)
  this.ctx.shaderSource(vertShaderObj, vertexShaderSrc)
  this.ctx.shaderSource(fragShaderObj, fragmentShaderSrc)
  this.ctx.compileShader(vertShaderObj)
  this.ctx.compileShader(fragShaderObj)

  var progObj = this.ctx.createProgram()
  this.ctx.attachShader(progObj, vertShaderObj)
  this.ctx.attachShader(progObj, fragShaderObj)

  this.ctx.linkProgram(progObj)
  this.ctx.useProgram(progObj)

  var width = this.displayWidth
  var height = this.displayHeight

  this.ctx.viewport(0, 0, width, height)

  this.vertexBuff = this.ctx.createBuffer()
  this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.vertexBuff)
  this.ctx.bufferData(
    this.ctx.ARRAY_BUFFER
  , new Float32Array([
      -1 / 8
    , 1 / 6
    , -1 / 8
    , -1 / 6
    , 1 / 8
    , -1 / 6
    , 1 / 8
    , 1 / 6
    ])
  , this.ctx.STATIC_DRAW
  )

  this.texBuff = this.ctx.createBuffer()
  this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.texBuff)
  this.ctx.bufferData(
    this.ctx.ARRAY_BUFFER,
    new Float32Array([0, 1, 0, 0, 1, 0, 1, 1]),
    this.ctx.STATIC_DRAW)

  this.vloc = this.ctx.getAttribLocation(progObj, 'aVertex')
  this.tloc = this.ctx.getAttribLocation(progObj, 'aUV')

}

WebGLRender.prototype.draw = function (image) {
//  this.renderer.drawTexture(image, image.width, image.height)
  this.renderer.drawTexture(image, 643, 1149)

}


WebGLRender.prototype.drawOld = function (image) {
  var tex = this.ctx.createTexture()
  this.ctx.bindTexture(this.ctx.TEXTURE_2D, tex)
  this.ctx.texParameteri(
    this.ctx.TEXTURE_2D
  , this.ctx.TEXTURE_MIN_FILTER
  , this.ctx.NEAREST
  )
  this.ctx.texParameteri(
    this.ctx.TEXTURE_2D
  , this.ctx.TEXTURE_MAG_FILTER
  , this.ctx.NEAREST
  )
/*
  this.ctx.texParameteri(
    this.ctx.TEXTURE_2D
  , this.ctx.TEXTURE_MIN_FILTER
  , this.ctx.LINEAR
  )

  this.ctx.texParameteri(
    this.ctx.TEXTURE_2D
  , this.ctx.TEXTURE_WRAP_S
  , this.ctx.CLAMP_TO_EDGE
  )
  this.ctx.texParameteri(
    this.ctx.TEXTURE_2D
  , this.ctx.TEXTURE_WRAP_T
  , this.ctx.CLAMP_TO_EDGE
  )
*/
  this.ctx.generateMipmap(this.ctx.TEXTURE_2D)
  this.ctx.texImage2D(
    this.ctx.TEXTURE_2D
  , 0
  , this.ctx.RGBA
  , this.ctx.RGBA
  , this.ctx.UNSIGNED_BYTE
  , image
  )

  this.ctx.enableVertexAttribArray(this.vloc)
  this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.vertexBuff)
  this.ctx.vertexAttribPointer(this.vloc, 2, this.ctx.FLOAT, false, 0, 0)

  this.ctx.enableVertexAttribArray(this.tloc)
  this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.texBuff)
  this.ctx.bindTexture(this.ctx.TEXTURE_2D, tex)
  this.ctx.vertexAttribPointer(this.tloc, 2, this.ctx.FLOAT, false, 0, 0)
}

WebGLRender.prototype.clear = function () {

}


// -------------------------------------------------------------------------------------------------


function FastImageRender(canvasElement, options) {
  var that = this
  this.options = options || {}
  this.canvasElement = canvasElement
  this.timeoutId = null

  if (that.options.raf) {
    that.animLoop = function() {
      that.raf = requireAnimationFrame(that.animLoop)

      // separate render from drawing
      // render
    }
  }

  if (true) {
    this.loader = new Image()
    this.loader.onload = function () {
      if (that.options.timeout) {
        clearTimeout(that.timeoutId)
      }
      if (typeof(that.onLoad) === 'function') {
        that.onLoad(this)
      }
    }
    this.loader.onerror = function () {
      if (that.options.timeout) {
        clearTimeout(that.timeoutId)
      }
      if (typeof(that.onError) === 'function') {
        that.onError(this)
      }
    }
  }

  if (this.options.render === 'webgl') {
    this.render = new WebGLRender(canvasElement, options)
  } else {
    this.render = new CanvasRender(canvasElement, options)
  }


}

FastImageRender.prototype.destroy = function () {

  cancelAnimationFrame(this.raf)

  // delete onLoad & onError
}

FastImageRender.prototype.load = function (url, type) {
  var that = this

  if (that.options.timeout) {
    that.timeoutId = setTimeout(function () {
      if (typeof(that.onError) === 'function') {
        that.onError('timeout')
      }
    }, that.options.timeout)
  }

  if (this.options.textureLoader) {
    if (!this.textureLoader) {
      this.textureLoader = new TextureUtil.TextureLoader(this.render.ctx)
    }
    var texture = null
    if (type) {
      texture = this.render.ctx.createTexture();
      this.textureLoader.loadEx(url, texture, true, function () {
        if (typeof(that.onLoad) === 'function') {
          that.onLoad(texture)
        }
      }, type)
    } else {
      this.textureLoader.load(url, function (texture) {
        if (typeof(that.onLoad) === 'function') {
          that.onLoad(texture)
        }
      })
    }

  } else {

    this.loader.src = url
  }
}

FastImageRender.prototype.draw = function (image) {
  this.render.draw(image)
}

FastImageRender.prototype.clear = function () {
  this.render.clear()
}

Object.defineProperty(FastImageRender.prototype, 'canvasWidth', {
  get: function () {
    return this.canvasElement.width
  },
  set: function (width) {
    if (width) {
      if (width !== this.canvasElement.width) {
        this.canvasElement.width = width;
      }
    }
  }
})

Object.defineProperty(FastImageRender.prototype, 'canvasHeight', {
  get: function () {
    return this.canvasElement.height
  },
  set: function (height) {
    if (height) {
      if (height !== this.canvasElement.height) {
        this.canvasElement.height = height;
      }
    }
  }
})

Object.defineProperty(FastImageRender.prototype, 'displayWidth', {
  get: function () {
    return this.canvasElement.width
  },
  set: function (width) {
    if (width) {
      if (width !== this.canvasElement.width) {
        this.canvasElement.width = width;
      }
    }
  }
})

Object.defineProperty(FastImageRender.prototype, 'displayHeight', {
  get: function () {
    return this.canvasElement.height
  },
  set: function (height) {
    if (height) {
      if (height !== this.canvasElement.height) {
        this.canvasElement.height = height;
      }
    }
  }
})

Object.defineProperty(FastImageRender.prototype, 'canvasStyleWidth', {
  get: function () {
    return parseInt(this.canvasElement.style.width, 10)
  },
  set: function (width) {
    if (width) {
      var styleWidth = width + 'px'
      if (styleWidth !== this.canvasElement.style.width) {
        this.canvasElement.style.width = styleWidth;
      }
    }
  }
})

Object.defineProperty(FastImageRender.prototype, 'canvasStyleHeight', {
  get: function () {
    return parseInt(this.canvasElement.style.height, 10)
  },
  set: function (height) {
    if (height) {
      var styleHeight = height + 'px'
      if (styleHeight !== this.canvasElement.style.height) {
        this.canvasElement.style.height = height;
      }
    }
  }
})


// -------------------------------------------------------------------------------------------------


// Check for Non CommonJS world
if (typeof module !== 'undefined') {
  module.exports = {
    FastImageRender: FastImageRender
  }
}
