# Nimbus
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Licensed under either of
#  * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
#    http://www.apache.org/licenses/LICENSE-2.0)
#  * MIT license ([LICENSE-MIT](LICENSE-MIT) or
#    http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.

import
  std/[os, strformat, sequtils, strutils, times],
  ../../nimbus/utils/tx_pool/[tx_chain, tx_desc, tx_gauge, tx_item, tx_tabs],
  ../../nimbus/utils/tx_pool/tx_tasks/[tx_packer, tx_recover],
  ../replay/[pp, undump],
  chronicles,
  eth/[common, keys],
  stew/[keyed_queue, sorted_set],
  stint

# Make sure that the runner can stay on public view without the need
# to import `tx_pool/*` sup-modules
export
  pp,
  tx_chain.TxChainGasLimits,
  tx_chain.`maxMode=`,
  tx_chain.clearAccounts,
  tx_chain.db,
  tx_chain.limits,
  tx_chain.nextFork,
  tx_chain.profit,
  tx_chain.receipts,
  tx_chain.reward,
  tx_chain.vmState,
  tx_desc.chain,
  tx_desc.txDB,
  tx_desc.verify,
  tx_gauge,
  tx_packer.packerVmExec,
  tx_recover.recoverItem,
  tx_tabs.TxTabsRef,
  tx_tabs.any,
  tx_tabs.decAccount,
  tx_tabs.dispose,
  tx_tabs.eq,
  tx_tabs.flushRejects,
  tx_tabs.gasLimits,
  tx_tabs.ge,
  tx_tabs.gt,
  tx_tabs.incAccount,
  tx_tabs.incNonce,
  tx_tabs.le,
  tx_tabs.len,
  tx_tabs.lt,
  tx_tabs.nItems,
  tx_tabs.reassign,
  tx_tabs.reject,
  tx_tabs.verify,
  undumpNextGroup

const
  # pretty printing
  localInfo* = block:
    var rc: array[bool,string]
    rc[true] = "L"
    rc[false] = "R"
    rc

  statusInfo* = block:
    var rc: array[TxItemStatus,string]
    rc[txItemPending] = "*"
    rc[txItemStaged] = "S"
    rc[txItemPacked] = "P"
    rc

# ------------------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------------------

proc joinXX(s: string): string =
  if s.len <= 30:
    return s
  if (s.len and 1) == 0:
    result = s[0 ..< 8]
  else:
    result = "0" & s[0 ..< 7]
  result &= "..(" & $((s.len + 1) div 2) & ").." & s[s.len-16 ..< s.len]

proc joinXX(q: seq[string]): string =
  q.join("").joinXX

proc toXX[T](s: T): string =
  s.toHex.strip(leading=true,chars={'0'}).toLowerAscii

proc toXX(q: Blob): string =
  q.mapIt(it.toHex(2)).join(":")

proc toXX(a: EthAddress): string =
  a.mapIt(it.toHex(2)).joinXX

proc toXX(h: Hash256): string =
  h.data.mapIt(it.toHex(2)).joinXX

proc toXX(v: int64; r,s: UInt256): string =
  v.toXX & ":" & ($r).joinXX & ":" & ($s).joinXX

# ------------------------------------------------------------------------------
# Public functions,  pretty printer
# ------------------------------------------------------------------------------

proc pp*(q: seq[(EthAddress,int)]): string =
  "[" & q.mapIt(&"{it[0].pp}:{it[1]:03d}").join(",") & "]"

proc pp*(w: TxItemStatus): string =
  ($w).replace("txItem")

proc pp*(tx: Transaction): string =
  ## Pretty print transaction (use for debugging)
  result = "(txType=" & $tx.txType

  if tx.chainId.uint64 != 0:
    result &= ",chainId=" & $tx.chainId.uint64

  result &= ",nonce=" & tx.nonce.toXX
  if tx.gasPrice != 0:
    result &= ",gasPrice=" & tx.gasPrice.toKMG
  if tx.maxPriorityFee != 0:
    result &= ",maxPrioFee=" & tx.maxPriorityFee.toKMG
  if tx.maxFee != 0:
    result &= ",maxFee=" & tx.maxFee.toKMG
  if tx.gasLimit != 0:
    result &= ",gasLimit=" & tx.gasLimit.toKMG
  if tx.to.isSome:
    result &= ",to=" & tx.to.get.toXX
  if tx.value != 0:
    result &= ",value=" & tx.value.toKMG
  if 0 < tx.payload.len:
    result &= ",payload=" & tx.payload.toXX
  if 0 < tx.accessList.len:
    result &= ",accessList=" & $tx.accessList

  result &= ",VRS=" & tx.V.toXX(tx.R,tx.S)
  result &= ")"

proc pp*(w: TxItemRef): string =
  ## Pretty print item (use for debugging)
  let s = w.tx.pp
  result = "(timeStamp=" & ($w.timeStamp).replace(' ','_') &
    ",hash=" & w.itemID.toXX &
    ",status=" & w.status.pp &
    "," & s[1 ..< s.len]

proc pp*(txs: openArray[Transaction]; pfx = ""): string =
  let txt = block:
    var rc = ""
    if 0 < txs.len:
      rc = "[" & txs[0].pp
      for n in 1 ..< txs.len:
        rc &= ";" & txs[n].pp
      rc &= "]"
    rc
  txt.multiReplace([
    (",", &",\n   {pfx}"),
    (";", &",\n  {pfx}")])

proc pp*(txs: openArray[Transaction]; pfxLen: int): string =
  txs.pp(" ".repeat(pfxLen))

proc pp*(w: TxTabsItemsCount): string =
  &"{w.pending}/{w.staged}/{w.packed}:{w.total}/{w.disposed}"

proc pp*(w: TxTabsGasTotals): string =
  &"{w.pending}/{w.staged}/{w.packed}"

proc pp*(w: TxChainGasLimits): string =
  &"min={w.minLimit}" &
    &" trg={w.lwmLimit}:{w.trgLimit}" &
    &" max={w.hwmLimit}:{w.maxLimit}"

# ------------------------------------------------------------------------------
# Public functions, other
# ------------------------------------------------------------------------------

proc isOK*(rc: ValidationResult): bool =
  rc == ValidationResult.OK

proc toHex*(acc: EthAddress): string =
  acc.toSeq.mapIt(it.toHex(2)).join

proc say*(noisy = false; pfx = "***"; args: varargs[string, `$`]) =
  if noisy:
    if args.len == 0:
      echo "*** ", pfx
    elif 0 < pfx.len and pfx[^1] != ' ':
      echo pfx, " ", args.toSeq.join
    else:
      echo pfx, args.toSeq.join

proc setTraceLevel* =
  discard
  when defined(chronicles_runtime_filtering) and loggingEnabled:
    setLogLevel(LogLevel.TRACE)

proc setErrorLevel* =
  discard
  when defined(chronicles_runtime_filtering) and loggingEnabled:
    setLogLevel(LogLevel.ERROR)

proc findFilePath*(file: string;
                   baseDir, repoDir: openArray[string]): Result[string,void] =
  for dir in baseDir:
    for repo in repoDir:
      let path = dir / repo / file
      if path.fileExists:
        return ok(path)
  err()

# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------