//
// Copyright (c) 2006, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   5 Jun 06  Brian Frank  Creation
//

**
** InitInput is responsible:
**   - verifies the CompilerInput instance
**   - checks the depends dir
**   - constructs the appropiate CNamespace
**   - initializes Comiler.pod with a PodDef
**   - tokenizes the source code from file or string input
**
class InitInput : CompilerStep
{

//////////////////////////////////////////////////////////////////////////
// Construction
//////////////////////////////////////////////////////////////////////////

  **
  ** Constructor takes the associated Compiler
  **
  new make(Compiler compiler)
    : super(compiler)
  {
    loc = compiler.input.inputLoc
    input = compiler.input
  }

//////////////////////////////////////////////////////////////////////////
// Run
//////////////////////////////////////////////////////////////////////////

  **
  ** Run the step
  **
  override Void run()
  {
    validateInput
    validatePodName
    initNamespace
    initPod
    initDepends
    initFiles
  }

//////////////////////////////////////////////////////////////////////////
// Validate Input
//////////////////////////////////////////////////////////////////////////

  **
  ** Validate that all the required input fields are set.
  **
  private Void validateInput()
  {
    try
      input.validate
    catch (CompilerErr err)
      throw errReport(err)
  }

//////////////////////////////////////////////////////////////////////////
// Validate pod name
//////////////////////////////////////////////////////////////////////////

  **
  ** Verify that pod name is valid
  **
  private Void validatePodName()
  {
    msg := isValidPodName(input.podName)
    if (msg != null) throw err(msg, input.inputLoc)
  }

  **
  ** Return 'null' if pod name is valid, or return a
  ** descriptive error message if name is not valid.
  **
  @NoDoc static Str? isValidPodName(Str name)
  {
    if (name.isEmpty) return "Pod name is empty"
    if (!name[0].isAlpha) return "Pod name must begin with alpha char"
    for (i:=0; i<name.size; i++)
    {
      ch := name[i]
      if (!ch.isAlphaNum && ch != '_')
        return "Pod name contains invalid char '$ch.toChar'"
    }
    return null
  }

//////////////////////////////////////////////////////////////////////////
// Init Namespace
//////////////////////////////////////////////////////////////////////////

  **
  ** Init the compiler.ns with an appropriate CNamespace
  **
  private Void initNamespace()
  {
    compiler.ns = input.ns
    input.ns.c = compiler
  }

//////////////////////////////////////////////////////////////////////////
// Init Pod
//////////////////////////////////////////////////////////////////////////

  **
  ** Init 'compiler.pod' with PodDef
  **
  private Void initPod()
  {
    ts := DateTime.now.floor(1sec)
    meta := Str:Str[:] { ordered = true }
    meta["pod.name"]       = input.podName
    meta["pod.version"]    = input.version.toStr
    meta["pod.depends"]    = input.depends.join(";")
    meta["pod.summary"]    = input.summary
    meta["pod.isScript"]   = input.isScript.toStr
    meta["fcode.version"]  = FConst.FCodeVersion
    meta["build.host"]     = Env.cur.host
    meta["build.user"]     = Env.cur.user
    meta["build.ts"]       = ts.toStr
    meta["build.tsKey"]    = ts.toLocale("YYMMDDhhmmss")
    meta["build.compiler"] = typeof.pod.version.toStr
    meta["build.platform"] = Env.cur.platform
    meta.addAll(input.meta)

    pod := PodDef(ns, input.inputLoc, input.podName)
    pod.meta  = meta
    pod.index = input.index

    compiler.pod = pod
    compiler.isSys = pod.name == "sys"
  }

//////////////////////////////////////////////////////////////////////////
// Init Depends
//////////////////////////////////////////////////////////////////////////

  **
  ** Init the compiler.depends with list of Depends
  **
  private Void initDepends()
  {
    compiler.depends = input.depends.map |d->CDepend| { CDepend(d, null) }
  }

//////////////////////////////////////////////////////////////////////////
// Init Source and Resource Files
//////////////////////////////////////////////////////////////////////////

  **
  ** Init the compiler's srcFiles and resFiles field (file mode only)
  **
  private Void initFiles()
  {
    if (input.mode !== CompilerInputMode.file) return

    // map pod facets to src/res files
    compiler.srcFiles     = findFiles(input.srcFiles, "fan")
    compiler.resFiles     = findFiles(input.resFiles, null)
    compiler.jsFiles      = findFiles(input.jsFiles,  "js")
    compiler.jsPropsFiles = findFiles(input.jsPropsFiles, "props")

    if (compiler.srcFiles.isEmpty && compiler.resFiles.isEmpty)
      throw err("No fan source files found", input.inputLoc)

    // map sure no duplicate names in srcFiles
    map := Str:File[:]
    compiler.srcFiles.each |file|
    {
      if (map[file.name] != null)
        throw err("Cannot have source files with duplicate names: $file.name", Loc.makeFile(file))
      map[file.name] = file
    }

    log.info("FindSourceFiles [${compiler.srcFiles.size} files]")
  }

  private File[] findFiles(Uri[]? uris, Str? ext)
  {
    base := input.baseDir
    acc := File[,]
    uris?.each |uri|
    {
      f := base + uri
      if (!f.exists) throw err("Invalid file or directory", Loc.makeFile(f))
      if (f.isDir)
      {
        f.list.each |kid|
        {
          if (kid.isDir) return
          if (ext == null || kid.ext == ext) acc.add(kid)
        }
      }
      else
      {
        acc.add(f)
      }
    }
    return acc
  }

//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////

  private Loc loc                // ctor
  private CompilerInput input    // ctor

}