///////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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 "globalDefs.plh"
include "playtype.plh"
include "diskops.plh"
include "gen_modules.plh"

const NOX_LEN = $80  // max size of the data block we care about

// Exported functions go here. First a predef for each one, then a table with function pointers
// in the same order as the constants are defined in the the header.
predef _getGame(pDiskOps)#1
word[] funcTbl = @_getGame

word pDiskOps

byte[] legendos_filename = "LEGENDOS.SYSTEM"

word origChksum_0, origChksum_1
word curChksum_0,  curChksum_1

word noxData

byte[] S_NOX_FILENAME = "DATA.SAVE.GAME1"

///////////////////////////////////////////////////////////////////////////////////////////////////
// Definitions used by assembly code
asm __defs

; Use hi-bit ASCII for Apple II
!convtab "../../include/hiBitAscii.ct"

; Headers
!source "../../include/global.i"
!source "../../include/plasma.i"
!source "../../include/mem.i"
!source "../../include/prorwts.i"

; General use
tmp         = $2
pTmp        = $4
chksum      = $8 ; 4 bytes
xsav        = $46

end

///////////////////////////////////////////////////////////////////////////////////////////////////
// Calculate a 4-byte checksum of a buffer.
// If doStore=1, store it in *pSum (4 bytes).
// If doStore=0, compare to *pSum (4 bytes), and return zero if equal
asm calcChecksum(doStore, pBuf, nPages, pSum)#0
!zone {
  +asmPlasmNoRet 4
  sta tmp           ; pSum
  sty tmp+1
  lda evalStkL+2,x  ; pBuf
  sta pTmp
  lda evalStkH+2,x
  sta pTmp+1
  lda evalStkL+3,x  ; save doStore flg for later
  pha
  lda evalStkL+1,x  ; nPages
  ldy #0
  ldx #3
- sta chksum,x      ; a-reg still has nPages, fine for consistent init
  dex
  bpl -
  clc

  ldx #$60
  stx 0
  jsr 0

.pag
  pha               ; save # pages left
.quad
  ldx #3
- lda chksum,x
  rol
  adc (pTmp),y
  sta chksum,x
  iny
  dex
  bpl -
  tya
  bne .quad

  inc pTmp+1      ; next page
  pla
  sec
  sbc #1
  bne .pag

  ldy #3
  pla             ; check doStore flg
  beq .chk

.sto
  lda chksum,y
  sta (tmp),y
  dey
  bpl .sto
  rts             ; return val irrelevant for store mode

.chk
  lda chksum,y
  eor (tmp),y
  bne +
  dey
  bpl .chk
+ ldy #0          ; return zero if equal
  rts
} ; end of zone
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def reinserted()#1
  if callProRWTS(RWTS_READ | RWTS_OPENDIR, @S_GAME1_FILENAME, LOAD_SAVE_BUF, HEAP_SIZE) == 0
    calcChecksum(TRUE, LOAD_SAVE_BUF, HEAP_SIZE >> 8, @curChksum_0)
    if curChksum_0 == origChksum_0 and curChksum_1 == origChksum_1
      return TRUE
    fin
  fin
  return FALSE
