local consts = require 'constants'
local tile = require 'tile'

local Entity = {}
Entity.__index = Entity

function Entity:new(state, x, y, name)
   local file = io.open('entities/' .. name .. '.lua')
   local char_data = load('return' .. file:read('*all'))()

   local sprites = {}
   for name, sprite in pairs(char_data.sprites) do
      sprites[name] = tile.getTile(sprite)
   end

   local t = {
      state = state,
      x = x,
      y = y,
      dx = 0,
      dy = 0,
      sprites = sprites,
      direction = {0, 1},
      facing = 'down',
   }

   return setmetatable(t, Entity)
end

function Entity:checkCollision(dx, dy)
   local bx = math.floor((self.x + dx) / consts.tileSize)
   local by = math.floor((self.y + dy) / consts.tileSize)
   local alignX = ((self.x + dx) % consts.tileSize) == 0
   local alignY = ((self.y + dy) % consts.tileSize) == 0

   local a = self.state.board:passable(bx, by)
   local b = self.state.board:passable(bx + 1, by)
   local c = self.state.board:passable(bx, by + 1)
   local d = self.state.board:passable(bx + 1, by + 1)

   if alignX and alignY then
      return a
   elseif alignX then
      return a and c
   elseif alignY then
      return a and b
   else
      return a and b and c and d
   end

end

function Entity:goingOffside(dx, dy)
   local bx = math.floor((self.x + dx) / consts.tileSize)
   local by = math.floor((self.y + dy) / consts.tileSize)
   return self.state.board:offside(bx, by) or
      self.state.board:offside(bx + 1, by) or
      self.state.board:offside(bx, by + 1) or
      self.state.board:offside(bx, by + 1)
end

function Entity:getFocus(board)
   local gx, gy = self:gameCoords()

   local es = board:lookupEntity(gx + self.direction[1],
                                 gy + self.direction[2])
   if es then
      return es[1]
   end
end

function Entity:move(board)
   local movement = true

   local gX, gY = self:gameCoords()
   debug = string.format(' {x=%d, y=%d, gx=%d, gy=%d}\n',
                         self.x, self.y, gX, gY)

   if self.dx == 0 and self.dy == 0 then
      return
   end

   if self.dx > 0 then
      self.direction[1] = 1
      self.facing = 'right'
   elseif self.dx < 0 then
      self.direction[1] = -1
      self.facing = 'left'
   elseif self.dy ~= 0 then
      self.direction[1] = 0
   end

   if self.dy > 0 then
      self.direction[2] = 1
      self.facing = 'down'
   elseif self.dy < 0 then
      self.direction[2] = -1
      self.facing = 'up'
   elseif self.dx ~= 0 then
      self.direction[2] = 0
   end

   local off = self:goingOffside(self.dx, self.dy)
   if off and self.state.board[off] then
      print('loading areas/' .. self.state.board[off] ..  '.lua')
      self.state:loadMap(self.state.board[off])
      if off == 'south' then
         self.y = 0
      elseif off == 'north' then
         self.y = consts.tileSize * (consts.boardHeight - 1)
      elseif off == 'west' then
         self.x = consts.tileSize * (consts.boardWidth - 1)
      elseif off == 'east' then
         self.x = 0
      end
   end

   if self:checkCollision(self.dx, self.dy) then
      self.x = self.x + self.dx
      self.y = self.y + self.dy
   elseif self:checkCollision(self.dx, 0) then
      self.x = self.x + self.dx
   elseif self:checkCollision(0, self.dy) then
      self.y = self.y + self.dy
   else
      movement = false
   end

   if movement then
      if self.dx ~= 0 and self.dy ~= 0 then
         -- nothing

      elseif self.dy ~= 0 then
         local xm = self.x % consts.halfTile
         if 0 < xm and xm < consts.quarterTile and self:checkCollision(-1, 0) then
            self.x = self.x - 1
         elseif xm >= consts.quarterTile and self:checkCollision(1, 0) then
            self.x = self.x + 1
         end

      elseif self.dx ~= 0 then
         local ym = self.y % consts.halfTile
         if 0 < ym and ym < consts.quarterTile and self:checkCollision(0, -1) then
            self.y = self.y - 1
         elseif ym >= consts.quarterTile and self:checkCollision(0, 1) then
            self.y = self.y + 1
         end
      end
   end

   local nX, nY = self:gameCoords()
   if nX ~= gX or nY ~= gY then
      self.state.board:enterSquare(nX, nY, self.state)
   end
end

function Entity:draw(t)
   if self.dx == 0 and self.dy == 0 then
      return self.sprites[self.facing]:drawPx(self.x, self.y, t)
   else
      return self.sprites[self.facing .. 'Moving']:drawPx(self.x, self.y, t)
   end
end

function Entity:gameCoords()
   return math.floor((self.x + consts.halfTile) / consts.tileSize),
   math.floor((self.y + consts.halfTile) / consts.tileSize)
end

function Entity:gameTopCoords()
   return math.floor(self.x / consts.tileSize),
   math.floor(self.y / consts.tileSize)
end

return {
   Entity = Entity
}
