# OP Environment

import math

import cocotb
from cocotb.triggers import Timer
from cocotb.handle import Force
from cocotb.handle import Release

from dotmap import DotMap

# ------------------------------------------------------------------------------------------------
# Classes

class Sim(DotMap):

   def msg(self, m):
     self.dut._log.info(f'[{self.cycle:08d}] {m}')  #wtf do multiline if /n in m

   def __init__(self, dut, cfg=None):
      super().__init__()
      self.dut = dut
      self.resetDone = False

      # defaults
      self.memFiles = ['../mem/boot_ieq1.bin.hex'] #wtf cmdline parm
      self.threads = None
      self.resetCycle = 10
      self.hbCycles = 100
      self.clk2x = True
      self.clk4x = False
      self.resetAddr = 0xFFFFFFFC
      self.resetOp = 0x48000002
      self.maxCycles = 500
      self.memFiles = None
      self.config = DotMap()
      # these should be in a2 core class
      self.config.core = DotMap({
         'creditsLd': None,
         'creditsSt': None,
         'creditsLdStSingle': None,
         'lsDataForward' : None,
         'cpcr4_sq_cnt' : None
      })
      self.config.a2l2 = DotMap({
         'badAddr': [('E0','E0', 'IRW')]
      })
      # json
      if cfg is not None:
         pass

      # runtime
      self.cycle = 0
      self.ok = True
      self.fail = None
      self.done = False

      if self.threads is None:
         try:
            v = dut.an_ac_pm_thread_stop[1].value
            self.threads = 2
         except:
            self.threads = 1
         self.msg(f'Set threads={self.threads}.')

   # helpers
   # wtf: assertFail doesn't work??#@#!!
   def safeint(self, v, n=10, assertFail=True, rc=False):
      try:
         res = int(v, n)
         ok = True
      except:
         self.ok = False
         self.fail = f'Bad integer conversion: {v},{n}'
         if assertFail:
            assert(False, self.fail)
         res = 0
         ok = False
      if rc:
         return (ok, res)
      else:
         return res


class TransQ(DotMap):
   def __init__(self):
      super().__init__()

# could allow registering callback for loads and/or stores in range
class Memory(DotMap):

   def __init__(self, sim, default=0, logStores=True):
      super().__init__()
      self.sim = sim
      self.data = {}
      self.le = False
      self.default = default  # default word data for unloaded
      self.logStores = logStores

   def loadFile(self, filename, format='ascii', addr=0, le=0):
      # format # binary, ascii, ascii w/addr
      # le: reverse bytes
      try:
         if format == 'ascii':
            with open(filename, 'r') as f:
               lines = f.readlines()
               for line in lines:
                  self.data[addr] = int(line, 16)  # key is int
                  addr += 4
         elif format == 'binary':
            pass
         elif format == 'addrdata':
            pass
      except Exception as e:
         self.sim.msg(f'Error reading {filename}:\n{e}')
         raise IOError

   # word-aligned byte address
   def read(self, addr):
      try:
         addr = addr + 0
      except:
         addr = int(addr, 16)
      if addr in self.data:
         return self.data[addr]
      else:
         return self.default

   # word-aligned byte address + data
   def write(self, addr, data):
      try:
         addr = addr + 0
      except:
         addr = int(addr, 16)
      try:
         data = data + 0
      except:
         data = int(data, 16)
      if self.logStores:
         if addr not in self.data:
            self.sim.msg(f'Mem Update: @{addr:08X} XXXXXXXX->{data:08X}')
         else:
            self.sim.msg(f'Mem Update: @{addr:08X} {self.data[addr]:08X}->{data:08X}')
      self.data[addr] = data

   def dump(self, start, end, cols=4, ascii=True, trimLeadingZeros=False, trimTrailingZeros=False):
      #wtf check for start>end and do in reverse
      #wtf trimTrailingZeros

      # word-align
      wordstart = (start >> 2) << 2
      wordend = (end >> 2) << 2
      # column align (handles powers of 2)
      c = int(math.log2(cols))
      # address of column 0 of first line
      linestart = (wordstart >> 2 >> c) << c << 2
      # column of wordstart
      colstart = (wordstart>>2) - (linestart>>2)
      # word address
      word = wordstart>>2

      text = ''
      row = ''
      rowTransDict = {}
      for i in range(32):
         rowTransDict[i] = '.'

      # first line may be unaligned by row
      zeros = True
      if colstart != 0:
         textRow += f'{linestart:08X}: '
         for i in range(colstart):
            textRow += f'{"":8s} '
            row += f'{"":8s} '
         for i in range(colstart, cols):
            v = f'{self.read(word<<2):08X}'
            textRow += v + ' '
            if v != '00000000':
               zeros = False
            row += v
            word +=1
         rowb = bytearray.fromhex(row)
         rowb = rowb.decode('iso-8859-1').translate(rowTransDict)
         textRow += ('   ' +  rowb) if ascii else ''
         textRow += '\n'
         if not trimLeadingZeros or not zeros:
            text += textRow

      lastword = wordend >> 2
      while (word <= lastword):
         textRow = f'{word<<2:08X}: '
         row = ''
         for j in range(cols):
            v = f'{self.read(word<<2):08X}'
            textRow += v + ' '
            if v != '00000000':
               zeros = False
            row += v
            word += 1
            if (word > lastword):
               break
         rowb = bytearray.fromhex(row)
         rowb = rowb.decode('iso-8859-1').translate(rowTransDict)
         textRow += ('   ' + rowb) if ascii else ''
         textRow += '\n'
         if not trimLeadingZeros or not zeros:
            text += textRow

      return text

# ------------------------------------------------------------------------------------------------
# Functions

# the zero char pad should be based on pad value; currently 64 max
def hex(n, pad=0):
   if pad:
      return f'0000000000000000000000000000000000000000000000000000000000000000{n.value.hex()[2:].upper()}'[-pad:]
   else:
      return n.value.hex()[2:].upper()