end

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// Nox fields
// 
// $0     - health status - ask Mark what this means
// $1     - level
// $2-3   - HP lo/hi
// $10    - str
// $11    - dex
// $12    - int
// $13    - gender - 0=male, 1=female, 2=other
// $19    - skill score - melee
// $1C    - skill score - ranged
// $1F    - skill score - dodge-parry
// $22    - skill score - critical hit
// $25    - skill score - lockpick
// $28    - skill score - pilfer
// $2B    - training - melee
// $2C    - training - ranged
// $2D    - training - dodge-parry
// $2E    - training - critical hit
// $2F    - training - lockpick
// $30    - training - pilfer
// $47    - attrib upgrade points
// $48-49 - hp max lo/hi
// $4B-59 - character name (hi-bit ASCII, zero terminated)
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////
def findNox(readOrOpen)#1
  word len
  ^LOAD_SAVE_BUF = $AA
  if callProRWTS(RWTS_SEEK | readOrOpen, @S_NOX_FILENAME, NULL, $C00) == 0
    ^LOAD_SAVE_BUF = $AA
    if callProRWTS(RWTS_READ | RWTS_RDWRPART, NULL, LOAD_SAVE_BUF, NOX_LEN) == 0
      // Check for valid name
      len = 0
      while ^(LOAD_SAVE_BUF+$4B+len) >= $A0
        len++
      loop
      if len > 0 and ^(LOAD_SAVE_BUF+$4B+len) == 0 and ^LOAD_SAVE_BUF <> $AA
        return TRUE
      fin
    fin
  fin
  return FALSE
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def parseNoxName()#1
  word len, c, prev

  len = 0
  prev = 0
  while TRUE
    c = ^(noxData + $4B + len)
    if c == 0; break; fin
    if c < $A0; break; fin
    c = c & $7F
    // Convert to Title Case:
    // if prev and c are both upper letters, convert this char to lower
    ^($201+len) = (prev >= 'A' and prev <= 'Z' and c >= 'A' and c <= 'Z') ?? c + $20 :: c
    prev = c
    len++
  loop
  ^$200 = len
  return mmgr(HEAP_INTERN, $200)
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def printNox()#0
  word len, c, prev

  textHome
  printf1("level=%d\n", ^(noxData+1))
  printf1("strength=%d\n", ^(noxData+$10))
  printf1("dexterity=%d\n", ^(noxData+$11))
  printf1("intelligence=%d\n", ^(noxData+$12))
  printf1("gender=%d\n", ^(noxData+$13))
  printf1("hp-max=%d\n", *(noxData+$48))
  printf1("name=%s\n", parseNoxName())
  rdkey
end

///////////////////////////////////////////////////////////////////////////////////////////////////
// Stats should be converted like nox 8-20 = 5 LL / 21-60 = 6 / 61+ =7
def calcAttrBonus(noxOffset)#1
  word val
  val = ^(noxData+noxOffset)
  if val <= 20
    return 1
  elsif val <= 60
    return 2
  fin
  return 3
end

