#
#
#           The Nim Compiler
#        (c) Copyright 2020 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## Module that contains code to replay global VM state changes and pragma
## state like ``{.compile: "foo.c".}``. For IC (= Incremental compilation)
## support.

import ".." / [ast, modulegraphs, trees, extccomp, btrees,
  msgs, lineinfos, pathutils, options, cgmeth]

import tables

import packed_ast, ic, bitabs

proc replayStateChanges*(module: PSym; g: ModuleGraph) =
  let list = module.ast
  assert list != nil
  assert list.kind == nkStmtList
  for n in list:
    assert n.kind == nkReplayAction
    # Fortunately only a tiny subset of the available pragmas need to
    # be replayed here. This is always a subset of ``pragmas.stmtPragmas``.
    if n.len >= 2:
      internalAssert g.config, n[0].kind == nkStrLit and n[1].kind == nkStrLit
      case n[0].strVal
      of "hint": message(g.config, n.info, hintUser, n[1].strVal)
      of "warning": message(g.config, n.info, warnUser, n[1].strVal)
      of "error": localError(g.config, n.info, errUser, n[1].strVal)
      of "compile":
        internalAssert g.config, n.len == 4 and n[2].kind == nkStrLit
        let cname = AbsoluteFile n[1].strVal
        var cf = Cfile(nimname: splitFile(cname).name, cname: cname,
                       obj: AbsoluteFile n[2].strVal,
                       flags: {CfileFlag.External},
                       customArgs: n[3].strVal)
        extccomp.addExternalFileToCompile(g.config, cf)
      of "link":
        extccomp.addExternalFileToLink(g.config, AbsoluteFile n[1].strVal)
      of "passl":
        extccomp.addLinkOption(g.config, n[1].strVal)
      of "passc":
        extccomp.addCompileOption(g.config, n[1].strVal)
      of "localpassc":
        extccomp.addLocalCompileOption(g.config, n[1].strVal, toFullPathConsiderDirty(g.config, module.info.fileIndex))
      of "cppdefine":
        options.cppDefine(g.config, n[1].strVal)
      of "inc":
        let destKey = n[1].strVal
        let by = n[2].intVal
        let v = getOrDefault(g.cacheCounters, destKey)
        g.cacheCounters[destKey] = v+by
      of "put":
        let destKey = n[1].strVal
        let key = n[2].strVal
        let val = n[3]
        if not contains(g.cacheTables, destKey):
          g.cacheTables[destKey] = initBTree[string, PNode]()
        if not contains(g.cacheTables[destKey], key):
          g.cacheTables[destKey].add(key, val)
        else:
          internalError(g.config, n.info, "key already exists: " & key)
      of "incl":
        let destKey = n[1].strVal
        let val = n[2]
        if not contains(g.cacheSeqs, destKey):
          g.cacheSeqs[destKey] = newTree(nkStmtList, val)
        else:
          block search:
            for existing in g.cacheSeqs[destKey]:
              if exprStructuralEquivalent(existing, val, strictSymEquality=true):
                break search
            g.cacheSeqs[destKey].add val
      of "add":
        let destKey = n[1].strVal
        let val = n[2]
        if not contains(g.cacheSeqs, destKey):
          g.cacheSeqs[destKey] = newTree(nkStmtList, val)
        else:
          g.cacheSeqs[destKey].add val
      else:
        internalAssert g.config, false

proc replayGenericCacheInformation*(g: ModuleGraph; module: int) =
  ## We remember the generic instantiations a module performed
  ## in order to to avoid the code bloat that generic code tends
  ## to imply. This is cheaper than deduplication of identical
  ## generic instantiations. However, deduplication is more
  ## powerful and general and I hope to implement it soon too
  ## (famous last words).
  assert g.packed[module].status == loaded
  for it in g.packed[module].fromDisk.typeInstCache:
    let key = translateId(it[0], g.packed, module, g.config)
    g.typeInstCache.mgetOrPut(key, @[]).add LazyType(id: FullId(module: module, packed: it[1]), typ: nil)

  for it in mitems(g.packed[module].fromDisk.procInstCache):
    let key = translateId(it.key, g.packed, module, g.config)
    let sym = translateId(it.sym, g.packed, module, g.config)
    var concreteTypes = newSeq[FullId](it.concreteTypes.len)
    for i in 0..high(it.concreteTypes):
      let tmp = translateId(it.concreteTypes[i], g.packed, module, g.config)
      concreteTypes[i] = FullId(module: tmp.module, packed: it.concreteTypes[i])

    g.procInstCache.mgetOrPut(key, @[]).add LazyInstantiation(
      module: module, sym: FullId(module: sym.module, packed: it.sym),
      concreteTypes: concreteTypes, inst: nil)

  for it in mitems(g.packed[module].fromDisk.methodsPerType):
    let key = translateId(it[0], g.packed, module, g.config)
    let col = it[1]
    let tmp = translateId(it[2], g.packed, module, g.config)
    let symId = FullId(module: tmp.module, packed: it[2])
    g.methodsPerType.mgetOrPut(key, @[]).add (col, LazySym(id: symId, sym: nil))

  for it in mitems(g.packed[module].fromDisk.enumToStringProcs):
    let key = translateId(it[0], g.packed, module, g.config)
    let tmp = translateId(it[1], g.packed, module, g.config)
    let symId = FullId(module: tmp.module, packed: it[1])
    g.enumToStringProcs[key] = LazySym(id: symId, sym: nil)

  for it in mitems(g.packed[module].fromDisk.methods):
    let sym = loadSymFromId(g.config, g.cache, g.packed, module,
                            PackedItemId(module: LitId(0), item: it))
    methodDef(g, g.idgen, sym)

  when false:
    # not used anymore:
    for it in mitems(g.packed[module].fromDisk.compilerProcs):
      let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it[1]))
      g.lazyCompilerprocs[g.packed[module].fromDisk.sh.strings[it[0]]] = symId

  for it in mitems(g.packed[module].fromDisk.converters):
    let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it))
    g.ifaces[module].converters.add LazySym(id: symId, sym: nil)

  for it in mitems(g.packed[module].fromDisk.trmacros):
    let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it))
    g.ifaces[module].patterns.add LazySym(id: symId, sym: nil)

  for it in mitems(g.packed[module].fromDisk.pureEnums):
    let symId = FullId(module: module, packed: PackedItemId(module: LitId(0), item: it))
    g.ifaces[module].pureEnums.add LazySym(id: symId, sym: nil)
