///////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 The 8-Bit Bunch. Licensed under the Apache License, Version 1.1 
// (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at <http://www.apache.org/licenses/LICENSE-1.1>.
// Unless required by applicable law or agreed to in writing, software distributed under 
// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 
// ANY KIND, either express or implied. See the License for the specific language 
// governing permissions and limitations under the License.
///////////////////////////////////////////////////////////////////////////////////////////////////

include "gamelib.plh"
include "playtype.plh"
include "gen_images.plh"
include "gen_enemies.plh"
include "gen_modules.plh"
include "combat.plh"

word global
predef _combat_zoneEncounter
word[] funcTbl = @_combat_zoneEncounter

// Combat variables that don't need to be saved in game state
byte nPlayersFighting
byte nEnemiesFighting
byte isFleeing

///////////////////////////////////////////////////////////////////////////////////////////////////
def canFight(p)
  return p=>w_health > 0
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def chooseEnemy(maxDist)
  word p
  byte n
  
  // TODO: Consider distance of enemies
  n = rand16() % nEnemiesFighting
  p = global=>p_combatFirst
  while TRUE
    if p->t_type == TYPE_ENEMY and canFight(p)
      if n == 0; return p; fin
      n = n - 1
    fin
    p = p=>p_combatNext
  loop
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def playerMelee(pPlayer, pWeapon)
  word pEnemy, dmg
  pEnemy = chooseEnemy(5)  // max distance 5 feet for melee
  if !pEnemy; return FALSE; fin
  if pWeapon
    dmg = rollDice(pWeapon=>r_meleeDmg)
  else
    dmg = (rand16() % 6) + 1   // default to 1d6 for fists
  fin
  // TODO: Add extra melee damage for skills, strength, etc.
  // TODO: consider enemy dodge
  pEnemy=>w_health = pEnemy=>w_health - dmg

  buildString(@addToString)
  printf3("\n%s pummels %s for %d damage.", pPlayer=>s_name, pEnemy=>s_name, dmg)
  if pEnemy=>w_health <= 0
    printf1(" %s is killed!", pEnemy=>s_name)
    nEnemiesFighting = nEnemiesFighting - 1
  fin
  puts("\n")
  displayStr(finishString(0))

  return TRUE
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def playerShoot(pPlayer, pWeapon)
  word pEnemy, pSkill, dmg
  word chance
  byte roll

  pEnemy = chooseEnemy(pWeapon->b_weaponRange)
  if !pEnemy; return FALSE; fin

  // Figure out chance to hit. First come agility and aim.
  chance = (pPlayer->b_agility * 4) + pPlayer->b_aiming

  // Add in skill modifier, if any
  pSkill = pPlayer=>p_skills
  while pSkill
    if pSkill=>b_modKind == pWeapon=>b_itemKind
      chance = chance + pSkill=>b_modValue
    fin
    pSkill = pSkill=>p_nextObj
  loop

  // Nobody can have 100% chance to hit. Let's cap it at 90%.
  chance = min(90, chance)

  roll = rand16() % 100
  if roll >= chance

    // Miss!
    displayf2("\n%s shoots at %s but misses.\n", pPlayer=>s_name, pEnemy=>s_name)

  else

    // TODO: consider multi-shot weapons
    dmg = rollDice(pWeapon=>r_projectileDmg)
    // TODO: Add extra melee damage for skills, strength, etc.
    // TODO: consider enemy dodge
    pEnemy=>w_health = pEnemy=>w_health - dmg

    buildString(@addToString)
    printf3("\n%s shoots %s for %d damage.", pPlayer=>s_name, pEnemy=>s_name, dmg)
    if pEnemy=>w_health <= 0
      printf1(" %s is killed!", pEnemy=>s_name)
      nEnemiesFighting = nEnemiesFighting - 1
    fin
    puts("\n")
    displayStr(finishString(0))

    return TRUE
  fin
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def playerDodge(pPlayer)
  displayf1("\n%s dodges.\n", pPlayer=>s_name)
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def chooseWeapon(player)
  displayStr("\nTODO: choose weapon\n")
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def combatPause()
  word n
  for n = 1 to 3000

  next
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def displayOpponents()
  word p
  byte count, first
  byte isPlural

  buildString(@addToString)
  puts("\nYou face ")
  first = TRUE
  p = global=>p_enemyGroups
  while p
    if !first
      if p=>p_nextObj
        puts(", ")
      else
        puts(" and ")
      fin
    fin
    first = FALSE
    count = countListFiltered(p=>p_enemies, p_nextObj, @canFight)
    isPlural = (count <> 1)
    if (p=>p_enemies=>r_groupSize == 0)
      printf2("%s at %d'", p=>p_enemies=>s_name, p->b_enemyGroupRange)
    else
      printf3("%d %s at %d'", count, p=>p_enemies=>s_name, p->b_enemyGroupRange)
    fin
    p = p=>p_nextObj
  loop
  puts(".\n")
  displayStr(finishString(isPlural))
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def displayOption(key, str)
  buildString(@addToString)
  printf2("\n  (%c)^T033%s ", key, str)
  rawDisplayStr(finishString(0))
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def playerCombatChoose(pl)
  word p, pWeapon
  byte nWeapons, key
  byte canShoot, canReload, canChange

  // Count weapons, and take the first as the current one.
  nWeapons = 0
  pWeapon = NULL
  p = pl=>p_items
  while p
    if p->t_type == TYPE_WEAPON
      if !pWeapon; pWeapon = p; fin
      nWeapons = nWeapons + 1
    fin
    p = p=>p_nextObj
  loop

  // NPCs always melee for now
  if pl->b_playerFlags & PLAYER_FLAG_NPC
    pl->b_combatChoice = 'M'
    return
  fin

  // Tell what the player currently faces
  displayOpponents()
  combatPause()

  // Let them know their options
  displayStr("\n")
  when rand16() % 5
    is 0
      displayStr("Think fast!"); break
    is 1
      displayStr("Do you:"); break
    is 2
      displayStr("Suck it up:"); break
    is 3
      displayStr("Options:"); break
    otherwise
      displayStr("Decisions, decisions..."); break
  wend

  displayOption('M', "Melee")
  if pWeapon
    if pWeapon->b_clipCurrent
      canShoot = TRUE
      displayOption('S', "Shoot")
    fin
    if pWeapon->b_clipCurrent < pWeapon->b_clipSize
      // TODO: Need to check for enough ammo, and use it up.
      canReload = TRUE
      displayOption('R', "Reload")
    fin
    if nWeapons >= 2
      canChange = TRUE
      displayOption('C', "Change weapon")
    fin
  fin
  displayOption('D', "Dodge")
  displayOption('F', "Flee")

  while TRUE
    pl->b_combatChoice = getUpperKey()
    displayStr("\n")
    when pl->b_combatChoice
      is 'M'
        return
      is 'F'
        isFleeing = TRUE
        return
      is 'D'
        return
      is 'S'
        if canShoot; return; fin
        break
      is 'R'
        if canReload; return; fin
        break
      is 'C'
        if canChange; return; fin
        break
    wend
    beep()
  loop
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def playerCombatTurn(pl)
  word pWeapon

  // Get weapon
  pWeapon = pl=>p_items
  while pWeapon
    if pWeapon->t_type == TYPE_WEAPON; break; fin
    pWeapon = pWeapon=>p_nextObj
  loop

  // Execute the player's choice
  when pl->b_combatChoice
    is 'M'
      playerMelee(pl, pWeapon)
      break
    is 'F'
      break
    is 'D'
      playerDodge(pl)
      break
    is 'S'
      playerShoot(pl, pWeapon)
      break
    is 'R'
      pWeapon->b_clipCurrent = pWeapon->b_clipSize
      setPlural(FALSE)
      displayf1("%s has reloaded.\n", pl=>s_name)
      break
    is 'C'
      setPlural(FALSE)
      displayf2("%s changed to using %s.\n", pl=>s_name, pWeapon=>s_name)
      break
  wend
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def enemyCombatTurn(pe)
  word pl
  byte roll, dam

  // Choose a target
  pl = randomFromListFiltered(global=>p_players, p_nextObj, @canFight)
  if !pl; return FALSE; fin

  buildString(@addToString)
  printf3("\n%s %s %s ", pe=>s_name, pe=>s_attackText, pl=>s_name)

  // Roll to hit
  roll = rand16() % 100
  if roll <= pe->b_chanceToHit
    dam = rollDice(pe=>r_enemyDmg)
    printf1("and hits for %d damage!", dam)
    pl=>w_health = max(0, pl=>w_health - dam)
    if pl=>w_health == 0
      printf1(" %s is killed!", pl=>s_name)
    fin
    showParty()
  else
    puts("and misses.")
  fin
  puts("\n")

  displayStr(finishString(0))
  return TRUE
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def combatInsert(toAdd)
  word p, pPrev

  // Find the proper position based on combat order number (keep largest first in the list)
  pPrev = @global=>p_combatFirst
  while TRUE
    p = *pPrev
    if !p or p->b_combatOrder < toAdd->b_combatOrder
      break
    fin
    pPrev = p + p_combatNext
  loop

  // Add it right there.
  toAdd=>p_combatNext = *pPrev
  *pPrev = toAdd
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def determineCombatOrder()
  word p, p2

  nPlayersFighting = 0
  nEnemiesFighting = 0

  // Do each player character, with random chance based on their agility
  global=>p_combatFirst = NULL
  p = global=>p_players
  while p
    if canFight(p)
      p->b_combatOrder = rand16() % (p->b_agility * 10)
      combatInsert(p)
      if (!(p=>b_playerFlags & PLAYER_FLAG_NPC)) // only count real players
        nPlayersFighting = nPlayersFighting + 1
      fin
    fin
    p = p=>p_nextObj
  loop

  // Then all the enemies, with random chance based on their chance to hit.
  p = global=>p_enemyGroups
  while p
    p2 = p=>p_enemies
    while p2
      if canFight(p2)
        p2->b_combatOrder = rand16() % p2->b_chanceToHit
        combatInsert(p2)
        nEnemiesFighting = nEnemiesFighting + 1
      fin
      p2 = p2=>p_nextObj
    loop
    p = p=>p_nextObj
  loop
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def makeEnemyGroup(enemyFunc)
  word p, enem, groupSize
  p = mmgr(HEAP_ALLOC, TYPE_ENEMY_GROUP)
  enem = enemyFunc()
  p->b_enemyGroupRange = max(1, rand16() % (enem->b_enemyAttackRange))
  if enem=>r_groupSize == 0  // handle unique enemies
    groupSize = 1
  else  
    groupSize = rollDice(enem=>r_groupSize)
  fin
  addToList(p + p_enemies, enem)
  while groupSize > 1
    addToList(p + p_enemies, enemyFunc())
    groupSize = groupSize - 1
  loop

  return p
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def makeRandomGroup(mapCode)
  word enemiesModule
  word enemyFunc

  enemiesModule = mmgr(QUEUE_LOAD, MODULE_GEN_ENEMIES<<8 | RES_TYPE_MODULE)
  mmgr(FINISH_LOAD, 1) // 1 = keep open

  global=>p_enemyGroups = NULL
  enemyFunc = enemiesModule()=>enemy_forZone(mapCode)
  addToList(@global=>p_enemyGroups, makeEnemyGroup(enemyFunc))

  mmgr(FREE_MEMORY, enemiesModule)
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def startCombat(mapCode)
  word p, p2, n, s

  // Setup
  isFleeing = FALSE
  makeRandomGroup(mapCode)

  // Display portrait of first group
  setPortrait(global=>p_enemyGroups=>p_enemies->b_image)

  // We're going to do all our text drawing in window 2. Also, might as well
  // set everything up so that the map gets redrawn when combat finishes.
  setWindow2()
  clearWindow()

  // Say who we're fighting
  when rand16() % 5
    is 0
      s = "Uh oh, it's gunna' be one of THOSE days.\n"; break
    is 1
      s = "Smells like trouble!\n"; break
    is 2
      s = "The hairs on the back of your neck reach for the sky!\n"; break
    is 3
      s = "You've got a bad feeling about this...\n"; break
    otherwise
      s = "Momma said there'd be days like this!\n"; break
  wend
  displayStr(s)
  p = global=>p_enemyGroups
  while p
    n = countList(p=>p_enemies)
    setPlural(n <> 1)
    if p=>p_enemies=>r_groupSize == 0
      displayf1("%s is in a fightin' mood!\n", p=>p_enemies=>s_name)
    else
      when rand16() % 5
        is 0
          s = "From out of nowhere comes/come %d %s to have their way with you!\n"; break
        is 1
          s = "%d nasty %s comes/come to stake their claim on you!\n"; break
        is 2
          s = "It's gone from bad to worse, %d %s is/are looking for trouble!\n"; break
        is 3
          s = "%d %s is/are sniffing you out, shoulda' bathed last week!\n"; break
        otherwise
          s = "You mutter a curse under your breath as you see %d %s approach you with malice!\n"; break
      wend
      displayf2(s, n, p=>p_enemies=>s_name)
    fin
    p = p=>p_nextObj
  loop

  rawDisplayStr("\nDo you:\n")
  displayOption('B', "Battle")
  displayOption('F', "Flee")
  while TRUE
    n = getUpperKey()
    if n == 'B'
      displayStr("\n\nBattle! \n")
      return TRUE
    elsif n == 'F'
      displayStr("\n\nCoward. ")
      return FALSE
    fin
    beep()
  loop

