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

import ast, renderer, msgs, options, idents, lineinfos,
  pathutils

import std/[strutils, os]

proc getModuleName*(conf: ConfigRef; n: PNode): string =
  # This returns a short relative module name without the nim extension
  # e.g. like "system", "importer" or "somepath/module"
  # The proc won't perform any checks that the path is actually valid
  case n.kind
  of nkStrLit, nkRStrLit, nkTripleStrLit:
    try:
      result = pathSubs(conf, n.strVal, toFullPath(conf, n.info).splitFile().dir)
    except ValueError:
      localError(conf, n.info, "invalid path: " & n.strVal)
      result = n.strVal
  of nkIdent:
    result = n.ident.s
  of nkSym:
    result = n.sym.name.s
  of nkInfix:
    let n0 = n[0]
    let n1 = n[1]
    when false:
      if n1.kind == nkPrefix and n1[0].kind == nkIdent and n1[0].ident.s == "$":
        if n0.kind == nkIdent and n0.ident.s == "/":
          result = lookupPackage(n1[1], n[2])
        else:
          localError(n.info, "only '/' supported with $package notation")
          result = ""
    else:
      if n0.kind in nkIdentKinds:
        let ident = n0.getPIdent
        if ident != nil and ident.s[0] == '/':
          let modname = getModuleName(conf, n[2])
          # hacky way to implement 'x / y /../ z':
          result = getModuleName(conf, n1)
          result.add renderTree(n0, {renderNoComments}).replace(" ")
          result.add modname
        else:
          result = ""
      else:
        result = ""
  of nkPrefix:
    when false:
      if n[0].kind == nkIdent and n[0].ident.s == "$":
        result = lookupPackage(n[1], nil)
      else:
        discard
    # hacky way to implement 'x / y /../ z':
    result = renderTree(n, {renderNoComments}).replace(" ")
  of nkDotExpr:
    localError(conf, n.info, warnDeprecated, "using '.' instead of '/' in import paths is deprecated")
    result = renderTree(n, {renderNoComments}).replace(".", "/")
  of nkImportAs:
    result = getModuleName(conf, n[0])
  else:
    localError(conf, n.info, "invalid module name: '$1'" % n.renderTree)
    result = ""

proc checkModuleName*(conf: ConfigRef; n: PNode; doLocalError=true): FileIndex =
  # This returns the full canonical path for a given module import
  let modulename = getModuleName(conf, n)
  let fullPath = findModule(conf, modulename, toFullPath(conf, n.info))
  if fullPath.isEmpty:
    if doLocalError:
      let m = if modulename.len > 0: modulename else: $n
      localError(conf, n.info, "cannot open file: " & m)
    result = InvalidFileIdx
  else:
    result = fileInfoIdx(conf, fullPath)

type
  SelectedBase = enum
    FromProject, FromSearchPath, FromNimblePath

proc mangleModuleName*(conf: ConfigRef; path: AbsoluteFile): string =
  ## Mangle a relative module path to avoid path and symbol collisions.
  ##
  ## Used by backends that need to generate intermediary files from Nim modules.
  ## This is needed because the compiler uses a flat cache file hierarchy.
  ##
  ## Example:
  ## `foo-#head/../bar` becomes `@foo-@hhead@s..@sbar`
  var best = relativeTo(path, conf.projectPath).string
  var selectedBase = FromProject
  for x in conf.searchPaths:
    let other = relativeTo(path, x).string
    if other.len < best.len:
      best = other
      selectedBase = FromSearchPath
  for x in conf.nimblePaths:
    let other = relativeTo(path, x).string
    if other.len < best.len:
      best = other
      selectedBase = FromNimblePath
  let prefix =
    case selectedBase
    of FromProject: "@m"
    of FromSearchPath: "@p"
    of FromNimblePath: "@n"

  prefix & best.multiReplace(
    {$os.DirSep: "@s", $os.AltSep: "@s", "#": "@h", "@": "@@", ":": "@c"})

proc demangleModuleName*(path: string): string =
  ## Demangle a relative module path.
  result = path.multiReplace({"@@": "@", "@h": "#", "@s": "/", "@m": "", "@p": "", "@n": "", "@c": ":"})
