// See LICENSE for license details.

package firrtlTests

import firrtl._

import java.io._

import scala.sys.process._
import org.scalatest._
import org.scalatest.prop._
import org.scalatest.Assertions._
import org.scalacheck._

class PrintfSpec extends FirrtlPropSpec {

  property("Printf should correctly print values in each format %x, %d, %b") {
    val prefix = "Printf"
    val testDir = compileFirrtlTest(prefix, "/features")
    val harness = new File(testDir, s"top.cpp")
    copyResourceToFile(cppHarness.toString, harness)

    verilogToCpp(prefix, testDir, Seq(), harness).!
    cppToExe(prefix, testDir).!

    // Check for correct Printf:
    // Count up from 0, match decimal, hex, and binary
    // see /features/Print.fir to see what we're matching
    val regex = """\tcount\s+=\s+(\d+)\s+0x(\w+)\s+b([01]+).*""".r
    var done = false
    var expected = 0
    var error = false
    val ret = Process(s"./V${prefix}", testDir) !
      ProcessLogger( line => {
        line match {
          case regex(dec, hex, bin) => {
            if (!done) {
              // Must mark error before assertion or sbt test will pass
              if (Integer.parseInt(dec, 10) != expected) error = true
              assert(Integer.parseInt(dec, 10) == expected)
              if (Integer.parseInt(hex, 16) != expected) error = true
              assert(Integer.parseInt(hex, 16) == expected)
              if (Integer.parseInt(bin, 2) != expected) error = true
              assert(Integer.parseInt(bin, 2) == expected)
              expected += 1
            }
          }
          case _ => // Do Nothing
        }
      })
    if (error) fail()
  }
}

class StringSpec extends FirrtlPropSpec {

  // Whitelist is [0x20 - 0x7e]
  val whitelist = 
    """ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ""" +
    """[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"""
  val whitelistBA: Array[Byte] = Array.range(0x20, 0x7e) map (_.toByte)

  property(s"Character whitelist should be supported: [$whitelist] ") {
    val lit = firrtl.FIRRTLStringLitHandler.unescape(whitelist)
    // Check internal
    lit.array zip whitelistBA foreach { case (b, e) =>
      assert(b == e, s"(${b.toChar} did not equal expected ${e.toChar})")
    }
    // Check result
    assert(lit.serialize == whitelist)
  }

  // Valid escapes = \n, \t, \\, \", \'
  val esc = """\\\'\"\t\n"""
  val validEsc = Seq('n', 't', '\\', '"', '\'')
  property(s"Escape characters [$esc] should parse") {
    val lit = firrtl.FIRRTLStringLitHandler.unescape(esc)
    assert(lit.array(0) == 0x5c) 
    assert(lit.array(1) == 0x27)
    assert(lit.array(2) == 0x22)
    assert(lit.array(3) == 0x09)
    assert(lit.array(4) == 0x0a)
    assert(lit.array.length == 5)
  }

  // Generators for random testing
  val validChar = Gen.oneOf((0x20.toChar to 0x5b.toChar).toSeq ++
                             (0x5e.toChar to 0x7e.toChar).toSeq) // exclude '\\'
  val validCharSeq = Gen.containerOf[Seq, Char](validChar)
  val invalidChar = Gen.oneOf(Gen.choose(0x00.toChar, 0x1f.toChar),
                             Gen.choose(0x7f.toChar, 0xff.toChar))
  val invalidEsc = Gen.oneOf((0x00.toChar to 0xff.toChar).toSeq diff validEsc)

  property("Random invalid strings should fail") {
    forAll(validCharSeq, invalidChar, validCharSeq) { 
      (head: Seq[Char], bad: Char, tail: Seq[Char]) =>
        val str = ((head :+ bad) ++ tail).mkString
        intercept[InvalidStringLitException] {
          firrtl.FIRRTLStringLitHandler.unescape(str)
        }
    }
  }
    
  property(s"Invalid escape characters should fail") {
    forAll(validCharSeq, invalidEsc, validCharSeq) {
      (head: Seq[Char], badEsc: Char, tail: Seq[Char]) =>
        val str = (head ++ Seq('\\', badEsc) ++ tail).mkString
        intercept[InvalidEscapeCharException] {
          firrtl.FIRRTLStringLitHandler.unescape(str)
        }
    }
  }
}
