//
// Copyright (c) 2015, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   02 Jul 15  Matthew Giannini  Creation
//

using compiler

class SourceMap
{

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

  new make(JsCompilerSupport support)
  {
    this.support = support
    this.c = support.compiler
  }

//////////////////////////////////////////////////////////////////////////
// SourceMap
//////////////////////////////////////////////////////////////////////////

  This add(Str text, Loc genLoc, Loc srcLoc, Str? name := null)
  {
    // map source
    File? source := files.getOrAdd(srcLoc.file) |->File?| { findSource(srcLoc) }
    if (source == null) return this

    // add map field
    fields.add(MapField(text, genLoc, srcLoc, name))
    return this
  }

  private File? findSource(Loc loc)
  {
    c.srcFiles?.find { it.osPath == File.os(loc.file).osPath }
  }

//////////////////////////////////////////////////////////////////////////
// Output
//////////////////////////////////////////////////////////////////////////

  Void write(OutStream out := Env.cur.out)
  {
    pod := support.pod.name
    out.writeChars("{\n")
    out.writeChars("\"version\": 3,\n")
    out.writeChars("\"file\": \"${pod}.js\",\n")
    out.writeChars("\"sourceRoot\": \"/dev/${pod}/\",\n")
    writeSources(out)
    writeMappings(out)
    out.writeChars("}\n")
    out.flush
  }

  private Void writeSources(OutStream out)
  {
    // write sources
    out.writeChars("\"sources\": [")
    files.vals.each |file, i|
    {
      if (i > 0) out.writeChars(",")
      if (file == null) out.writeChars("null")
      else out.writeChars("\"${file.name}\"")
    }
    out.writeChars("],\n")

  }

  private Void writeMappings(OutStream out)
  {
    // map source index
    srcIdx := [Str:Int][:]
    files.keys.each |k, i| { srcIdx[k] = i }

    out.writeChars("\"mappings\": \"")
    prevFileIdx := 0
    prevSrcLine := 0
    prevSrcCol  := 0
    prevGenLine := 0
    prevGenCol  := 0
    MapField? prevField
    fields.each |MapField f, Int i|
    {
      fileIdx := srcIdx[f.srcLoc.file]
      genLine := f.genLoc.line
      genCol  := f.genLoc.col
      srcLine := f.srcLine
      srcCol  := f.srcCol
      if (genLine < prevGenLine) throw Err("${f} is before line ${prevGenLine}")

      // handle missing/blank lines
      if (genLine != prevGenLine)
      {
        prevGenCol = 0
        while (genLine != prevGenLine)
        {
          out.writeChar(';')
          ++prevGenLine
        }
      }
      else
      {
        if (i > 0)
        {
          if (genCol <= prevGenCol) throw Err("${genCol} is before col ${prevGenCol}")
          out.writeChar(',')
        }
      }

      // calculate diffs
      genColDiff  := genCol - prevGenCol
      fileDiff    := fileIdx - prevFileIdx
      srcLineDiff := srcLine - prevSrcLine
      srcColDiff  := srcCol - prevSrcCol

      // write segment field
      out.writeChars(Base64VLQ.encode(genColDiff))
         .writeChars(Base64VLQ.encode(fileDiff))
         .writeChars(Base64VLQ.encode(srcLineDiff))
         .writeChars(Base64VLQ.encode(srcColDiff))

      // update prev state
      prevGenCol  = genCol
      prevFileIdx = fileIdx
      prevSrcLine = srcLine
      prevSrcCol  = srcCol
    }
    out.writeChars(";\"\n")
  }

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

  private JsCompilerSupport support
  private Compiler c
  private [Str:File?] files := [Str:File][:] { ordered = true }
  private MapField[] fields := [,]
}

class MapField
{
  new make(Str text, Loc genLoc, Loc srcLoc, Str? name)
  {
    this.text = text
    this.genLoc = genLoc
    this.srcLoc = srcLoc
    this.name = name
  }

  ** zero-indexed line from original source file
  Int srcLine() { srcLoc.line - 1 }
  ** zero-indexed column from original source file
  Int srcCol() { srcLoc.col - 1 }

  override Str toStr()
  {
    "([${fname}, ${srcLine}, ${srcCol}], [${genLoc.line}, ${genLoc.col}], ${name}, ${text})"
  }

  Str fname()
  {
    i := srcLoc.file.indexr("/")
    return srcLoc.file[i+1..-1]
  }

  Str text
  Loc genLoc
  Loc srcLoc
  Str? name
}