--appmain must define these:
appname="test1"
appversion=0.1

function appload(playerName)
  love.graphics.setMode( 800, 600 )
  startGame(playerName)
end

function appupdate(dt)
  tickGame(dt)
end

function appdraw()
  drawGame()
end
--------------------------------------

local playerList={}
local gameFont
local map = { --0=road 1=building
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0},
{0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0},
{0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0},
{0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0},
{0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0},
{0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0},
{0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
}
local gridSize=19
local blockSize=30
local worldSize=570
local moveSpeed=100
local playerSize = 20
local numRoles={0,0,0}
local maxEmergenciesPPPR=3
local emergencyList={}
local numEmergencies={0,0,0}
local spawnRandom=100
local actionRadius=playerSize*2
local actionRate = 25
local startTime = 5 * 60
local timeRemains = 0
local totalScore = 0

function startGame(playerName)
  assert(playerName)
  gameFont = love.graphics.newFont(love._vera_ttf, 12)
  local x = 0
  local y = 0
  local done=false
  while done==false do
    local gx = math.random(gridSize) - 1
    local gy = math.random(gridSize) - 1
    if getGrid(gx,gy)== 0 then
      x,y = gridToPos(gx,gy)
      done=true
      break
    end
  end
  addPlayer(playerName,x,y,0)
  if getIsHost() == true then
    playerList[1][4] = getNextRole()
    timeRemains=startTime
  end
end

function posToGrid(posX,posY)
  local x = math.floor(posX / blockSize)
  local y = math.floor(posY / blockSize)
  return x,y
end

function gridToPos(gridX,gridY)
  local x = gridX * blockSize
  local y = gridY * blockSize
  return x,y
end

function getGrid(gridX,gridY)
  local x=gridX+1
  local y=gridY+1
  if x < 1 then x = 1 end
  if x > gridSize then x = gridSize end
  if y < 1 then y = 1 end
  if y > gridSize then y = gridSize end
  return map[x][y]
end

