local Battler = require("scenes.battlesystem.actors.battler")
local Hero = Battler:extend()

local gui         = require "game.modules.gui"

-- INIT FUNCTIONS
-- Initialize the hero

function Hero:new(world, x, y, charid, charnumber)
  Hero.super.new(self, world, x, y, 0)
  self.isHero     = true

  self:initMovementSystem()

  self:initCharacter(charid)

  self:initSprite()
  self:initChoregraphySystem()

  self:initVoices()
end

-- CHARACTER FUNCTIONS
-- All functions related to character handling

function Hero:initCharacter(charid)
  if charid == nil then
    core.debug:error("FATAL ERROR: charid not set")
  end
  self.charid     = charid
  self.charnumber = charnumber or 1

  self.hp = game.characters.list[self.charid].stats.hp
  self.pp = game.characters.list[self.charid].stats.pp

  self.actionPerTurn = game.characters.list[self.charid].turns

  self.turnAction = nil
end

function Hero:getStats()
  return game.characters.list[self.charid].stats
end

function Hero:setHP(value, relative)
  if relative == true then
    value = game.characters.list[self.charid].stats.hp + value
  end

  game.characters.list[self.charid].stats.hp = value
  self.tweens:newTween(0, 0.3, {hp = game.characters.list[self.charid].stats.hp}, 'linear')
end

function Hero:setPP(value, relative)
  if relative == true then
    value = game.characters.list[self.charid].stats.pp + value
  end

  game.characters.list[self.charid].stats.pp = value
  self.tweens:newTween(0, 0.3, {pp = game.characters.list[self.charid].stats.pp}, 'linear')
end


-- ACTIVITY FUNCTION
-- Function to set or unset activity to the character

function Hero:setActive()
  core.debug:print("cbs/hero", "hero " .. self.charid .. " is now active")
  local gridsize = game.characters.list[self.charid].move
  if (gridsize == nil) then
    gridsize = 3
    core.debug:warning("cbs/character", "move value is nil")
  end
  self.world.cursor:setGrid(self.x, self.y, "circle", gridsize, 1, self)
  self.startx, self.starty = self.x, self.y
  self.world.cursor:set(self.startx, self.starty, "cursorMove")

  self:talk("turnstart")

  self.directionPrevious = self.direction
end

-- UPDATE FUNCTION
-- Update the hero

function Hero:update(dt)
  Hero.super.update(self, dt)
  -- Get keys to have some keyboard functions
  self.keys = self.scene:getKeys(1)

  -- Calculate speed to calculate animation speed
  self:updateSpeed(dt)

  if (self.isChoregraphyActive) then
    self:updateChoregraphy()
  end

  if (self.scene:haveMenus()) then
    self:changeDirection(dt)
  end

  self.xprevious = self.x
  self.yprevious = self.y
end

-- MOVE FUNCTIONS
-- All functions handling the moving

local MOVEMENT_DURATION = 0.20

function Hero:initMovementSystem()
  self.startx, self.starty        = self.x, self.y
  self.xprevious, self.yprevious  = self.x, self.y
  self.direction = 1
  self.directionPrevious = 1

  self:initJump()
end

function Hero:getMovementDuration(dx, dy, duration)
  local duration = duration or MOVEMENT_DURATION
  local coef = 0.8
  local dx, dy = dx, dy
  local distance = utils.math.pointDistance(self.x, self.y, dx, dy) * coef
  return duration * distance
end

function Hero:goTo(dx, dy, timerName, duration)
  local duration = self:getMovementDuration(dx, dy, duration)
  if duration > 0 then
    self.tweens:newTween(0, duration, {x = dx, y = dy}, 'inOutQuad')
  end
  self.tweens:newTimer(duration + 0.02, timerName)
end

function Hero:jumpTo(dx, dy, size, timerName, spinjump, duration)
  local duration = self:getMovementDuration(dx, dy, duration)
  self.tweens:newTween(0, duration, {x = dx, y = dy}, 'inOutQuad')
  self.tweens:newTimer(duration + 0.02, timerName)
  self:setJump(size, spinjump, duration)
end

function Hero:updateSpeed(dt)
  self:applyMotion(dt)
  self.xspeed = self.x - self.xprevious
  self.yspeed = self.y - self.yprevious

  self.gspeed = math.sqrt(self.xspeed^2 + self.yspeed^2)

  -- Handle direction
  if math.abs(self.xspeed) > 0 then
    self.direction = utils.math.sign(self.xspeed)
  end

  self:setCustomSpeed(self.gspeed * 320)
