// 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 FastImageLoader(url) {
  var that = this
  this.loader = new Image()

  if (url) {
    this.load(url)
  }

  this.loader.onload = function () {
    if (typeof(that.onLoad) === 'function') {
      that.onLoad(this)
    }
  }

  this.loader.onerror = function () {

    if (typeof(that.onError) === 'function') {
      that.onError(this)
    }
  }
}

/**
 * Loads an URL
 * @param {string} url
 */
FastImageLoader.prototype.load = function (url) {
  this.loader.src = url
}


function FastPixiRender(canvasElement, options) {
  this.options = options

  this.stage = new PIXI.Stage(0x000000)

  this.canvasWidth = 643
  this.canvasHeight = 1149

  switch (this.options.render) {
    case 'pixi':
      this.renderer = new PIXI.autoDetectRenderer(this.canvasWidth, this.canvasHeight, canvasElement)
      break;
    case 'pixi-canvas':
      this.renderer = new PIXI.CanvasRenderer(this.canvasWidth, this.canvasHeight, canvasElement)
      break;
    case 'pixi-webgl':
      this.renderer = new PIXI.WebGLRenderer(this.canvasWidth, this.canvasHeight, canvasElement)
      break;
  }
}


FastPixiRender.prototype.draw = function (image) {

//  if (this.baseTexture) {
//    this.baseTexture.destroy()
//  }
  this.baseTexture = new PIXI.BaseTexture(image)

  if (this.texture) {
    this.texture.destroy(true)
  }
  this.texture = new PIXI.Texture(this.baseTexture)

  if (this.sprite) {
    this.sprite.setTexture(this.texture)
    //this.stage.removeChild(this.sprite)
  } else {
    this.sprite = new PIXI.Sprite(this.texture)
    this.stage.addChild(this.sprite)
  }

  this.renderer.render(this.stage)
}


function CanvasRender(canvasElement, options) {
  var checkForCanvasElement = function checkForCanvasElement() {
    if (!canvasElement) {
      throw new Error('Needs a canvas element')
    }

    this.displayWidth = canvasElement.offsetWidth
    this.displayHeight = canvasElement.offsetHeight

    if (!this.displayWidth || !this.displayHeight) {
      throw new Error('Unable to get display size canvas must have dimensions')
    }
  }

  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.displayWidth, this.displayHeight)
}


function WebGLRender(canvasElement, options) {
  var checkForCanvasElement = function checkForCanvasElement() {
    if (!canvasElement) {
      throw new Error('Needs a canvas element')
    }

    this.displayWidth = canvasElement.offsetWidth
    this.displayHeight = canvasElement.offsetHeight

    if (!this.displayWidth || !this.displayHeight) {
      throw new Error('Unable to get display size canvas must have dimensions')
    }
  }

  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)
    }
  }

  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) {
  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) {
  this.options = options || {}
  this.canvasElement = canvasElement

  if (this.options.render === 'webgl') {
    this.render = new WebGLRender(canvasElement, options)
  } else if (this.options.render.indexOf('pixi') !== -1) {
    this.render = new FastPixiRender(canvasElement, options)
  } else {
    this.render = new CanvasRender(canvasElement, options)
  }
}

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,
    FastImageLoader: FastImageLoader
  }
}
