#       Nimony
# (c) Copyright 2024 Andreas Rumpf
#
# See the file "license.txt", included in this
# distribution, for details about the copyright.

## Nimony index generator.

import std / [os, assertions, sets, hashes, tables, syncio]
include ".." / lib / nifprelude
include ".." / lib / compat2
import ".." / lib / [nifindexes, symparser]
import decls, nimony_model, programs, semos
import ".." / models / nifindex_tags

proc getAttachedOp(symId: SymId, attackedOp: var AttachedOp): bool =
  var name = pool.syms[symId]
  extractBasename(name)

  attackedOp = case name
    of "=destroy": attachedDestroy
    of "=wasMoved": attachedWasMoved
    of "=trace": attachedTrace
    of "=copy": attachedCopy
    of "=sink": attachedSink
    of "=dup": attachedDup
    else: return false

  return true

proc buildIndexExports(exports: Table[string, HashSet[SymId]]; infile: string): TokenBuf =
  result = default(TokenBuf)
  let mp = splitModulePath infile
  if exports.len != 0:
    result = createTokenBuf(32)
    for suffix, syms in exports:
      # open NIF file to get the path of source file of the module from the line info.
      let modPath = mp.dir / (suffix & mp.ext)
      var stream = nifstreams.open(modPath)
      discard processDirectives(stream.r)
      discard stream.next   # first stmts node doesn't have line info.
      let t = stream.next
      let fileId = pool.man.getFileId(t.info)
      assert fileId.isValid
      stream.close
      let path = pool.files[fileId].toAbsolutePath
      result.addParLe(TagId(FromexportIdx))
      result.addStrLit path
      for s in syms:
        var isGlobal = false
        let ident = extractBasename(pool.syms[s], isGlobal)
        result.addIdent(ident)
      result.addParRi()

proc indexFromNif*(infile: string) =
  ## Extract index from `infile` Nif file and write it to `*.idx.nif` file.
  ##
  ## See https://github.com/nim-lang/nimony/issues/1162
  var stream = nifstreams.open(infile)
  discard processDirectives(stream.r)
  var buf = fromStream(stream)
  stream.close

  var n = beginRead buf
  let root = n.info
  var converterIndexMap = default seq[(SymId, SymId)]
  var exports = default Table[string, HashSet[SymId]]  # Module suffix -> symbols to export in the module

  assert n.stmtKind == StmtsS
  inc n
  while n.kind != ParRi:
    if n.kind == ParLe:
      case n.stmtKind:
      of ProcS, FuncS, ConverterS, MethodS:
        let kind = n.stmtKind
        let routine = takeRoutine(n, SkipFinalParRi)
        let symId = routine.name.symId
        if kind == ConverterS:
          if routine.exported.kind != DotToken and
             routine.typevars.typeKind != InvokeT:
            # don't register instances and not exported ones
            let root = routine.retType.skipModifier.symId
            converterIndexMap.add((root, symId))
      of ExportS:
        inc n
        # .nif files generated by `nim nif foo.nim` has `(exports symbol1, symbol2, ...)`, but doesn't have other kinds of export node.
        # export statement like `export modname` becomes `(exports symbols1, symbols2, ...)` where symbols1, symbols2, ... are exported symbols in the module.
        # So they are indexed only with `FromexportIdx`.
        while n.kind != ParRi:
          assert n.kind == Symbol
          let sym = n.symId
          let name = pool.syms[sym]
          let suffix = extractModule(name)
          assert suffix != ""
          exports.mgetOrPut(suffix, default(HashSet[SymId])).incl sym
          inc n
        inc n
      else:
        skip n
    else:
      skip n

  endRead buf

  var exportBuf = buildIndexExports(exports, infile)

  try:
    createIndex infile, root, true,
      IndexSections(
        converters: move converterIndexMap,
        exportBuf: exportBuf)
  except:
    quit "createIndex failed: " & infile
