///////////////////////////////////////////////////////////////////////////////////////////////////
// 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.
///////////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////
def newObj(typeNum)
  word ptr
  typeCounts[typeNum] = typeCounts[typeNum] + 1
  ptr = mmgr(HEAP_ALLOC, typeNum)
  if typeNum >= $80
    nextObjID = nextObjID + 1
    ptr=>id = nextObjID
  fin
  return ptr
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def freeObj(ptr)
  byte typeNum
  if ptr == NULL; return; fin
  typeNum = ^ptr
  //printf3("  freeObj p=$%x t=$%x ct=%d\n", ptr, typeNum, typeCounts[typeNum])
  if typeCounts[typeNum] == 0
    fatal("Can't decr count past 0")
  fin
  typeCounts[typeNum] = typeCounts[typeNum] - 1
  if typeNum >= $80
    when typeNum
      is TYPE_GLOBAL
        fatal("can't free global obj")
      is TYPE_PLAYER
        freeObj(ptr=>p_nextObj)
        freeObj(ptr=>name)
        freeObj(ptr=>items)
        break
      is TYPE_ITEM
        freeObj(ptr=>p_nextObj)
        freeObj(ptr=>name)
        break
      otherwise
        fatal("unknown obj type encountered in heap")
    wend
  fin
end

///////////////////////////////////////////////////////////////////////////////////////////////////
// Locate the heap string with the specified length.
def findTestString(len)
  typeCounts[len] = typeCounts[len] + 1
  return mmgr(HEAP_INTERN, testStrings[len-1])
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def randomString()
  return findTestString((rand16() % 10) + 1)
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def addTypes()
  mmgr(HEAP_ADD_TYPE, @typeTbl_Global)
  typeLengths[TYPE_GLOBAL & $7F] = Global
  global = mmgr(HEAP_ALLOC, TYPE_GLOBAL)
  typeCounts[TYPE_GLOBAL] = 1

  mmgr(HEAP_ADD_TYPE, @typeTbl_Player)
  typeLengths[TYPE_PLAYER & $7F] = Player
  mmgr(HEAP_ADD_TYPE, @typeTbl_Item)
  typeLengths[TYPE_ITEM & $7F] = Item
end

///////////////////////////////////////////////////////////////////////////////////////////////////
// Check the object counts on the heap
def checkHeapCounts()
  word p, t
  byte bad
  //word testStr
  //testStr = "  Type $%x obj at $%x\n"

  // Clear the check array
  for t = 0 to 255; checkCounts[t] = 0; next
  
  // Now traverse the heap, counting the number of each object type
  p = heapStart
  while *p
    t = ^p
    //printf2(testStr, t, p)
    if p < heapStart or (p-heapStart) >= heapSize
      fatal("Invalid pointer in heap")
    fin
    checkCounts[t] = checkCounts[t] + 1
    if t < $80
      p = p + t + 1
    elsif t < TYPE_GLOBAL or t > TYPE_ITEM
      fatal("Unknown type in heap")
    else
      p = p + typeLengths[t & $7F]
    fin
  loop

  // Then verify the counts
  bad = 0
  for t = 0 to 127
    if (typeCounts[t] > 0) and (checkCounts[t] <> 1)
      printf2("Count for type $%x should be 1, got %d\n", t, checkCounts[t])
      bad = bad+1
    fin
  next
  for t = 128 to 255
    if typeCounts[t] <> checkCounts[t]
      printf3("Count for type $%x should be %d, got %d\n", t, typeCounts[t], checkCounts[t])
      bad = bad+1
    fin
  next

  if bad
    fatal("Heap errors found.")
  fin
end

def addItem(addTo)
  word p

  // Create the object, link it into the player's list
  puts("  Adding item.\n")
  p = newObj(TYPE_ITEM)
  addToList(addTo + items, p)

  // Assign attributes
  p=>name = randomString()
  p->kind = rand16()
  p->cost = rand16()

  return p
end

def addPlayer()
  word p
  byte nItems, i

  // Create the object, and link it in to the global list
  p = newObj(TYPE_PLAYER)

  // Assign attributes
  p=>name = randomString()
  p->muscle = rand16()
  p->quickness = rand16()

  nItems = rand16() % 3
  for i = 0 to nItems
    addItem(p)
  next

  p->health = rand16()

  addToList(global + players, p)

  return p
end

def countList(p)
  word n
  word testStr
  n = 0
  while p
    n = n+1
    p = p=>p_nextObj
  loop
  return n
end

def randomFromList(p)
  word n
  n = rand16() % countList(p)
  while p and n > 0
    p = p=>p_nextObj
    n = n-1
  loop
  return p
end

def collect()
  word nFree
  mmgr(CHECK_MEM, 0)
  nFree = mmgr(HEAP_COLLECT, 0)
  printf1("  heap avail=$%x\n", nFree)
  checkHeapCounts()
  mmgr(CHECK_MEM, 0)
end

///////////////////////////////////////////////////////////////////////////////////////////////////
// Do something random to the heap
def randomHeapAction()
  byte acted
  word p, p2

  // Random actions
  acted = FALSE
  when rand16() % 12

    // Add player
    is 0
    is 1
    is 2
    is 3
      if countList(global=>players) < 15
        puts("\n*** ADDING PLAYER ***\n")
        addPlayer()
        acted = TRUE
      fin
      break

    // Remove player
    is 4
      if countList(global=>players) > 8
        puts("\n*** REMOVING PLAYER ***\n")
        p = randomFromList(global=>players)
        printf1("  Unlinking player $%x\n", p)
        removeFromList(global + players, p)
        freeObj(p)
        acted = TRUE
      fin
      break

    // Add item
    is 5
    is 6
    is 7
      p = randomFromList(global=>players)
      if p and countList(p=>items) < 10
        puts("\n*** ADDING ITEM ***\n")
        addItem(p)
        acted = TRUE
      fin
      break

    // Remove item
    is 8
      p = randomFromList(global=>players)
      if p and p=>items
        puts("\n*** REMOVING ITEM ***\n")
        p2 = randomFromList(p=>items)
        removeFromList(p + items, p2)
        freeObj(p2)
        acted = TRUE
      fin
      break

    // Trade item
    is 9
    is 10
      p = randomFromList(global=>players)
      if p and p=>items
        puts("\n*** TRADING ITEM ***\n")
        p2 = randomFromList(p=>items)
        removeFromList(p + items, p2)
        p = randomFromList(global=>players)
        addToList(p + items, p2)
      fin
      break

    // Garbage collect
    is 11
      puts("\n*** COLLECTING GARBAGE ***\n")
      collect()
      acted = TRUE
  wend

  if acted
    mmgr(CHECK_MEM, 0)
    //puts("Hit a key: ")
    //getUpperKey()
    //crout()
  fin
end

///////////////////////////////////////////////////////////////////////////////////////////////////
// Test out the heap
def testHeap()
  // Close out memory load
  mmgr(FINISH_LOAD, 0) // 0 = close

  *seed = 0  // start with fixed seed for repeatable results
  collect()

  // Random actions
  while TRUE
    randomHeapAction()
  loop

  puts("Heap test complete. Hit a key.\n")
  getUpperKey()
end
