local folder = "scenes.subgame.sonic-boost.actors."

local Entity        = require(folder .. "parent")
local phys          = require "datas.subgame.sonic-boost.physics"
local playerHeight  = 32
local grindheight   = 32

local Character     = Entity:extend()

-- Un "personnage" est une entité unique, dans le sens où elle possède un controller
-- special, qui fait le lien entre elle et les différents éléments du niveau

-- Le code des personnages normaux et rivaux est exactement le même, les différence
-- sont géré par les controlleurs, qui s'occupent de leur envoyer les données
-- de touche, et d'effecter les réactions logique en cas de disparition de l'entité
-- (terminer le niveau pour un joueur, juste le détruire pour un rival)

-- Le personnage ne sait pas à l'intérieur de lui même s'il est contrôler par une
-- IA ou par un vrai joueur.

-- Il n'a pas non plus accès aux détails de la mission, qui sont aussi géré par
-- son controller special (vu par "self.charcontroller")

function Character:new(charcontroller, rail, character, id)
  self.charcontroller = charcontroller
  local world         = self.charcontroller.controller.world


  local rail = rail or 2
  local y = 10 + (rail*20)
  Character.super.new(self, world, "character", 16-8, y-8, 0, 16, 16, playerHeight)
  self.name = "player"
  self.charid = id or 1

  self.yspeed = 0
  self.xspeed = 0
  self.zspeed = 0

  self.ymove            = false
  self.onGround         = true
  self.groundBellow     = true
  self.groundTerrain    = 0
  self.isJumping        = false
  self.isInAerialAction = false
  self.canAerialAction  = true
  self.isInAction       = false
  self.canAction        = true

  self.dash             = false
  self.dashCam          = false

  self:setDebugColor(0, 255, 0)

  self:characterInit(character)

  self:setSprite("character" .. self.charid, 32, 48, true)

  self.depth = -1

  self.rings = 0
  self.score = 0
  self.combo = 0
  self.bonus = 0
  self.turn  = 1

  self.life  = 3

  self.startx   = self.x
  self.starty   = self.y

  self.view     = {}
  self.view.x   = self.x
  self.view.y   = self.y
  self.view.dx  = 0

  self.relx       = 0
  self.relxspeed  = 0

  self.grind = false

  self.ai = isAIControlled or false
end

function Character:characterInit(char)
  self.data = game.subgame.sonicboost:getCharacterData(char)

  self.lifeicon = self.data.assets.lifeicon
end

function Character:update(dt)
  self:updateTimers(dt)

  -- On initialise les variables de directions
  local dx, dy, dz = 0, 0, 0

  -- On commence par récupérer les inputs fait sur le pad virtuel, qui est géré
  -- dans core.input.keys. On le garde dans une variable qui sera utilisée dans
  -- tout l'objet joueur

  self.groundTerrain, self.groundBellow = self:getTerrain()

  self.keys = self.charcontroller:getKeys()

  self:sideSteps()
  self:jumpAction()
  self:jump()
  self:normalAction()

  self:applyVerticalVelocity(dt)

  self:autoMove(dt)
  self:snaptoRails(dt)

  self:move()
end

-- ACTIONS FUNCTIONS --

function Character:sideSteps()
  if self.grind == true then
    if (self.keys["up"].isPressed) then
      self.yspeed     = - 1 * 60 * phys.side
      self.ymove      = true
    elseif (self.keys["down"].isPressed) then
      self.yspeed     = 60 * phys.side
      self.ymove      = true
    else
      self.ymove = false
    end
  else
    if (self.keys["up"].isDown) then
      self.yspeed = - 1 * 60 * phys.side
      self.ymove  = true
    elseif (self.keys["down"].isDown) then
      self.yspeed = 60 * phys.side
      self.ymove  = true
    else
      self.ymove = false
    end
  end
end

function Character:jump()
  if (self.keys["A"].isPressed) and (self.onGround) then
    self.zspeed     = 60 * self.data.stats.jmp
    self.onGround   = false
    self.isJumping  = true
    self.isInAction = false
  end
end

function Character:jumpAction()
  if self.data.stats.jumpaction == "doublejump" then
    if (self.keys["A"].isPressed) and (self.isJumping) and (self.canAerialAction) then
      self.zspeed           = 60 * self.data.stats.jumpaction_power
      self.isInAerialAction = true
      self.canAerialAction  = false
    end
  elseif self.data.stats.jumpaction == "jumpdash" then
    if (self.keys["A"].isPressed) and (self.isJumping) and (self.canAerialAction) then
      self.zspeed           = 60 * 1
      self.xspeed           = math.max(self.xspeed, self.data.stats.spd * 60 * 1.2)
      self.isInAerialAction = true
      self.canAerialAction  = false
      self.dashCam          = true
      self:addTimer("dashCam", .5)
    end
  end
