local love = love
local step = 1.0 / 120
local sitelenpona
local text
local marble
local wave
local particles
local audio
local recorder
local DetectCollision
local OnImpact
local OnVictory
local BeatScore
local Draw
local Update
local ExtrapolateBeatScore
local _ExtrapolateBeatScore

local state 
state = {

  Reset = function()
    state.tick = 0
    state.beat = {}
    state.currentBeat = 1
    state.timeToSimulate = 0.0
    state.longestStreak = 0
    state.currentStreak = 0
    state.lastCollisionTick = 0
  end,

  finishTime = 0.0,
  timeToSimulate = 0.0,
  isGameStarted = false,
  lastBeatScore = 0.0,
  currentBeat = 1,
  longestStreak = 0,
  currentStreak = 0,

  beat = {
    t = nil,
    mu = nil,
  },
}

local function UpdateWindowTransform( w, h )
  local d = math.min( w, h )
  local r = love.graphics.get
  local tf = love.math.newTransform()

  local size = 3

  tf:translate( w / 2, h / 2)
  tf:scale( d / size, -d / size )
  transform = tf
end

OnImpact = function( impact )
  if not impact then return end
  local score = BeatScore( impact.t )
  --DEBUG
  state.lastBeatScore = score
  state.lastCollisionTick = state.tick
  local pass = false

  if score > 1.0 then

    pass = true

    state.currentStreak = state.currentStreak + 1
    if state.currentStreak > state.longestStreak then
      state.longestStreak = state.currentStreak
    end

    state.currentBeat = state.currentBeat + 1
    if state.currentBeat >= 119 then
      return OnVictory()
    end

  else
    state.currentStreak = 0
  end


  local x, y = math.cos(impact.th), math.sin(impact.th)
  particles:setPosition( impact.r * x, impact.r * y )
  particles:setEmissionArea( "normal", 0.1, 0.2, impact.th, true )
  particles:emit( 50 * score * score )

  audio.OnImpact( impact, score, pass, state.currentBeat )
  marble.OnImpact( impact, state.currentBeat )
  wave.OnImpact( impact, state.currentBeat )
end
local _OnImpact = OnImpact

local function NewGame( demoName )
  love.graphics.setBackgroundColor( 245 / 255, 169 / 255, 184 / 255 ) --Trans pink.
  OnImpact = _OnImpact
  ExtrapolateBeatScore = _ExtrapolateBeatScore
  love.draw = Draw
  love.update = Update
  
  do --particle shit
  particles:reset()
  particles:setSizes( 0.0007, 0.0001, 0.0003 )
      --particles:setSizes( 0.0007, 0.0001, 0.0003 )
    particles:setSizeVariation( 1 )
    particles:setRadialAcceleration( 0, 0.5 )
    particles:setSpeed( 0.2, 1 )
    particles:setLinearDamping( 1, 10 )
    particles:setRelativeRotation( true )
    particles:setEmitterLifetime( -1 )
    particles:setParticleLifetime( 0, 5 )
    particles:setSpread( 0.05 )
    particles:setColors(
      1, 1, 1, 1,
      91 / 255, 206 / 255, 250 / 255, 1,
      245 / 255, 169 / 255, 184 / 255, 1,
      1,1,1,0,
      245 / 255, 169 / 255, 184 / 255, 1,
      1,1,1,0,
      245 / 255, 169 / 255, 184 / 255, 1,
      1,1,1,0)
  end
  state.Reset()
  marble.Reset()
  wave.Reset()
  text.Reset()
  audio.Reset()
  recorder.Reset()

  state.isDemo = demoName
  if demoName then
    recorder.Load( demoName )
  end
end

function love.load()
  UpdateWindowTransform( love.graphics.getDimensions() )


  do--particle system setup
    particles = love.graphics.newParticleSystem(
      love.graphics.newImage( "prideflag.png" ),
      2048)



    
  end


  sitelenpona = assert( require "sitelenpona" )
  text = assert( require "text" )
  marble = assert( require "marble" )
  wave = assert( require "wave" )
  DetectCollision = assert ( require "collision" )
  audio = assert( require "audio" )
  recorder = assert( require "recorder" )
  return NewGame()

end

ExtrapolateBeatScore = function( )
  local t = state.tick / 120.0
  local beat = state.beat
  if not beat.t then return 2.0 end
  if not beat.mu then return 2.0 end
  if beat.mu < 0.001 then return 2.0 end

  local pow = 1 / math.max( beat.mu, 0.2 )
  t = 1.05 * math.sin( 0.5 * math.pi * ( t - beat.t ) / beat.mu )
  return math.pow( t * t, pow )
end
_ExtrapolateBeatScore = ExtrapolateBeatScore