///////////////////////////////////////////////////////////////////////////////////////////////////
// Levels : 1-2= 2 /3-5 =3/5-8=4/9+=5
def calcLevelBonus(noxOffset)#1
  word val
  val = ^(noxData+noxOffset)
  if val <= 2
    return 1
  elsif val <= 5
    return 2
  elsif val <= 8
    return 3
  fin
  return 4
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def noxMod()#1
  word pl
  byte intBonus, strBonus, dexBonus, lvlBonus, skpBonus, i
  word hpBonus, healthBefore

  pl = global=>p_players
  pl=>s_name = parseNoxName()
  
  displayf1("Nox import bonuses:\n\n", pl=>s_name)
  lvlBonus = calcLevelBonus(1)
  intBonus = calcAttrBonus($12)
  strBonus = calcAttrBonus($10)
  dexBonus = calcAttrBonus($11)
  skpBonus = 0
  for i = 1 to lvlBonus
    skpBonus = skpBonus + callGlobalFunc(GS_LEVEL_S_P, i+1, 0, 0)
  next
  hpBonus = lvlBonus * 12

  displayf1("+%d Level\n", lvlBonus)
  displayf1("+%d Intelligence\n", intBonus)
  displayf1("+%d Strength\n", strBonus)
  displayf1("+%d Agility\n", dexBonus)
  displayf1("+%d Stamina\n", lvlBonus)
  displayf1("+%d Skill points\n", skpBonus)
  displayf1("+%d Health\n", lvlBonus*12)

  displayStr("\nApply these bonuses?")
  if getYN
    healthBefore = pl=>w_maxHealth
    for i = 1 to lvlBonus
      pl->b_stamina++
      setStat(pl, @S_XP, pl=>w_nextXP)
    next
    pl->b_strength = pl->b_strength + strBonus
    pl->b_intelligence = pl->b_intelligence + intBonus
    pl->b_agility = pl->b_agility + dexBonus
    pl=>w_maxHealth = healthBefore + hpBonus
    pl=>w_health = pl=>w_maxHealth
  fin

  return 0
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def importNox()#1

  ^$c051
  ^$c054
  textHome

  // Check for a nox game file outside the "NA" subdir
  if !findNox(RWTS_OPENDIR)
    // Check for game file within the "NA" subdir
    if callProRWTS(RWTS_READ | RWTS_OPENDIR, "NA", LOAD_SAVE_BUF, 512) == 0
      if !findNox(RWTS_READDIR)
        ^$c050
        return FALSE
      fin
    else
      ^$c050
      return FALSE
    fin
  fin

  // Save aside the Nox data for use when creating the character
  noxData = mmgr(REQUEST_MEMORY, NOX_LEN)
  memcpy(LOAD_SAVE_BUF, noxData, NOX_LEN, 0)
  ^$c050

  // Make sure the user wants to import this game
  while True
    textHome()
    ^$25 = 19
    printf1("\nNox Archaist character imported:\n  ==>  %s  <==\n", parseNoxName())
    puts("Re-insert disk 1 and press a key,\nor hit [Esc] to cancel import.")
    if rdkey == $9B // esc
      // To cancel, we need the original disk back in the drive
      if reinserted
        mmgr(FREE_MEMORY, noxData)
        return FALSE
      fin
    else
      if reinserted(); break; fin
    fin
  loop

  ^$c052 // all graphics from here on...

  // Create a new game, and hook in just after the character is created but before
  // they are displayed.
  pDiskOps=>diskops_newGame(@noxMod)
  mmgr(FREE_MEMORY, noxData)
  return TRUE
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def importLL()#1
  word p_loaded, marksSize, p_marks

  if !pDiskOps=>diskops_gameExists()
    return FALSE
  fin

  // Put game in high mem and check it. Also loads the disk marks into LOAD_SAVE_BUF
  textHome // so that "Heap %" is on non-visible part of the screen
  pDiskOps=>diskops_loadInternal()

  // Keep a copy of the imported disk marks aside in main mem
  marksSize = *LOAD_SAVE_BUF
  p_marks = mmgr(REQUEST_MEMORY, marksSize+2)
  memcpy(LOAD_SAVE_BUF, p_marks, marksSize+2, 0)

  // Make sure the user wants to import this game
  while True
    textHome()
    ^$25 = 19
    printf1("\nGame imported with this main character:\n  ==>  %s  <==\n", global=>p_players=>s_name)
    puts("Re-insert disk 1 and press a key,\nor hit [Esc] to cancel import.")
    if rdkey == $9B // esc
      mmgr(FREE_MEMORY, p_marks)
      // To cancel, we need the original disk back in the drive
      if reinserted; return FALSE; fin
    else
      if reinserted(); break; fin
    fin
  loop

  // Write the imported game data
  memcpy(HEAP_BOTTOM, LOAD_SAVE_BUF, HEAP_SIZE, 0)  // LC to low mem to get the import data back
  rwGame(RWTS_WRITE)

  // Write the imported marks right after the heap data
  callProRWTS(RWTS_WRITE | RWTS_RDWRPART, NULL, p_marks, marksSize+2)
  mmgr(FREE_MEMORY, p_marks)

  // Proceed with normal load
  ^$c052 // no text
  clearWindow
  rawDisplayStr("\n^YLoading...^N")
  return TRUE
end

///////////////////////////////////////////////////////////////////////////////////////////////////
// Load and display the title screen, set up everything in memory
def _getGame(_pDiskOps)#1
  byte ret
  pDiskOps = _pDiskOps

  // May want to use graphic font someday, but mixed-text mode for now
  ^$c053

  // Record checksum of the original data so we can tell when a different LL game is inserted, and
  // also when the original is re-inserted.
  calcChecksum(TRUE, LOAD_SAVE_BUF, HEAP_SIZE >> 8, @origChksum_0)

  // Find a game to import
  while TRUE
    textHome
    ^$25 = 19
    puts("\nInsert disk for import (in same drive),\nthen press a key, or [Esc] to cancel.")
    if rdkey == $9B // esc
      while TRUE
        if reinserted; break; fin
        textHome
        ^$25 = 19
        puts("\nRe-insert disk 1 and press a key.")
        rdkey
      loop
      ^$c052 // no text
      return 0
    fin
    if importNox; ret = 2; break; fin  // like a New game
    if importLL; ret = 1; break; fin   // like a Load game
  loop
  ^$c052 // no text
  return ret
end

///////////////////////////////////////////////////////////////////////////////////////////////////
// Boilerplate module initialization code
return @funcTbl
done