end

function Hero:changeDirection(dt)
  -- Change direction by pressing left or right when selecting the next action
  if (self.keys["left"].isPressed) then
    self.direction = -1
  elseif (self.keys["right"].isPressed) then
    self.direction = 1
  end
end

function Hero:initJump()
  self.jump = {}
  self.jump.spin  = false
end

function Hero:setJump(size, spinjump, duration)
  local tweenDuration = duration / 2
  self.tweens:newTween(0, tweenDuration, {z = size}, 'outQuad')
  self.tweens:newTween(tweenDuration, tweenDuration, {z = 0}, 'inQuad')
end

function Hero:setMotionX(direction, speed)
  self.motion = speed
  self.motionDirection = direction
end

function Hero:applyMotion(dt)
  if self.motion ~= 0 and self.motion ~= nil then
    local dx = self.x + self.motion * self.motionDirection * dt
    -- {1, 0, "line", 5, true}
    local ox        = self.choregraphy.startx + (self.choregraphy.effectArea[1] * self.choregraphy.direction)
    local oy        = self.choregraphy.starty + self.choregraphy.effectArea[2]
    local shape     = self.choregraphy.effectArea[3]
    local size      = self.choregraphy.effectArea[4]
    local direction = self.choregraphy.direction

    local new_case_x = utils.math.round(dx)
    local new_case_y = utils.math.round(self.y)
    print(new_case_x, new_case_y, self.world:caseIsEmpty(new_case_x, new_case_y, self))
    if self.maputils.isInMask(dx, self.y, ox, oy, shape, size, direction) and self.world:caseIsEmpty(new_case_x, new_case_y, self) then
      self.x = dx
    else
      self.x = dx
      self.motion = 0
      if (self.choregraphy.blockedBy == 'action_dashForward') then
        self.choregraphy.changeAction = true
        self.direction = self.choregraphy.direction
      end
    end
  end
end

-- SIGNAL FUNCTIONS
-- All functions related to signal receiving

function Hero:receiveSignal(action_type, id)
  if id == nil then
    core.debug:print("battler/hero", "action selected : " .. action_type)
  else
    core.debug:print("battler/hero", "action selected : " .. action_type .. " (" .. id .. ")")
  end
  if (action_type == "defend") then
    self.turnAction = "defend"
    self:switchActiveBattler( )
  elseif (action_type == "attack") then
    --self:changeAnimation("hit1")
    self:attack()
  elseif (action_type == "skill") then
    self.world.cursor:unset( )
    self:useSkill(id, self.world.cursor.x, self.world.cursor.y)
  elseif (action_type == "cursorMove") then
    self:changeAnimation("walk", true)
    self:goTo(self.world.cursor.x, self.world.cursor.y, 'cursorMove')
    self.world.cursor:unset( )
  else
    self:switchActiveBattler( )
  end
end

function Hero:receiveBackSignal()
  self.world.cursor:set(self.x, self.y, "cursorMove")
  self:goTo(self.startx, self.starty, 'backMove')

  self:changeAnimation("walk")
end

function Hero:timerResponse(timer)
  if timer == "switchActiveBattler" then
    self.world:switchActiveBattler()
  elseif timer == "wait" then
    self.choregraphy.changeAction = true
  elseif timer == "cursorMove" then
    self:changeAnimation("idle")
    self.world:resetActiveGrid()
    self.scene.menu:set( self )
  elseif timer == 'backMove' then
    self:changeAnimation("idle")
    self.direction = self.directionPrevious
  elseif timer == self.choregraphy.blockedBy and self.choregraphy.changeAction == false then
    self.choregraphy.changeAction = true
  end
end

-- ACTION FUNCTIONS
-- All functions related to actions

function Hero:switchActiveBattler()
  self.tweens:newTimer(0.15, "switchActiveBattler")
end

-- CHOREGRAPHY FUNCTIONS
-- All functions related to the choregraphy system

function Hero:initChoregraphySystem()
  self.choregraphy = {}
  self.choregraphy.current      = 0
  self.choregraphy.isFinished   = false
  self.choregraphy.changeAction = true
  self.choregraphy.data = {}
  self.choregraphy.effectArea   = nil
  self.choregraphy.dx           = self.x
  self.choregraphy.dy           = self.y
  self.choregraphy.blockedBy    = ""

  self.choregraphy.startx       = self.x
  self.choregraphy.starty       = self.y
  self.choregraphy.direction    = self.direction

  self.isChoregraphyActive      = false