BeatScore = function( t )
  local beat = state.beat

  --Base case 1: first tap.
  if not beat.t then
    beat.t = t
    return 2.0
  end

  local dt = t - beat.t
  beat.t = t

  --Base case 2: second tap.
  if not beat.mu then
    beat.mu = dt
    return 2.0
  end

  local pow = 1 / math.max( beat.mu, 0.1 )
  local score = 1.1 * math.sin( 0.5 * math.pi * dt / beat.mu )
  score = math.pow( score * score, pow )

  --General case: update average beat length.

  --Debounce: max BPM 200
  if dt > 60.0 / 200.0 then

    local WEIGHT = 0.75 --High number makes the last beat more significant.
    local mu = ( 1.0 - WEIGHT ) * beat.mu + WEIGHT * dt
    beat.mu = mu

  else
    score = 0
  end

  return score
end

OnVictory = function()

  if state.isDemo then
    state.isDemo = false
  else
    recorder.Save()
  end

  particles:setParticleLifetime( 0, 30 )
  particles:setSizes( 0.001, 0.0001, 0.0005 )
  particles:setColors( 
    245 / 255, 169 / 255, 184 / 255, 1,
    1, 1, 1, 1,
    245 / 255, 169 / 255, 184 / 255, 1,
    1, 1, 1, 0, 
    245 / 255, 169 / 255, 184 / 255, 1,
    1, 1, 1, 0
  )
  particles:setPosition( 0, 0 )
  particles:setEmissionArea( "normal", 0.5, 0.5, 0, true )
  particles:emit( 500 )
  particles:setEmissionArea( "normal", 0.01, 0.01, 0, true )

  love.graphics.setCanvas( marble.Canvas() )
  love.graphics.setCanvas() 

  local totalTime = state.tick / 120.0
  OnImpact = function() end
  love.update = function( dt )
    while dt > step do
      marble.Integrate( step )
      wave.Integrate( step )
      marble.Update()
      wave.Update()

      dt = dt - step
    end

    local marblePos = marble.Current()
    particles:emit( 1 )
    particles:update( dt )
    particles:moveTo( marblePos.x, marblePos.y )
  end
  love.draw = function()
    love.graphics.push( "transform" )
    love.graphics.applyTransform( transform )
    love.graphics.setColor( 1, 1, 1, 1 )
    love.graphics.draw( particles )
    love.graphics.pop()



    text.Draw( 119 )

    local score = state.longestStreak * state.longestStreak / totalTime

    love.graphics.setColor( 1, 1, 1, 1 )
    love.graphics.printf( 
      string.format( "time:\t%.2f\nstreak:\t%d\nscore:%.2f", totalTime, state.longestStreak, score):gsub( "%.", "," ),
      0, 0.5 * love.graphics.getHeight() - 2.0* love.graphics.getFont():getHeight(),
      love.graphics.getWidth(),
      "center"
    )

    marble.Draw()
  end
  marble.OnVictory()
  love.graphics.setBackgroundColor( 91 / 255, 206 / 255, 250 / 255 ) --Trans blue.
end

Draw = function()

  local score = ExtrapolateBeatScore()


  love.graphics.push( "transform" )

  love.graphics.applyTransform( transform )
  love.graphics.setColor( 1.0, 1.0, 1.0, 1.0 )
  love.graphics.draw(particles, 0, 0)
  wave.Draw( score )

  love.graphics.pop()


  sitelenpona.Draw( text.words[state.currentBeat] )
  marble.Draw()
  text.Draw( state.currentBeat )
  
  love.graphics.print( state.tick, 400, 400, 0, 1, 1 )
  love.graphics.print( state.currentStreak, 400, 450, 0, 1, 1 )
  love.graphics.print( state.lastCollisionTick, 400, 500, 0, 1, 1 )
end




Update = function( dt )

  audio.Update( ExtrapolateBeatScore(), state.currentBeat )
  particles:update( dt )
  dt = dt + state.timeToSimulate

  --Physics tick.
  while dt > step do
    
    state.tick = state.tick + 1

    if state.isDemo then
      local ddx, ddy = recorder.NextTick()
      if ddx then marble.SetAcceleration( ddx, ddy )
      else state.isDemo = false end
    else
      recorder.Update( marble.GetAcceleration() ) --For savegames.
    end

    marble.Integrate( step )
    wave.Integrate( step )

    OnImpact(
      DetectCollision( 
        marble.Current(), marble.Next(),
        wave.Current(), wave.Next() ))

    marble.Update()
    wave.Update()

    dt = dt - step
  end
  state.timeToSimulate = dt
end
_Update = Update

local function LoadGame() -- Load game screen.
  local loadGame = assert( dofile "loadgame.lua" )
  love.mousepressed = function( x, y, button, istouch, presses)
    local demoName = loadGame( x, y, button, istouch, presses )
    if demoName then return NewGame( demoName ) end
  end
end

function love.keypressed( key, code, isRepeat )
  if key == "escape" then return love.event.quit() end
  if key == "space" then return NewGame() end
  if code == "return" then return LoadGame() end --Play demo.
  if state.isDemo then return end
  return marble.OnKey( state.tick )
end

function love.keyreleased( key, code )
  return marble.OnKey( state.tick )
end

function love.resize( w, h )
  UpdateWindowTransform( w, h )
  if marble then marble.Resize() end
end

function love.mousepressed( x, y, button, istouch, presses )
  return NewGame()
end