end

function Character:normalAction()
  if self.data.stats.action == "spinattack" then
    if (self.keys["B"].isPressed) and (self.onGround) and (self.canAction) and (self.grind == false) then
      self.xspeed     = math.max(self.xspeed, self.data.stats.spd * 60 * 1.5)
      self.isInAction = true
      self.canAction  = false
      self.dashCam    = true
      self:addTimer("action", .5)
      self:addTimer("action_cooldown", 1)
      self:addTimer("dashCam", .5)
    end
  end
end

-- Moving functions --

function Character:move()
    local cols, coll_number

    self:setFilter()

    self:updateRelativePosition()

    self.x, self.y, self.z, cols, coll_number = Character.super.move(self)

    for i=1, coll_number do
      local col = cols[i]
      if col.other.type == "collectible" then
        col.other:pickedUp(self)
      end
      self.collisionResponse(cols[i])
    end

end

function Character:updateRelativePosition()
  local MAXVIEW =  80
  local MINVIEW = -20
  if not ((self.onGround == false) and (self.z < 0)) then
    if (self.dashCam) or (self.dash) then
      self.relx = math.min(self.relx + 0.4, MAXVIEW)
    else
      if self.keys["left"].isDown then
        if self.relx > 0 then
          self.relx = math.max(self.relx - 0.3, 0)
        elseif self.relx <= 0 then
          self.relx = math.max(self.relx - 0.1, MINVIEW)
        end
      elseif self.keys["right"].isDown then
        if self.relx >= 0 then
          if self.relx <= MAXVIEW*3/4 then
            self.relx = math.min(self.relx + 0.2, MAXVIEW*3/4)
          else
            self.relx = math.max(self.relx - 0.1, MAXVIEW*3/4)
          end
        elseif self.relx < 0 then
          self.relx = math.min(self.relx + 0.3, 0)
        end
      else
        if self.relx > 0 then
          self.relx = math.max(self.relx - 0.3, 0)
        elseif self.relx < 0 then
          self.relx = math.min(self.relx + 0.3, 0)
        end
      end
    end
  end

  self.x = self.view.x + self.relx
end

function Character:getViewCenter()
  local x, y, z
  x = math.floor(self.view.x + self.w / 2)
  y = math.floor(self.y + self.h / 2)
  z = math.floor(self.z + self.d / 2)

  return x, y, z
end

function Character:collisionResponse(other)
  if other == nil then return 0 end
  if other.type == "collectible" then
    other:pickedUp()
  end
end

function Character:setFilter()
  self.filter = function(item, other)
    if other.type == "collectible" then return "cross"
    else return "cross" end
  end
end

function Character:autoMove(dt)
  local max = self.data.stats.spd
  self.lockspeed  = false




  acc = phys.acc

  if (self.groundTerrain == 1) and (self.z <= 0) then
    self.dash       = true
    self:addTimer("dash", 2)
    acc             = phys.acc
  end

  if (self.groundTerrain == 2) and (self.z <= 0) then
    max             = phys.max * 1/3
    self.dash       = false
    self.acc        = acc*2
  end

  if (self.groundTerrain == 4) and (self.z <= 0) then
    self.dash       = true
    self:addTimer("dash", 2)
    self.zspeed     = 60 * (phys.spring * 4/5)
    self.onGround   = false
    self.isJumping  = true
    self.assets.sfx["jumpboard"]:play()
  end

  if (self.groundTerrain == 3) and (self.z <= 0) then
    max             = 0
    self.dash       = false
    self.acc        = acc*3
    self.xspeed     = self.xspeed * 3/4
    --self.lockspeed  = true
  end

  if (self.lockspeed == false) then
    if self.dash == true then
      self.xspeed = max * 60 * phys.boost
    elseif self.grind == true then
      self.xspeed = max * 60
    else
      if self.xspeed < (max*60) then
        self.xspeed = self.xspeed + acc
      elseif (self.xspeed - acc*2) > (phys.max*60) then
        self.xspeed = self.xspeed - acc*2
      else
        self.xspeed = (max*60)
      end
    end

    self.view.x = self.view.x + self.xspeed * dt
  end

  if self.x >= self.controller.world.width then
    self:overflowLevel()
  end
end