end

function Hero:attack(id, dx, dy)
  local skill = game.skills:getSkillData("attack")
  self:startChoregraphy(skill, dx, dy)
end

function Hero:useSkill(id, dx, dy)
  local skill = game.skills:getSkillData(id)
  self:setPP(skill.cost * -1, true)
  self:startChoregraphy(skill, dx, dy)
end

function Hero:startChoregraphy(skill, dx, dy)
  local skill = skill
  self.choregraphy.current      = 0
  self.choregraphy.isFinished   = false
  self.choregraphy.changeAction = true
  self.choregraphy.data         = skill.choregraphy
  self.choregraphy.effectArea   = skill.effectArea

  self.choregraphy.startx       = self.x
  self.choregraphy.starty       = self.y
  self.choregraphy.direction    = self.direction

  self.choregraphy.dx           = dx or self.x
  self.choregraphy.dy           = dy or self.y

  self.choregraphy.haveSentDamage = false

  self.isChoregraphyActive      = true
end

function Hero:updateChoregraphy(dt)
  if (self.choregraphy.changeAction) then
    self:switchAction()
  end
end

function Hero:switchAction()
  self.choregraphy.current = self.choregraphy.current + 1
  local nextAction = self.choregraphy.data[self.choregraphy.current]
  if nextAction == nil then
    self.isChoregraphyActive = false
    self:switchActiveBattler()
  else
    local args = game.skills:getActionArguments(nextAction)
    local condition = args.condition
    if (condition == "sentDamage") and (not self.choregraphy.haveSentDamage) then
      core.debug:print("cbs/hero", "you didn't do damage, skipping " .. args.name)
      self:switchAction()
    else
      self:doChoregraphyAction(nextAction)
    end

  end
end

function Hero:doChoregraphyAction(choregraphyAction)
  local args = game.skills:getActionArguments(choregraphyAction)
  local type = args.type or "unknown"
  local effectArea = self.choregraphy.effectArea
  self.choregraphy.changeAction = true

  if type == "wait" then
    local duration = choregraphyAction[3] or 1
    self:wait(args.duration)
  elseif type == "setAnimation" then
    self:chorSetAnimation(args)
  elseif type == "sendDamage" then
    self:sendDamageToArea(effectArea, args.power, args.accuracy, args.isSpecial, args.isAerial)
  elseif type == "sendDamageToPoint" then
    self:chorSendDamageToPoint(args)
  elseif type == "addGFX" then
    self:chorAddGFX(args)
  elseif type == "playSFX" then
    self.assets.sfx[args.sfx]:play()
  elseif type == "dashForward" then
    self:setMotionX(self.direction, args.speed)
    self:blockChoregraphy(args.blockProcess, "action_dashForward")
  elseif type == "jump" then
    self:chorJump(args)
  else
    core.debug:warning("cbs/hero", "unknown action type " .. type .. ' (' .. args.name .. ')')
  end
end

function Hero:chorSetAnimation(args)
  self:changeAnimation(args.animation)
  self:blockChoregraphy(args.blockProcess, args.animation)
end

function Hero:chorAddGFX(args)
  local dx        = args.dx
  if (args.affectedByDirection) then
    dx = dx * self.direction
  end
  local x         = self.x
  local y         = self.y
  local z         = 0

  self.world.obj.GFX(self.world, x + dx, y + args.dy, z, args.sprite, self, args.blockProcess)
end

function Hero:chorSendDamageToPoint(args)
  local xx, yy
  if args.name == "sendDamageFromCursor" then
    xx = self.choregraphy.dx
    yy = self.choregraphy.dy
  elseif args.name == "sendDamageFromPos" then
    xx = utils.math.round(self.x)
    yy = utils.math.round(self.y)
  end

  local dx = args.dx
  if (args.affectedByDirection) then
    dx = dx * self.choregraphy.direction
  end
  xx = xx + dx
  yy = yy + args.dy
  self.choregraphy.haveSentDamage = self:sendDamage(xx, yy, args.power, args.accuracy, args.isSpecial, args.isAerial)
end

