//
// Copyright (c) 2024, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   30 Oct 2024  Matthew Giannini  Creation
//

**
** Runs all the tests from the common mark specification
**
** The spec.json was generated from the [commonmark-spec]`https://github.com/commonmark/commonmark-spec`
** repo using this command:
** pre>
** python test/spec_tests.py --dump-tests > spec.json
** <pre
**
** The commonmark-java implementation actually does a simple parsing of the spec.md
** file to extract the examples, but we can't do that because of fantom unicode issues.
** Also, it would require a bit more code to do that and this prcoess is not so bad.
**
abstract class CommonMarkSpecTest : Test
{
  private const File specFile := Env.cur.homeDir + `etc/markdown/tests/spec.json`
  protected [Str:Example[]] bySection := [:] { ordered = true }
  protected Example[] examples() { bySection.vals.flatten }

  ** These all fail because of unicode issues in fantom
  protected virtual Int[] expectedFailures() { [208, 356, 542] }

  protected virtual Example[] examplesToRun() { examples }

  protected abstract ExampleRes run(Example example)

  Void test()
  {
    if (!init) return

    results := ExampleRes[,]
    examplesToRun.each |example| { results.add(run(example)) }

    failedCount := 0
    results.each |result|
    {
      if (result.failed)
      {
        ex := result.example

        // ignore expected failures
        if (expectedFailures.contains(ex.id)) return

        ++failedCount
        echo("""${ex.section}: ${ex.id}
                === Markdown
                ${ex.markdown}
                === Expected
                ${ex.html}
                === Actual
                ${result.rendered}
                ===
                ${result.err.traceToStr}""")
      }
    }
    echo("${results.size-failedCount}/${results.size} tests passed.")
    if (failedCount > 0) fail("CommonMark spec tests did not pass.")
  }

  protected Bool init()
  {
    if (!specFile.exists)
    {
      echo(Str<|
                ************ WARNING ***********

                Common Mark spec.json not found.
                Skipping tests.

                ********************************
                |>)
      return false
    }

    // use reflection to load test examples
    in := specFile.in
    try
    {
      Map[] arr := Type.find("util::JsonInStream").make([specFile.in])->readJson
      arr.each |json|
      {
        example := Example(json)
        bySection.getOrAdd(example.section) |Str section->Example[]| { Example[,] }.add(example)
      }
    }
    finally in.close
    return true
  }
}

class HtmlCoreSpecTest : CommonMarkSpecTest
{
  private Parser parser := Parser()

  ** the spec says URL-escaping is optional, but the examples assume it's enabled
  private HtmlRenderer renderer := HtmlRenderer.builder.withPercentEncodeUrls.build

  protected override ExampleRes run(Example example)
  {
    Str? r
    try
    {
      doc := parser.parse(example.markdown)
      // Node.tree(doc)
      r = renderer.render(doc)
      verifyEq(example.html, r)
      return ExampleRes(example)
    }
    catch (Err err)
    {
      return ExampleRes(example, err, r)
    }
  }
}

@NoDoc const class ExampleRes
{
  new makeOk(Example example) : this.make(example, null, null) { }
  new make(Example example, Err? err, Str? rendered)
  {
    this.example = example
    this.err = err
    this.rendered = rendered
  }

  const Example example
  const Err? err
  const Str? rendered
  Bool failed() { err != null }
}

@NoDoc const class Example
{
  new make(Map json)
  {
    this.json = json
  }

  const Map json
  Str markdown() { json["markdown"] }
  Str html() { json["html"] }
  Int id() { json["example"] }
  Str section() { json["section"] }

  override Str toStr() { "$id" }
}