//
// Copyright (c) 2010, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//  31 Jan 10  Brian Frank  Creation
//

using concurrent

**
** PathEnv is a simple implementation of a Fantom
** environment which uses a search path to resolve files.
**
const class PathEnv : Env
{

  **
  ** Constructor initializes the search path using the
  ** 'FAN_ENV_PATH' environment variable (see `sys::Env.vars`).
  **
  new make() : super(Env.cur)
  {
    vars := super.vars
    path := parsePath(null, vars["FAN_ENV_PATH"] ?: "") |msg, err|
    {
      log.warn("Parsing FAN_ENV_PATH: $msg", err)
    }

    this.vars = vars
    this.pathRef = AtomicRef(path.toImmutable)
  }

  **
  ** Prototype to explore using directory structure to setup
  ** path by placing a "fan.props" in working directory.
  **
  @NoDoc new makeProps(File file) : super.make(Env.cur)
  {
    props := file.readProps

    vars := super.vars.rw
    props.each |v, n|
    {
      if (n.startsWith("env.") && n.size > 5) vars[n[4..-1]] = v
    }

    workDir := file.parent
    path := parsePath(workDir, props["path"] ?: "") |msg, err|
    {
      log.warn("Parsing $file.osPath: $msg", err)
    }

    this.vars = vars
    this.pathRef = AtomicRef(path.toImmutable)
  }

  **
  ** Parse path string from env var or props file.
  ** Always put given workDir first and boot homeDir last
  **
  @NoDoc static File[] parsePath(File? workDir, Str path, |Str, Err?| onWarn)
  {
    // add workDir first
    acc := File[,]
    acc.addNotNull(workDir.normalize)

    // parse path
    try
    {
      path.split(';').each |item|
      {
        if (item.isEmpty) return
        dir := (item.startsWith("..") && workDir != null)
          ? File.os("$workDir.osPath/$item").normalize
          : File.os(item).normalize
        if (!dir.exists) dir = File(item.toUri.plusSlash, false).normalize
        if (!dir.exists) { onWarn("Dir not found: $dir", null); return }
        if (!dir.isDir) { onWarn("Not a dir: $dir", null); return }
        doAdd(acc, dir)
      }
    }
    catch (Err e) onWarn("Cannot parse path: $path", e)

    // ensure homeDir is last
    acc.remove(Env.cur.homeDir)
    acc.add(Env.cur.homeDir)
    return acc
  }

  **
  ** Search path of directories in priority order.  The
  ** last item in the path is always the `sys::Env.homeDir`
  **
  override File[] path() { pathRef.val }
  private const AtomicRef pathRef

  **
  ** Working directory is always first item in `path`.
  **
  override File workDir() { path.first }

  **
  ** Temp directory is always under `workDir`.
  **
  override File tempDir() { workDir + `temp/` }

  **
  ** Add given directory to the front of the path which
  ** will update both the workDir and tempDir
  **
  @NoDoc Void addToPath(File dir)
  {
    dir = dir.normalize
    pathRef.val = doAdd(path.dup, dir, 0).toImmutable
  }

  **
  ** Add a directory to the path only if its not already mapped
  **
  private static File[] doAdd(File[] path, File dir, Int insertIndex := -1)
  {
    if (!path.contains(dir))
    {
      if (insertIndex < 0) path.add(dir)
      else path.insert(insertIndex, dir)
    }
    return path
  }

  **
  ** Get the environment variables as a case insensitive, immutable
  ** map of Str name/value pairs.  The environment map is initialized
  ** from the following sources from lowest priority to highest priority:
  **   1. shell environment variables
  **   2. Java system properties (Java VM only obviously)
  **   3. props in "fan.props" prefixed with "env."
  **
  override const Str:Str vars

  **
  ** Search `path` for given file.
  **
  override File? findFile(Uri uri, Bool checked := true)
  {
    if (uri.isPathAbs) throw ArgErr("Uri must be rel: $uri")
    result := path.eachWhile |dir|
    {
      f := dir.plus(uri, false)
      return f.exists ? f : null
    }
    if (result != null) return result
    if (checked) throw UnresolvedErr(uri.toStr)
    return null
  }

  **
  ** Search `path` for all versions of given file.
  **
  override File[] findAllFiles(Uri uri)
  {
    acc := File[,]
    path.each |dir|
    {
      f := dir + uri
      if (f.exists) acc.add(f)
    }
    return acc
  }

  **
  ** Search `path` for all "lib/fan/*.pod" files.
  **
  override Str[] findAllPodNames()
  {
    acc := Str:Str[:]
    path.each |dir|
    {
      lib := dir + `lib/fan/`
      lib.list.each |f|
      {
        if (f.isDir || f.ext != "pod") return
        podName := f.basename
        acc[podName] = podName
      }
    }
    return acc.keys
  }

  private const Log log := Log.get("pathenv")
}