function tickGame(dt)
  if playerList[1] ~= nil and timeRemains > 0 then
    if love.keyboard.isDown("w") then
      if playerList[1][3] > 0 then
        local move = (moveSpeed * dt)
        local testPos = playerList[1][3] - move
        local gridX1,gridY1 = posToGrid(playerList[1][2],testPos)
        local gridX2,gridY2 = posToGrid(playerList[1][2]+playerSize,testPos+playerSize)
        if getGrid(gridX1,gridY1) == 0 and getGrid(gridX2,gridY2) == 0 and isCollide(playerList[1][2],testPos) == false then
          playerList[1][3] = testPos
        end
      end
    end
    if love.keyboard.isDown("s") then
      if playerList[1][3] < (worldSize-playerSize) then
        local move = (moveSpeed * dt)
        local testPos = playerList[1][3] + move
        local gridX1,gridY1 = posToGrid(playerList[1][2],testPos)
        local gridX2,gridY2 = posToGrid(playerList[1][2]+playerSize,testPos+playerSize)
        if getGrid(gridX1,gridY1) == 0 and getGrid(gridX2,gridY2) == 0 and isCollide(playerList[1][2],testPos) == false then
          playerList[1][3] = testPos
        end
      end
    end
    if love.keyboard.isDown("a") then
      if playerList[1][2] > 0 then
        local move = (moveSpeed * dt)
        local testPos = playerList[1][2] - move
        local gridX1,gridY1 = posToGrid(testPos,playerList[1][3])
        local gridX2,gridY2 = posToGrid(testPos+playerSize,playerList[1][3]+playerSize)
        if getGrid(gridX1,gridY1) == 0 and getGrid(gridX2,gridY2) == 0 and isCollide(testPos,playerList[1][3]) == false then
          playerList[1][2] = testPos
        end
      end
    end
    if love.keyboard.isDown("d") then
      if playerList[1][2] < (worldSize-playerSize) then
        local move = (moveSpeed * dt)
        local testPos = playerList[1][2] + move
        local gridX1,gridY1 = posToGrid(testPos,playerList[1][3])
        local gridX2,gridY2 = posToGrid(testPos+playerSize,playerList[1][3]+playerSize)
        if getGrid(gridX1,gridY1) == 0 and getGrid(gridX2,gridY2) == 0 and isCollide(testPos,playerList[1][3]) == false then
          playerList[1][2] = testPos
        end
      end
    end
    local msg = string.format("%s %d %d %d",playerList[1][1], playerList[1][2], playerList[1][3], playerList[1][4])
    clientPushMessage("UPDATEPLAYER",msg)
    if playerList[1] ~= nil and playerList[1][4] <= 0 then
      local msg = string.format("%s",playerList[1][1])
      clientPushMessage("GETROLE",msg)
    end
  end
  
  local msg = clientPopMessage()
  while msg ~= nil do
    if msg[1] == "UPDATEPLAYER" then
      local data = string.format("%s",msg[2])
      local name, params = data:match("^(%S*) (.*)")
      local xs,ys,roles = params:match("^(%-?[%d.e]*) (%-?[%d.e]*) (%-?[%d.e]*)")
      local x = tonumber(xs)
      local y = tonumber(ys)
      local role = tonumber(roles)
      local found = false
      for k,v in pairs(playerList) do
        if name == v[1] then
          found=true
          v[2]=x
          v[3]=y
          v[4]=role
        end
      end
      if found == false then
        addPlayer(name,x,y,role)
      end
    elseif msg[1] == "GETROLE" and getIsHost() == true then
      local data = string.format("%s",msg[2])
      local name, params = data:match("^(%S*)")
      for k,v in pairs(playerList) do
        if name == v[1] then
          local role = getNextRole()
          local msg = string.format("%s %d",v[1], role)
          clientPushMessage("SETROLE",msg)
          break
        end
      end
    elseif msg[1] == "SETROLE" then
      local data = string.format("%s",msg[2])
      local name, params = data:match("^(%S*) (.*)")
      local roles = params:match("^(%-?[%d.e]*)")
      local role = tonumber(roles)
      for k,v in pairs(playerList) do
        if name == v[1] then
          playerList[k][4]=role
          break
        end
      end
    elseif msg[1] == "UPDATEGAME" and getIsHost()==false then
      local data = string.format("%s",msg[2])
      local is,rs,xs,ys,ss = data:match("^(%-?[%d.e]*) (%-?[%d.e]*) (%-?[%d.e]*) (%-?[%d.e]*) (%-?[%d.e]*)")
      local i = tonumber(is)
      local r = tonumber(rs)
      local x = tonumber(xs)
      local y = tonumber(ys)
      local s = tonumber(ss)
      local listSize = 0
      for k,v in pairs(emergencyList) do
        listSize = listSize + 1
      end
      if i <= listSize then emergencyList[i]={r,x,y,s}
      else
        table.insert(emergencyList,i,{r,x,y,s})
      end
    elseif msg[1] == "UPDATETIME" and getIsHost()==false then
      local data = string.format("%s",msg[2])
      local is = data:match("^(%-?[%d.e]*)")
      timeRemains = tonumber(is)
    elseif msg[1] == "UPDATESCORE" and getIsHost()==false then
      local data = string.format("%s",msg[2])
      local is = data:match("^(%-?[%d.e]*)")
      totalScore = tonumber(is)
    end
    msg = clientPopMessage()
  end
  
  numRoles={0,0,0}
  for k,v in pairs(playerList) do
    if v[4] == 1 then numRoles[1] = numRoles[1] + 1 end
    if v[4] == 2 then numRoles[2] = numRoles[2] + 1 end
    if v[4] == 3 then numRoles[3] = numRoles[3] + 1 end
  end
  
  if getIsHost() then
    if numRoles[1] > 0 and timeRemains > 0 then
      if numEmergencies[1] < (maxEmergenciesPPPR*numRoles[1]) then
        local rnd = math.random(spawnRandom)
        if rnd == 1 then
          local done=false
          while done == false do
            local gridX = math.random(gridSize)-1
            local gridY = math.random(gridSize)-1
            if getGrid(gridX,gridY) == 0 and isCollide(gridX,gridY)==false then
              addEmergency(1,gridX*blockSize,gridY*blockSize)
              done=true
            end
          end
        end
      end
    end
    if numRoles[2] > 0 and timeRemains > 0 then
      if numEmergencies[2] < (maxEmergenciesPPPR*numRoles[2]) then
        local rnd = math.random(spawnRandom)
        if rnd == 1 then
          local done=false
          while done == false do
            local gridX = math.random(gridSize)-1
            local gridY = math.random(gridSize)-1
            if getGrid(gridX,gridY) == 0 and isCollide(gridX,gridY)==false then
              addEmergency(2,gridX*blockSize,gridY*blockSize)
              done=true
            end
          end
        end
      end
    end
    if numRoles[3] > 0 and timeRemains > 0 then
      if numEmergencies[3] < (maxEmergenciesPPPR*numRoles[3]) then
        local rnd = math.random(spawnRandom)
        if rnd == 1 then
          local done=false
          while done == false do
            local gridX = math.random(gridSize)-1
            local gridY = math.random(gridSize)-1
            if getGrid(gridX,gridY) == 0 and isCollide(gridX,gridY)==false then
              addEmergency(3,gridX*blockSize,gridY*blockSize)
              done=true
            end
          end
        end
      end
    end
    for k,v in pairs(playerList) do
      for l,w in pairs(emergencyList) do
        if v[4] == w[1] and timeRemains > 0 then
          local dist = math.sqrt((math.abs(v[2]-w[2])*math.abs(v[2]-w[2]))+(math.abs(v[3]-w[3])*math.abs(v[3]-w[3])))
          if dist < actionRadius then
            emergencyList[l][4] = emergencyList[l][4] - (dt * actionRate)
            if emergencyList[l][4] <= 0 then
              table.remove(emergencyList,l)
              numEmergencies[w[1]] = numEmergencies[w[1]] - 1
              totalScore = totalScore + 1
              break
            end
          end
        end
      end
    end
    for k,v in pairs(emergencyList) do
      local msg = string.format("%d %d %d %d %d",k,v[1], v[2], v[3], v[4])
      clientPushMessageUnique("UPDATEGAME",msg)
    end
    timeRemains = timeRemains - dt
    if timeRemains <= 0 then
      timeRemains = 0
      emergencyList = {}
    end
    local msg = string.format("%d",timeRemains)
    clientPushMessageUnique("UPDATETIME",msg)
    local msg = string.format("%d",totalScore)
    clientPushMessageUnique("UPDATESCORE",msg)
  end
