// See LICENSE for license details.

package firrtlTests

import java.io._
import org.scalatest._
import org.scalatest.prop._
import firrtl._
import firrtl.annotations._
import firrtl.ir.Circuit
import firrtl.passes._
import firrtl.Parser.IgnoreInfo

class InoutVerilog extends FirrtlFlatSpec {
  private def executeTest(input: String, expected: Seq[String], compiler: Compiler) = {
    val writer = new StringWriter()
    compiler.compile(CircuitState(parse(input), ChirrtlForm), writer)
    val lines = writer.toString().split("\n") map normalized
    expected foreach { e =>
      lines should contain(e)
    }
  }
  "Circuit" should "attach a module input source" in {
    val compiler = new VerilogCompiler
    val input =
      """circuit Attaching : 
         |  module Attaching : 
         |    input an: Analog<3>
         |    inst a of A
         |    inst b of B
         |    attach an to (a.an1, b.an2)
         |  module A: 
         |    input an1: Analog<3>
         |  module B:
         |    input an2: Analog<3> """.stripMargin
     val check = 
      """module Attaching(
        |  inout  [2:0] an
        |);
        |  A a (
        |    .an1(an)
        |  );
        |  B b (
        |    .an2(an)
        |  );
        |endmodule
        |module A(
        |  inout  [2:0] an1
        |);
        |endmodule
        |module B(
        |  inout  [2:0] an2
        |);
        |endmodule
        |""".stripMargin.split("\n") map normalized
     executeTest(input, check, compiler)
   }

  "Circuit" should "attach a module output source" in {
    val compiler = new VerilogCompiler
    val input =
      """circuit Attaching : 
         |  module Attaching : 
         |    output an: Analog<3>
         |    inst a of A
         |    inst b of B
         |    attach an to (a.an1, b.an2)
         |  module A: 
         |    input an1: Analog<3>
         |  module B:
         |    input an2: Analog<3> """.stripMargin
     val check = 
      """module Attaching(
        |  inout  [2:0] an
        |);
        |  A a (
        |    .an1(an)
        |  );
        |  B b (
        |    .an2(an)
        |  );
        |endmodule
        |module A(
        |  inout  [2:0] an1
        |);
        |endmodule
        |module B(
        |  inout  [2:0] an2
        |);
        |endmodule
        |""".stripMargin.split("\n") map normalized
     executeTest(input, check, compiler)
   }

  "Circuit" should "not attach an instance input source" in {
    val compiler = new VerilogCompiler
    val input =
      """circuit Attaching : 
         |  module Attaching : 
         |    inst a of A
         |    inst b of B
         |    attach a.an to (b.an)
         |  module A: 
         |    input an: Analog<3>
         |  module B:
         |    input an: Analog<3> """.stripMargin
     intercept[CheckTypes.IllegalAttachSource] {
       executeTest(input, Seq.empty, compiler)
     }
   }

  "Circuit" should "attach an instance output source" in {
    val compiler = new VerilogCompiler
    val input =
      """circuit Attaching : 
         |  module Attaching : 
         |    inst a of A
         |    inst b of B
         |    attach b.an to (a.an)
         |  module A: 
         |    input an: Analog<3>
         |  module B:
         |    input an: Analog<3> """.stripMargin
    intercept[CheckTypes.IllegalAttachSource] {
      executeTest(input, Seq.empty, compiler)
    }
  }

  "Circuit" should "attach a wire source" in {
    val compiler = new VerilogCompiler
    val input =
      """circuit Attaching : 
         |  module Attaching : 
         |    wire x: Analog
         |    inst a of A
         |    attach x to (a.an)
         |  module A: 
         |    input an: Analog<3> """.stripMargin
    val check = 
      """module Attaching(
        |);
        |  wire [2:0] x;
        |  A a (
        |    .an(x)
        |  );
        |endmodule
        |""".stripMargin.split("\n") map normalized
    executeTest(input, check, compiler)
  }
}

class AttachAnalogSpec extends FirrtlFlatSpec {
  private def executeTest(input: String, expected: Seq[String], passes: Seq[Pass]) = {
    val c = passes.foldLeft(Parser.parse(input.split("\n").toIterator)) {
      (c: Circuit, p: Pass) => p.run(c)
    }
    val lines = c.serialize.split("\n") map normalized

    expected foreach { e =>
      lines should contain(e)
    }
  }

  "Connecting analog types" should "throw an exception" in {
    val passes = Seq(
      ToWorkingIR,
      CheckHighForm,
      ResolveKinds,
      InferTypes,
      CheckTypes)
    val input =
      """circuit Unit :
        |  module Unit :
        |    input y: Analog<1>
        |    output x: Analog<1>
        |    x <= y""".stripMargin
    intercept[CheckTypes.InvalidConnect] {
      passes.foldLeft(parse(input)) {
        (c: Circuit, p: Pass) => p.run(c)
      }
    }
  }

  "Declaring register with analog types" should "throw an exception" in {
    val passes = Seq(
      ToWorkingIR,
      CheckHighForm,
      ResolveKinds,
      InferTypes,
      CheckTypes)
    val input =
      """circuit Unit :
        |  module Unit :
        |    input clk: Clock
        |    reg r: Analog<2>, clk""".stripMargin
    intercept[CheckTypes.IllegalAnalogDeclaration] {
      passes.foldLeft(parse(input)) {
        (c: Circuit, p: Pass) => p.run(c)
      }
    }
  }

