require "luascape.reflect"

reflect.loadReflectors("graphics")

reflect.makeNamespaceGlobal("scape::graphics")
reflect.makeNamespaceGlobal("scape::graphics::uniforms")

graphics = {}
  function graphics.applyTexture(visual, texture, unit)
    local shader = visual:getOrCreateShader()
    local textureState = shader:getOrCreateTextureState()
    if unit == nil then unit = 0 end
    if type(texture) == "string" then
      texture = Texture.loadFromFile(texture)
    end
    textureState:setTexture(unit, texture)
  end

  function graphics.createShaderProgramState(vertexProgramFilename, vertexUniforms, fragmentProgramFilename, fragmentUniforms)
    local shaderProgramState = Display.instance():createShaderProgramState()
    shaderProgramState.vertexProgram = ShaderProgram.loadFromFile(vertexProgramFilename, ShaderProgram.TYPE_VERTEX_PROGRAM)
    if vertexUniforms then
      for i,uniform in pairs(vertexUniforms) do shaderProgramState.vertexUniforms:add(uniform) end
    end
    shaderProgramState.fragmentProgram = ShaderProgram.loadFromFile(fragmentProgramFilename, ShaderProgram.TYPE_FRAGMENT_PROGRAM)
    if fragmentUniforms then
      for i,uniform in pairs(fragmentUniforms) do shaderProgramState.fragmentUniforms:add(uniform) end
    end
    return shaderProgramState
  end

  function graphics.applyVertexProgram(visual, filename, uniforms)
    local shader = visual:getOrCreateShader()
    local shaderProgramState = shader:getOrCreateShaderProgramState()
    local shaderProgram = ShaderProgram.loadFromFile(filename, ShaderProgram.TYPE_VERTEX_PROGRAM)
    shaderProgramState.vertexProgram = shaderProgram
    if uniforms then
      for i,uniform in pairs(uniforms) do shaderProgramState.vertexUniforms:add(uniform) end
    end
    return shaderProgramState
  end
  
  function graphics.applyFragmentProgram(visual, filename, uniforms)
    local shader = visual:getOrCreateShader()
    local shaderProgramState = shader:getOrCreateShaderProgramState()
    local shaderProgram = ShaderProgram.loadFromFile(filename, ShaderProgram.TYPE_FRAGMENT_PROGRAM)
    shaderProgramState.fragmentProgram = shaderProgram
    if uniforms then
      for i,uniform in pairs(uniforms) do shaderProgramState.fragmentUniforms:add(uniform) end
    end
    return shaderProgramState
  end

  function graphics.createBlendingStates(sourceFactor, destinationFactor)
    local display = Display.instance()
    local blendState = display:createAlphaBlendState()
    blendState.enabled = true
    blendState.sourceFactor = sourceFactor
    blendState.destinationFactor = destinationFactor
    local depthBufferState = display:createDepthBufferState()
    depthBufferState.writable = false
    return blendState, depthBufferState
  end
  
  function graphics.applyBlending(visual, sourceFactor, destinationFactor)
    local blendState, depthBufferState = graphics.createBlendingStates(sourceFactor, destinationFactor)
    local shader = visual.material
    shader.alphaBlendState = blendState
    shader.depthBufferState = depthBufferState    
    return blendState, depthBufferState
  end
  
  function graphics.applyAdditiveBlending(visual)
    graphics.applyBlending(visual, AlphaBlendState.BLEND_ONE, AlphaBlendState.BLEND_ONE)
  end
  
  function graphics.createAlphaBlendStates(alpha)
    local blendState, depthBufferState =
      graphics.createBlendingStates(AlphaBlendState.BLEND_CONSTANT_ALPHA, AlphaBlendState.BLEND_INV_CONSTANT_ALPHA)
    blendState.blendColor = Color(1, 1, 1, alpha)
    return blendState, depthBufferState
  end
  
  function graphics.applyAlphaTesting(visual, referenceAlpha, comparison)
    local shader = visual.material
    local state = shader:getOrCreateAlphaTestState()
    state.enabled = true
    state.reference = referenceAlpha
    if not comparison then comparison = AlphaTestState.COMPARE_GREATER end
    state["function"] = comparison
  end
  
  function graphics.applyBackFaceCulling(visual, doCull)
    local shader = visual.material
    local cullState = shader:getOrCreateCullState()
    if doCull then
      cullState.mode = CullState.CULL_CCW
    else
      cullState.mode = CullState.CULL_NONE
    end
  end

  function graphics.applyColor(visual, ambient, diffuse, specular, shininess)
    local shader = visual:getOrCreateShader()
    local materialState = shader:getOrCreateMaterialState()
    if ambient then materialState.ambient = ambient end
    if diffuse then materialState.diffuse = diffuse end
    if specular then materialState.specular = specular end
    if shininess then materialState.shininess = shininess end
    return materialState
  end
  
  function graphics.createMaterialState(ambient, diffuse, specular, shininess)
    local materialState = Display.instance():createMaterialState()
    if ambient then materialState.ambient = ambient end
    if diffuse then materialState.diffuse = diffuse end
    if specular then materialState.specular = specular end
    if shininess then materialState.shininess = shininess end
    return materialState
  end  
  
  function graphics.applyLightState(visual, enabled)
    local shader = visual:getOrCreateShader()
    local lightState = shader:getOrCreateLightState()
    lightState.enabled = enabled
  end  
  
  function graphics.createLightState(enabled)
    local lightState = Display.instance():createLightState()
    if enabled ~= nil then lightState.enabled = enabled end
    return lightState
  end
  
  function graphics.createTextureState(texture)
    local textureState = Display.instance():createTextureState()
    if type(texture) == "string" then
      texture = Texture.loadFromFile(texture)
    end
    textureState:setTexture(0, texture)
    return textureState
  end

  function graphics.createCullState(mode)
    if not mode then mode = CullState.CULL_CCW end
    local cullState = Display.instance():createCullState()
    cullState.mode = mode
    return cullState
  end

  function graphics.createRenderTarget(width, height)
    local texture = DynamicTexture()
    texture:create(width, height, PixelFormat.RGB888, PixelBuffer.DYNAMIC)
    local renderTarget = Display.instance():createRenderTarget(PixelBufferFormat())
    renderTarget.textures:add(texture)
    return renderTarget, texture
  end