end

function drawGame()
  love.graphics.setColor( 255,255,255 )
  love.graphics.rectangle("line",0,0,worldSize,worldSize)
  for k,v in pairs(map) do
    for l,w in pairs(v) do
      if w == 1 then
        love.graphics.rectangle("fill",(k-1)*blockSize,(l-1)*blockSize,blockSize,blockSize)
      end
    end
  end
  
  love.graphics.setFont(gameFont) 
  love.graphics.setColor( 255,255,255 )
  love.graphics.print("Time: " .. timeRemains, 700, 50);
  love.graphics.print("Score: " .. totalScore, 700, 70);
  
  for k,v in pairs(emergencyList) do
    local col = getRoleColour(v[1])
    love.graphics.setColor( col[1],col[2],col[3] )
    love.graphics.circle("fill",v[2]+(blockSize/2),v[3]+(blockSize/2),(playerSize/2))
    love.graphics.setColor( 255,255,255 )
    love.graphics.circle("line",v[2]+(blockSize/2),v[3]+(blockSize/2),v[4]/10)
  end
  for k,v in pairs(playerList) do
    local col = getRoleColour(v[4])
    love.graphics.setColor( col[1],col[2],col[3] )
    love.graphics.rectangle("fill",v[2],v[3],playerSize,playerSize)
    love.graphics.setFont(gameFont) 
    love.graphics.print(v[1], v[2], v[3]+playerSize);
  end
end

function addPlayer(name,x,y,role)
  local player = {name,x,y,role}
  table.insert(playerList,player)
end

function randomColour()
  local col = {0,0,0}
  local red = (math.random(14) + 1) * 16
  local green = (math.random(14) + 1) * 16
  local blue = (math.random(14) + 1) * 16
  col = {red,green,blue}
  return col
end

function getNextRole()
  local smallestId = 0
  local smallestNum = 9999999
  for k,v in pairs(numRoles) do
    if v < smallestNum then
      smallestNum=v
      smallestId=k
    end
  end
  if smallestId < 1 then smallestId = 1 end
  return smallestId
end

function getRoleColour(role)
  if role == 1 then
    return {0,0,255}
  elseif role == 2 then
    return {255,0,0}
  elseif role == 3 then
    return {0,255,0}
  else
    return {255,255,255}
  end
end

function addEmergency(role,x,y)
  local severity = 100 + math.random(100)
  local emergency = {role,x,y,severity}
  table.insert(emergencyList,emergency)
  numEmergencies[role] = numEmergencies[role] + 1
end

function isCollide(x,y)
  local col1 = isEmergencyCollide(x,y)
  if col1 == true then
    return true
  else
    local col2 = isPlayerCollide(x,y)
    return col2
  end
end

function isEmergencyCollide(x,y)
  local collide = false
  x = x + (playerSize/2)
  y = y + (playerSize/2)
  for k,v in pairs(emergencyList) do
    local dist = math.sqrt((math.abs(v[2]-x)*math.abs(v[2]-x))+(math.abs(v[3]-y)*math.abs(v[3]-y)))
    if dist < playerSize then 
      collide=true
      break
    end
  end
  return collide
end

function isPlayerCollide(x,y)
  local collide = false
  x = x + (playerSize/2)
  y = y + (playerSize/2)
  for k,v in pairs(playerList) do
    if k ~= 1 then
      local px = v[2] + (playerSize/2)
      local py = v[3] + (playerSize/2)
      local dist = math.sqrt((math.abs(px-x)*math.abs(px-x))+(math.abs(py-y)*math.abs(py-y)))
      if dist < playerSize then
        collide=true
        break
      end
    end
  end
  return collide
end