function Character:applyVerticalVelocity(dt)
  if (self.groundBellow == false) and (self.onGround == true) then
    self.onGround = false
  end

  local grindBellow = self.world:getGrindAtPoint(self.x, self.y, self.turn)

  if (grindBellow == false) and (self.onGround == true) and self.z > 0 then
    self.onGround = false
    self.grind    = false
  end

  if (self.onGround == false) then
    self.zspeed = self.zspeed - phys.grv

    if self.zspeed < 0 then
      if (self.z + self.zspeed*dt <= 0) and (self.groundBellow == true) then
        self.z          = 0
        self.onGround   = true
        self.isJumping  = false
        self.grind      = false -- pas vraiment nécessaire de retirer le grind ici
        self.isInAerialAction = false
        self.canAerialAction  = true
      end

      if (self.z + self.zspeed*dt <= grindheight) and (self.z + self.zspeed*dt > grindheight - 8) and (grindBellow == true) then
        self.z          = grindheight
        self.onGround   = true
        self.isJumping  = false
        self.grind      = true
        self.isInAerialAction = false
        self.canAerialAction  = true
      end


      if (self.groundBellow == false) and (self.z < -64) then
        self:die()
      end
    end
    self.z = self.z + self.zspeed*dt
  end

  if self.ground == true then
    self.zspeed = 0
  end
end

function Character:snaptoRails(dt)
  local centery = self.y + 8
  local yrail = math.floor((centery - 10) / 20)
  local newyrail = math.floor((centery + self.yspeed*dt - 10) / 20)

  if (newyrail < 0) and (self.yspeed < 0) then
    centery     = 10
    self.yspeed = 0
  elseif (newyrail >= 4) and (self.yspeed > 0) then
    centery     = 90
    self.yspeed = 0
  else
    if (self.ymove == false) and (yrail ~= newyrail) then
      if self.yspeed > 0 then
        centery = newyrail * 20 + 10
      end
      if self.yspeed < 0 then
        centery = yrail * 20 + 10
      end
      self.yspeed = 0
    else
      centery = centery + self.yspeed*dt
    end
  end

  self.y = centery - 8
end

---------- INTERACTION FUNCTIONS ---------

function Character:getTerrain()
  local terrain, groundbellow
  terrain = self.world:getTerrainAtPoint(self.x, self.y, self.turn)

  if (terrain == 3) then
    groundbellow = false
  else
    groundbellow = true
  end

  return terrain, groundbellow
end

function Character:addScore(score)
  self.combo = self.combo + 1

  self.bonus = math.floor(self.combo / 5) * 2

  self.score = self.score + score + math.floor(1/10 * score * self.bonus)

  self:addTimer("combo", 3)
end

---------- DRAWING FUNCTIONS ---------

function Character:animations()
  self.assets.sprites["character" .. self.charid]:setCustomSpeed(math.abs(self.xspeed / 16))

  if self.onGround then
    if self.grind == true then
      self:setAnimation("grind")
    elseif self.isInAction == true then
      self:setAnimation("action")
    else
      if self.xspeed == 0 then
        self:setAnimation("idle")
      elseif self.xspeed < phys.max*60 then
        self:setAnimation("run2")
      elseif self.xspeed >= phys.max*60 then
        self:setAnimation("dash")
      end
    end
  else
    if self.isJumping then
      self:setAnimation("jump")
    else
      self:setAnimation("fall")
    end
  end
end

function Character:setAnimation(animation)
  if (self.animation ~= animation) then
    self.animation = animation
    self.assets.sprites["character" .. self.charid]:changeAnimation(animation, true)
  end
end

function Character:draw()
  self:animations()
  if (self.z < 0) and (self.onGround == false) then
    z = self.z
    if self.z < -48 then z = -48 end
    love.graphics.setScissor(0, 0, 424, self.y + 100)
  end
  self:drawSprite()
  love.graphics.setScissor()
end

function Character:drawEcho()
  self:animations()
  if (self.z < 0) and (self.onGround == false) then
    z = self.z
    if self.z < -48 then z = -48 end
    love.graphics.setScissor(0, 0, 424, self.y + 100)
  end
  self:drawSprite(self.world.width, 0)
  love.graphics.setScissor()
end

function Character:drawShadow(tx, ty)
  if (self.z < 0) and (self.onGround == false) then
    return 0
  end
  Character.super.drawShadow(self, tx, ty)
end

function Character:drawShadowEcho()
  if (self.z < 0) and (self.onGround == false) then
    return 0
  end
  Character.super.drawShadow(self, self.world.width, 0)
end

---------- LEVEL FINISH FUNCTIONS ----------

function Character:overflowLevel()
  self.charcontroller:newLap()
end

function Character:finishLevel()
  --love.event.quit()
end

function Character:die()
  if self.life == 0 then
    self.charcontroller:die()
  else
    self.life     = self.life - 1
    self.view.x   = self.startx
    self.relx     = 0
    self.dash     = false
    self.dashCam  = false
    self.y        = self.starty
    self.z        = 0
    self.rings    = 0
  end
end

----- TIMERS -----

function Character:endedTimer(name)
  if name == "dash" then
    self.dash       = false
  elseif name == "combo" then
    self.combo = 0
    self.bonus = 0
  elseif name == "action" then
    self.isInAction = false
  elseif name == "dashCam" then
    self.dashCam    = false
  elseif name == "action_cooldown" then
    self.canAction = true
  end
end

return Character