  "Declaring memory with analog types" should "throw an exception" in {
    val passes = Seq(
      ToWorkingIR,
      CheckHighForm,
      ResolveKinds,
      InferTypes,
      CheckTypes)
    val input =
      """circuit Unit :
        |  module Unit :
        |    input clk: Clock
        |    mem m: 
        |      data-type => Analog<2>
        |      depth => 4
        |      read-latency => 0
        |      write-latency => 1
        |      read-under-write => undefined""".stripMargin
    intercept[CheckTypes.IllegalAnalogDeclaration] {
      passes.foldLeft(parse(input)) {
        (c: Circuit, p: Pass) => p.run(c)
      }
    }
  }

  "Declaring node with analog types" should "throw an exception" in {
    val passes = Seq(
      ToWorkingIR,
      CheckHighForm,
      ResolveKinds,
      InferTypes,
      CheckTypes)
    val input =
      """circuit Unit :
        |  module Unit :
        |    input in: Analog<2>
        |    node n = in """.stripMargin
    intercept[CheckTypes.IllegalAnalogDeclaration] {
      passes.foldLeft(parse(input)) {
        (c: Circuit, p: Pass) => p.run(c)
      }
    }
  }

  "Attaching a non-analog source" should "not be ok" in {
    val passes = Seq(
      ToWorkingIR,
      CheckHighForm,
      ResolveKinds,
      InferTypes,
      CheckTypes)
    val input =
      """circuit Unit :
        |  module Unit :
        |    input source: UInt<2>
        |    inst a of A
        |    inst b of B
        |    attach source to (a.o, b.o)
        |  extmodule A :
        |    output o: Analog<2>
        |  extmodule B:
        |    input o: Analog<2>""".stripMargin
    intercept[CheckTypes.IllegalAttachSource] {
      passes.foldLeft(parse(input)) {
        (c: Circuit, p: Pass) => p.run(c)
      }
    }
  }

  "Attach instance analog male source" should "not be ok." in {
    val passes = Seq(
      ToWorkingIR,
      CheckHighForm,
      ResolveKinds,
      InferTypes,
      CheckTypes)
    val input =
      """circuit Unit :
        |  module Unit :
        |    inst a of A
        |    inst b of B
        |    attach a.o to (b.o)
        |  extmodule A :
        |    output o: Analog<2>
        |  extmodule B:
        |    input o: Analog<2>""".stripMargin
    intercept[CheckTypes.IllegalAttachSource] {
      passes.foldLeft(parse(input)) {
        (c: Circuit, p: Pass) => p.run(c)
      }
    }
  }

  "Attach instance analog female source" should "not be ok." in {
    val passes = Seq(
      ToWorkingIR,
      CheckHighForm,
      ResolveKinds,
      InferTypes,
      CheckTypes)
    val input =
      """circuit Unit :
        |  module Unit :
        |    inst a of A
        |    inst b of B
        |    attach b.o to (a.o)
        |  extmodule A :
        |    output o: Analog<2>
        |  extmodule B:
        |    input o: Analog<2>""".stripMargin
    intercept[CheckTypes.IllegalAttachSource] {
      passes.foldLeft(parse(input)) {
        (c: Circuit, p: Pass) => p.run(c)
      }
    }
  }

  "Attach port analog expr" should "throw an exception" in {
    val passes = Seq(
      ToWorkingIR,
      CheckHighForm,
      ResolveKinds,
      InferTypes,
      CheckTypes)
    val input =
      """circuit Unit :
        |  module Unit :
        |    input i: Analog<2>
        |    input j: Analog<2>
        |    attach j to (i) """.stripMargin
    intercept[CheckTypes.IllegalAttachExp] {
      passes.foldLeft(parse(input)) {
        (c: Circuit, p: Pass) => p.run(c)
      }
    }
  }

  "Inequal attach widths" should "throw an exception" in {
    val passes = Seq(
      ToWorkingIR,
      CheckHighForm,
      ResolveKinds,
      InferTypes,
      CheckTypes,
      InferWidths,
      CheckWidths)
    val input =
      """circuit Unit :
        |  module Unit :
        |    input i: Analog<3>
        |    inst a of A
        |    attach i to (a.o) 
        |  extmodule A :
        |    output o: Analog<2> """.stripMargin
    intercept[CheckWidths.AttachWidthsNotEqual] {
      passes.foldLeft(parse(input)) {
        (c: Circuit, p: Pass) => p.run(c)
      }
    }
  }

  //"Simple compound expressions" should "be split" in {
  //  val passes = Seq(
  //    ToWorkingIR,
  //    ResolveKinds,
  //    InferTypes,
  //    ResolveGenders,
  //    InferWidths,
  //    SplitExpressions
  //  )
  //  val input =
  //    """circuit Top :
  //       |  module Top :
  //       |    input a : UInt<32>
  //       |    input b : UInt<32>
  //       |    input d : UInt<32>
  //       |    output c : UInt<1>
  //       |    c <= geq(add(a, b),d)""".stripMargin
  //  val check = Seq(
  //    "node GEN_0 = add(a, b)",
  //    "c <= geq(GEN_0, d)"
  //  )
  //  executeTest(input, check, passes)
  //}
}
