local dkjson = require( "lib.dkjson" )
local love = assert( love )
local lg = assert( love.graphics )
local math = math
local mat = require( "mat4" )
local strings = require( "i18n" )


local attributeNames = {
  POSITION = "VertexPosition",
  NORMAL = "VertexNormal",
  TEXCOORD_0 = "VertexTexCoord",
}

local types = {
  ["SCALAR"] = 1,
  ["VEC3"] = 3,
  ["VEC2"] = 2,
}

local componentTypes = {
  [5121] = "byte",
  [5123] = "unorm16",
  [5126] = "float",
}

local elementComponentTypes = {
  [5123] = "uint16",
  [5126] = "uint32",
}

local filterTypes = {
  [9729] = "linear",
  [9987] = "linear",
}

local asset
local buffer
do
  local fd = assert( love.filesystem.newFileData( "models/bell.gltf" ) )
  asset = dkjson.decode( fd:getString() )
  buffer = love.filesystem.newFileData( "models/bell.bin" )
end

--load all the bufferViews
local bufferViews = {}
for i, view in pairs( asset.bufferViews ) do
  bufferViews[i] = love.data.newByteData( buffer, view.byteOffset, view.byteLength )
end

local function attributeFormat( attributeName, accessor )
  return {
    assert(attributeNames[attributeName]),
    assert(componentTypes[accessor.componentType]),
    assert(types[accessor.type]) }
end

local function lgTexture( matIdx )
  local tex = asset.textures[1+matIdx]
  local img = asset.images[1+tex.source]
  local smpl = asset.samplers[1+tex.sampler]
  img = love.graphics.newImage( "tex/"..img.uri, { mipmaps = true } )
  img:setWrap( "repeat", "repeat" )
  img:setFilter( filterTypes[smpl.minFilter], filterTypes[smpl.magFilter] )
  img:setMipmapFilter( filterTypes[smpl.minFilter] )
  return img
end

local function lgMesh( primitive )
  assert( not( primitive.mode ) ) --triangles only

  local finalMesh
  for name, accIdx in pairs( primitive.attributes ) do
    local acc = asset.accessors[accIdx+1]

    local mesh = love.graphics.newMesh(
      {attributeFormat(name, acc)}, --vertexformat
      acc.count,
      "triangles",
      "static"
    )

    local bfv = bufferViews[1+acc.bufferView]
    mesh:setVertices( bfv )

    if finalMesh then
      finalMesh:attachAttribute( attributeNames[name], mesh )
    else
      finalMesh = mesh
    end

  end


  do
    local acc = asset.accessors[1+primitive.indices]
    local bfv = bufferViews[1+acc.bufferView]
    finalMesh:setVertexMap(bfv, elementComponentTypes[acc.componentType] )
  end

  if primitive.material then finalMesh:setTexture( lgTexture( primitive.material ) ) end
  return finalMesh
end


local bellMesh = lgMesh( asset.meshes[1].primitives[1] )
--each bell has the following positional data:
-- scale, x, z, colorparam
local bells = {}
local bellInstanceMesh
local rings = { poly = false }
do 
  rings[true] = love.audio.newSource( "sfx/bellC2.ogg", "static" )
  rings[false] = love.audio.newSource( "sfx/bellC2.ogg", "static" )
end
rings[true]:setVolume( 0.1 )
rings[false]:setVolume( 0.1 )

local function getPitch( size )
  return 5.0 / size
end

do
  local c, s = math.cos( 2*math.pi / 7 ), -math.sin( 2*math.pi / 7 )
  local x, z = math.cos(math.pi/7), -math.sin( math.pi/7 )
  local pitches = { 0, 5, 8, 10, 12, 22, 26 }
  for i = 1, #pitches do
    pitches[i] = math.pow( 2.0, pitches[i] / 12.0 )
  end

  local bellMt = { __index = { get = function(t)
        return t[2], t[3], t[1]
      end }}

  local r = 20
  for i = 1, 7 do
    bells[i] = setmetatable({ 5.0 / pitches[i], r * x, r * z, (i-1)/6 }, bellMt )
    x, z = c*x+s*z, c*z-s*x
  end
  bellInstanceMesh = lg.newMesh({{"bellInstance","float",4}}, bells, nil, "static" )
  bellMesh:attachAttribute( "bellInstance", bellInstanceMesh,
    "perinstance" )
end

local t = {}

t.shader = require( "shaders.bell" )
t.mesh = bellMesh
t.timer = 0


function t.draw( view, proj, light )
  local s = t.shader
  love.graphics.setColor(1,1,1,1)
  s:send( "view", "column", view )
  s:send( "proj", "column", proj )
  s:send("light", light )
  lg.setShader(s)
  lg.drawInstanced( t.mesh, 7 )
end

local queue = {}
function t.queue( i, ticks )
  local time = ticks + (queue.time or t.timer)
  local q = queue
  while q.next do
    q = q.next
  end
  q.time = time
  q.bell = i
  q.next = {}
end


function t.transpose( i, j )
  io.flush()
  bells[i][1], bells[j][1] = bells[j][1], bells[i][1]
  bellInstanceMesh:setVertices( bells ) 
  bellMesh:attachAttribute( "bellInstance", bellInstanceMesh,
    "perinstance" )
  t.ring( i )
  t.queue( j, 100 )
end

function t.ring( i )
  local b = rings.poly
  b = not( b )
  rings.poly = b
  rings[b]:stop()
  rings[b]:setPitch( getPitch( bells[i][1] ) )
  rings[b]:setPosition( bells[i][2], 0, bells[i][3] )
  rings[b]:play()
end

function t.ringAll( i )
  local b = i
  for j = 0, 6 do
    t.queue( b, j * 100 )
    b = b + 1
    if b > 7 then b = 1 end
  end
end

function t.nearBell(x, z)
  for i, bell in ipairs( bells ) do
    local bx, bz, br = bell:get()
    if (x-bx)*(x-bx)+(z-bz)*(z-bz) < 2 * (br*br) then
      return i
    end
  end
end

t.solution = {
  bells[7],
  bells[6],
  bells[4],
  bells[5],
  bells[3],
  bells[2],
  bells[1]
}
function t.checkWin( i )
  local idx = i
  for j = 1, 7 do
    idx = idx + 1
    if idx > 7 then idx = 1 end
    local bell = t.solution[j]
    if bell ~= bells[idx] then return false end
  end
  t.winTime = t.timer + 700
  return true
end

local trans = {}
function t.update( enteringCircle, leavingCircle, x, z )
  t.timer = t.timer + 1

  if t.winTime and t.timer > t.winTime then
    local strings = require( "i18n" )
    local slideshow = require( "scenes.slideshow" )
    return slideshow.play( strings.win, love.event.quit, "tex/win.jpg", "sfx/mus.ogg" )
  end

  if queue.time and (queue.time <= t.timer) then
    t.ring( queue.bell )
    queue = queue.next or {}
  end

  if enteringCircle then trans[1] = enteringCircle end
  if leavingCircle then trans[2] = leavingCircle end
  if trans[1] and trans[2] then
    t.transpose( trans[1], trans[2] )
    trans[1], trans[2] = nil
  end

  if t.winTime then
    return strings.phoneWin
  end

  t.near = t.nearBell( x, z )
  if t.near then 
    return strings.ringBell:format(strings.keyUse)
  end
end

t.circles = bells
t.near = false

return t