//
// Copyright (c) 2006, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   3 Sep 05  Brian Frank  Creation
//   7 Oct 06  Brian Frank  Port from Java to Fan
//

**
** WritePod writes the FPod to a zip file.
**
class WritePod : CompilerStep
{

//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////

  new make(Compiler compiler)
    : super(compiler)
  {
  }

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

  **
  ** Not used, use write instead
  **
  override Void run() { throw UnsupportedErr.make }

  **
  ** Run the step and return pod file written
  **
  File write()
  {
    dir  := compiler.input.outDir
    fpod := compiler.fpod
    podName := fpod.name
    podFile := dir + "${podName}.pod".toUri
    loc = Loc.makeFile(podFile)

    log.info("WritePod [${podFile.toStr}]")

    // create output directory
    dir.create

    Zip? zip := null
    try
    {
      // open zip store
      zip = Zip.write(podFile.out)

      // write fpod data structures into zip file
      fpod.write(zip)

      // write javascript
      if (compiler.js != null)
      {
        writeStr(zip, `${podName}.js`, compiler.js)
        if (compiler.jsSourceMap != null)
        {
          writeStr(zip, `${podName}.js.map`, compiler.jsSourceMap)
        }
      }

      // write javascript cjs
      if (compiler.cjs != null)
      {
        writeStr(zip, `js/${podName}.js`, compiler.cjs)
        if (compiler.cjsSourceMap != null)
          writeStr(zip, `js/${podName}.js.map`, compiler.cjsSourceMap)
      }

      // write javascript esm
      if (compiler.esm != null)
      {
        writeStr(zip, `js/${podName}.mjs`, compiler.esm)
        // write <pod>.d.ts
        if (compiler.tsDecl != null)
          writeStr(zip, `js/${podName}.d.ts`, compiler.tsDecl)
      }

      // if explicit locale props
      if (compiler.localeProps != null)
        writeStr(zip, `locale/en.props`, compiler.localeProps)

      // write resource files
      if (compiler.resFiles != null)
        compiler.resFiles.each |file| { writeRes(zip, file) }

      // if including fandoc write it out too
      if (compiler.input.includeDoc) writeDocs(zip)

      // if including source write it out too
      if (compiler.input.includeSrc)
        compiler.srcFiles.each |file| { writeSrc(zip, file) }
    }
    catch (CompilerErr e)
    {
      throw e
    }
    catch (Err e)
    {
      e.trace
      throw errReport(CompilerErr("Cannot write", loc, e))
    }

    // close file
    if (zip != null) zip.close
    return podFile
  }

//////////////////////////////////////////////////////////////////////////
// JavaScript
//////////////////////////////////////////////////////////////////////////

  private Void writeStr(Zip zip, Uri path, Str content)
  {
    try
      zip.writeNext(path, DateTime.now).print(content).close
    catch (Err e)
      throw errReport(CompilerErr("Cannot write resource '$path'", loc, e))
  }

//////////////////////////////////////////////////////////////////////////
// Resource
//////////////////////////////////////////////////////////////////////////

  private Void writeRes(Zip zip, File file, Uri? path := null)
  {
    input := compiler.input
    if (path == null)
    {
      path = file.uri
      path = path.relTo(input.baseDir.uri)
    }

    // ignore stupid OS X .DS_Store
    if (file.name == ".DS_Store") return

    // if locale/en.props and we have explicit definition
    // from LocaleProps then skip it
    if (path == `locale/en.props` && compiler.localeProps != null)
      return

    // if resource is jar file, then unzip it
    if (file.ext == "jar") { writeResZip(zip, file); return }

    try
    {
      out := zip.writeNext(path, file.modified)
      file.in.pipe(out)
      out.close
    }
    catch (Err e)
    {
      if (!file.isDir)
        throw errReport(CompilerErr("Cannot write resource file '$path': $e", loc, e))
    }
  }

  private Void writeResZip(Zip zip, File resFile)
  {
    // open resource file as a zip file
    Zip? resZip := null
    try
      resZip = Zip.open(resFile)
    catch (Err e)
      errReport(CompilerErr("Cannot open resource file as zip: 'f'", loc, e))

    // process each entry in zip as resource
    try
    {
      resZip.contents.each |c| { writeRes(zip, c, c.uri) }
    }
    finally resZip.close
  }

//////////////////////////////////////////////////////////////////////////
// Source Code
//////////////////////////////////////////////////////////////////////////

  private Void writeSrc(Zip zip, File file)
  {
    writeRes(zip, file, `src/$file.name`)
  }

//////////////////////////////////////////////////////////////////////////
// Doc
//////////////////////////////////////////////////////////////////////////

  private Void writeDocs(Zip zip)
  {
    writePodDocs(zip)
    compiler.types.each |type|
    {
      if (type.isDocumented) writeApiDoc(zip, type)
    }
  }

  **
  ** If there is a *.fandoc as peer to build.fan then
  ** copy it into doc/
  **
  private Void writePodDocs(Zip zip)
  {
    files := compiler.input.baseDir.list.findAll |f| { f.ext == "fandoc" }
    files.each |f|
    {
      writeRes(zip, f, `doc/${f.name}`)
    }
  }

  **
  ** Write the API doc text file used by compilerDoc
  **
  private Void writeApiDoc(Zip zip, TypeDef t)
  {
    try
    {
      out := zip.writeNext("doc/${t.name}.apidoc".toUri)
      ApiDocWriter(out).writeType(t).close
    }
    catch (Err e)
    {
      throw errReport(CompilerErr("Cannot write fandoc '$t.name'", t.loc, e))
    }
  }

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

  private Loc? loc
  private FacetDef[] noFacets := FacetDef[,].ro
}