end

///////////////////////////////////////////////////////////////////////////////////////////////////
def _combat_zoneEncounter(s_encZone)
  word p
  byte answer

  // Show portrait and threat details, find out if player wants to fight (vs. run)
  if !startCombat(s_encZone); return; fin

  // Do each round of combat until player wins, loses, or flees
  while TRUE
    p = global=>p_players
    while p
      if canFight(p) and nEnemiesFighting
        playerCombatChoose(p)
      fin
      p = p=>p_nextObj
    loop

    determineCombatOrder()

    p = global=>p_combatFirst
    while p
      if !nPlayersFighting
        setPortrait(POdeath)
        when rand16() % 2
          is 0
            displayStr("\nYou bought the farm, with your life! Thanks for playing! Reload last save?\n"); break
          otherwise
            displayStr("\nDidn't see that coming... to see a fine player like you slaughtered like a common rodent... Well, let's reload and try that again, ok?\n")
        wend
        answer = getYN()  // workaround for PLASMA problem: "if !getYN()" results in CALL 0
        if !answer
          displayStr("Ah, okay. Well... just hit a key when you're ready then.")
          getUpperKey()
        fin
        reboot()
      elsif !nEnemiesFighting
        setPortrait(POcombatwin)
        when rand16() % 3
          is 0
            displayStr("\nYou survive the ordeal and rifle through the carnage.\n"); break
          is 1
            displayStr("\nThat was a close call! You search the ground.\n"); break
          otherwise
            displayStr("\nLooks like you live to fight another day!\n"); break
        wend
        getUpperKey()
        // Note: no need to clear heap -- the caller does that.
        return
      elsif isFleeing
        displayStr("\nYou have fled.")
        // Note: no need to clear heap -- the caller does that.
        return
      fin

      if canFight(p)
        when p->t_type
          is TYPE_PLAYER
            playerCombatTurn(p)
            combatPause()
            break
          is TYPE_ENEMY
            if enemyCombatTurn(p); combatPause(); fin
            break
          otherwise
            brk()
        wend
      fin
      p = p=>p_combatNext
    loop
  loop
end

///////////////////////////////////////////////////////////////////////////////////////////////////
// initialization code

global = getGlobals()
return @funcTbl
done