function Hero:chorJump(args)
  local xx, yy
  if args.name == "jumpBack" then
    xx, yy = self.choregraphy.startx, self.choregraphy.starty
  elseif args.name == "jumpToCursor" then
    xx, yy = self.choregraphy.dx, self.choregraphy.dy
  end

  self:jumpTo(xx, yy, 32, "action_jumpBack", false)
  self:blockChoregraphy(args.blockProcess, "action_jumpBack")
end

function Hero:blockChoregraphy(isBlocked, blockedBy)
  if (isBlocked) then
    self.choregraphy.blockedBy = blockedBy
    self.choregraphy.changeAction = false
  end
end

function Hero:sendDamageToArea(effectArea, power, accuracy, isSpecial, isAerial)
  local dx = effectArea[1]
  if effectArea[5] then
    dx = dx * self.choregraphy.direction
  end
  local ox = self.choregraphy.startx + dx
  local oy = self.choregraphy.starty + effectArea[2]
  local grid = self.maputils.maskToMap(ox, oy, effectArea[3], effectArea[4], self.choregraphy.direction)

  local test = false

  for i, line in ipairs(grid) do
    for j, case in ipairs(line) do
      if grid[i][j] == 1 then
        test = self:sendDamage(j, i, power, accuracy, isSpecial, isAerial)
      end
    end
  end

  self.choregraphy.haveSentDamage = test
end

function Hero:wait(time)
  self.tweens:newTimer(time, "wait")
  self.choregraphy.changeAction = false
end

-- ASSETS FUNCTIONS
-- Load and play assets needed by the character

function Hero:initSprite()
  self.assets:addSprite(self.charid, "datas/gamedata/characters/" .. self.charid .. "/sprites")
  self.assets.sprites[self.charid]:setCustomSpeed(16)
  self:setSprite(self.charid, 32, 48, true)
  self:cloneSprite()
  self:changeAnimation("idle")
end

function Hero:animationEnded(animation)
  if (animation == self.choregraphy.blockedBy) then
    self.choregraphy.blockedBy    = ""
    self.choregraphy.changeAction = true
  end
end

function Hero:initVoices()
  self:addVoiceEffect("move")
  self:addVoiceEffect("turnstart")
end

function Hero:addVoiceEffect(name)
  local completename = self.charid .. "_" .. name
  local path = "datas/gamedata/characters/" .. self.charid .. "/voices/" .. name .. ".wav"
  self.assets:newSFX(completename, path)
end

function Hero:talk(name)
  local completename = self.charid .. "_" .. name
  self.assets.sfx[completename]:play()
end

-- DRAW FUNCTIONS
-- Draw everything related to the hero

function Hero:draw()
  self:drawSprite(0, -self.z)
end

function Hero:drawIcon(x, y)
  local iconID = 1
  self.assets.tileset["charicons"]:drawTile(iconID, x, y)
end

function Hero:drawHUD()
  local HUDBASE = 8
  local HUDSEP  = 152
  local x = HUDBASE + (self.charnumber-1)*HUDSEP
  local y = self.world:getPlayerHUDPosition()

  self.assets.images["e_speedster"]:draw(x, y)
  core.screen:setScissor(x, y-16, 32, 40)
  self.assets.sprites[self.charid]:drawAnimation(x+14, y+14)
  core.screen:resetScissor( )
  self.assets.images["m_speedster"]:draw(x, y)
  self.assets.images["statusbar"]:draw(x+12, y-6)

  local hpmax = game.characters.list[self.charid].stats.hpmax
  local ppmax = game.characters.list[self.charid].stats.ppmax

  local bar1 = math.floor((self.hp/hpmax)*107)
  local bar2 = math.floor((self.pp/ppmax)*108)

  love.graphics.setColor(248/255, 160/255, 0, 1)
  gui.drawBar(x+29, y+5, bar1, 7)
  love.graphics.setColor(0, 248/255, 248/255, 1)
  gui.drawBar(x+17, y+17, bar2, 7)
  utils.graphics.resetColor()

  self.assets.fonts["hudnbrs_small"]:set()
  love.graphics.print(math.floor(self.hp) .. "/" .. hpmax, x+34, y+5)
  love.graphics.print(math.floor(self.pp) .. "/" .. ppmax, x+28, y+17)

  local lvl = game.characters.list[self.charid].stats.level

  if lvl < 100 then
    lvl = "0" .. lvl
  end

  love.graphics.print(lvl, x+122, y-5)
end

return Hero
