// SPDX-License-Identifier: Apache-2.0

package firrtlTests.constraint

import firrtl.constraint._
import firrtl.ir.Closed
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

class InequalitySpec extends AnyFlatSpec with Matchers {

  behavior.of("Constraints")

  "IsConstraints" should "reduce properly" in {
    IsMin(Closed(0), Closed(1)) should be(Closed(0))
    IsMin(Closed(-1), Closed(1)) should be(Closed(-1))
    IsMax(Closed(-1), Closed(1)) should be(Closed(1))
    IsNeg(IsMul(Closed(-1), Closed(-2))) should be(Closed(-2))
    val x = IsMin(IsMul(Closed(1), VarCon("a")), Closed(2))
    x.children.toSet should be(IsMin(Closed(2), IsMul(Closed(1), VarCon("a"))).children.toSet)
  }

  "IsAdd" should "reduce properly" in {
    // All constants
    IsAdd(Closed(-1), Closed(1)) should be(Closed(0))

    // Pull Out IsMax
    IsAdd(Closed(1), IsMax(Closed(1), VarCon("a"))) should be(IsMax(Closed(2), IsAdd(VarCon("a"), Closed(1))))
    IsAdd(Closed(1), IsMax(Seq(Closed(1), VarCon("a"), VarCon("b")))) should be(
      IsMax(Seq(Closed(2), IsAdd(VarCon("a"), Closed(1)), IsAdd(VarCon("b"), Closed(1))))
    )

    // Pull Out IsMin
    IsAdd(Closed(1), IsMin(Closed(1), VarCon("a"))) should be(IsMin(Closed(2), IsAdd(VarCon("a"), Closed(1))))
    IsAdd(Closed(1), IsMin(Seq(Closed(1), VarCon("a"), VarCon("b")))) should be(
      IsMin(Seq(Closed(2), IsAdd(VarCon("a"), Closed(1)), IsAdd(VarCon("b"), Closed(1))))
    )

    // Add Zero
    IsAdd(Closed(0), VarCon("a")) should be(VarCon("a"))

    // One argument
    IsAdd(Seq(VarCon("a"))) should be(VarCon("a"))
  }

  "IsMax" should "reduce properly" in {
    // All constants
    IsMax(Closed(-1), Closed(1)) should be(Closed(1))

    // Flatten nested IsMax
    IsMax(Closed(1), IsMax(Closed(1), VarCon("a"))) should be(IsMax(Closed(1), VarCon("a")))
    IsMax(Closed(1), IsMax(Seq(Closed(1), VarCon("a"), VarCon("b")))) should be(
      IsMax(Seq(Closed(1), VarCon("a"), VarCon("b")))
    )

    // Eliminate IsMins if possible
    IsMax(Closed(2), IsMin(Closed(1), VarCon("a"))) should be(Closed(2))
    IsMax(
      Seq(
        Closed(2),
        IsMin(Closed(1), VarCon("a")),
        IsMin(Closed(3), VarCon("b"))
      )
    ) should be(
      IsMax(
        Seq(
          Closed(2),
          IsMin(Closed(3), VarCon("b"))
        )
      )
    )

    // One argument
    IsMax(Seq(VarCon("a"))) should be(VarCon("a"))
    IsMax(Seq(Closed(0))) should be(Closed(0))
    IsMax(Seq(IsMin(VarCon("a"), Closed(0)))) should be(IsMin(VarCon("a"), Closed(0)))
  }

  "IsMin" should "reduce properly" in {
    // All constants
    IsMin(Closed(-1), Closed(1)) should be(Closed(-1))

    // Flatten nested IsMin
    IsMin(Closed(1), IsMin(Closed(1), VarCon("a"))) should be(IsMin(Closed(1), VarCon("a")))
    IsMin(Closed(1), IsMin(Seq(Closed(1), VarCon("a"), VarCon("b")))) should be(
      IsMin(Seq(Closed(1), VarCon("a"), VarCon("b")))
    )

    // Eliminate IsMaxs if possible
    IsMin(Closed(1), IsMax(Closed(2), VarCon("a"))) should be(Closed(1))
    IsMin(
      Seq(
        Closed(2),
        IsMax(Closed(1), VarCon("a")),
        IsMax(Closed(3), VarCon("b"))
      )
    ) should be(
      IsMin(
        Seq(
          Closed(2),
          IsMax(Closed(1), VarCon("a"))
        )
      )
    )

    // One argument
    IsMin(Seq(VarCon("a"))) should be(VarCon("a"))
    IsMin(Seq(Closed(0))) should be(Closed(0))
    IsMin(Seq(IsMax(VarCon("a"), Closed(0)))) should be(IsMax(VarCon("a"), Closed(0)))
  }

  "IsMul" should "reduce properly" in {
    // All constants
    IsMul(Closed(2), Closed(3)) should be(Closed(6))

    // Pull out max, if positive stays max
    IsMul(Closed(2), IsMax(Closed(3), VarCon("a"))) should be(
      IsMax(Closed(6), IsMul(Closed(2), VarCon("a")))
    )

    // Pull out max, if negative is min
    IsMul(Closed(-2), IsMax(Closed(3), VarCon("a"))) should be(
      IsMin(Closed(-6), IsMul(Closed(-2), VarCon("a")))
    )

    // Pull out min, if positive stays min
    IsMul(Closed(2), IsMin(Closed(3), VarCon("a"))) should be(
      IsMin(Closed(6), IsMul(Closed(2), VarCon("a")))
    )

    // Pull out min, if negative is max
    IsMul(Closed(-2), IsMin(Closed(3), VarCon("a"))) should be(
      IsMax(Closed(-6), IsMul(Closed(-2), VarCon("a")))
    )

    // Times zero
    IsMul(Closed(0), VarCon("x")) should be(Closed(0))

    // Times 1
    IsMul(Closed(1), VarCon("x")) should be(VarCon("x"))

    // One argument
    IsMul(Seq(Closed(0))) should be(Closed(0))
    IsMul(Seq(VarCon("a"))) should be(VarCon("a"))

    // No optimizations
    val isMax = IsMax(VarCon("x"), VarCon("y"))
    val isMin = IsMin(VarCon("x"), VarCon("y"))
    val a = VarCon("a")
    IsMul(a, isMax).children should be(Vector(a, isMax)) //non-known multiply
    IsMul(a, isMin).children should be(Vector(a, isMin)) //non-known multiply
    IsMul(Seq(Closed(2), isMin, isMin)).children should be(Vector(Closed(2), isMin, isMin)) //>1 min
    IsMul(Seq(Closed(2), isMax, isMax)).children should be(Vector(Closed(2), isMax, isMax)) //>1 max
    IsMul(Seq(Closed(2), isMin, isMax)).children should be(Vector(Closed(2), isMin, isMax)) //mixed min/max
  }

  "IsNeg" should "reduce properly" in {
    // All constants
    IsNeg(Closed(1)) should be(Closed(-1))
    // Pull out max
    IsNeg(IsMax(Closed(1), VarCon("a"))) should be(IsMin(Closed(-1), IsNeg(VarCon("a"))))
    // Pull out min
    IsNeg(IsMin(Closed(1), VarCon("a"))) should be(IsMax(Closed(-1), IsNeg(VarCon("a"))))
    // Pull out add
    IsNeg(IsAdd(Closed(1), VarCon("a"))) should be(IsAdd(Closed(-1), IsNeg(VarCon("a"))))
    // Pull out mul
    IsNeg(IsMul(Closed(2), VarCon("a"))) should be(IsMul(Closed(-2), VarCon("a")))
    // No optimizations
    // (pow), (floor?)
    IsNeg(IsPow(VarCon("x"))).children should be(Vector(IsPow(VarCon("x"))))
    IsNeg(IsFloor(VarCon("x"))).children should be(Vector(IsFloor(VarCon("x"))))
  }

  "IsPow" should "reduce properly" in {
    // All constants
    IsPow(Closed(1)) should be(Closed(2))
    // Pull out max
    IsPow(IsMax(Closed(1), VarCon("a"))) should be(IsMax(Closed(2), IsPow(VarCon("a"))))
    // Pull out min
    IsPow(IsMin(Closed(1), VarCon("a"))) should be(IsMin(Closed(2), IsPow(VarCon("a"))))
    // Pull out add
    IsPow(IsAdd(Closed(1), VarCon("a"))) should be(IsMul(Closed(2), IsPow(VarCon("a"))))
    // No optimizations
    // (mul), (pow), (floor?)
    IsPow(IsMul(Closed(2), VarCon("x"))).children should be(Vector(IsMul(Closed(2), VarCon("x"))))
    IsPow(IsPow(VarCon("x"))).children should be(Vector(IsPow(VarCon("x"))))
    IsPow(IsFloor(VarCon("x"))).children should be(Vector(IsFloor(VarCon("x"))))
  }

  "IsFloor" should "reduce properly" in {
    // All constants
    IsFloor(Closed(1.9)) should be(Closed(1))
    IsFloor(Closed(-1.9)) should be(Closed(-2))
    // Pull out max
    IsFloor(IsMax(Closed(1.9), VarCon("a"))) should be(IsMax(Closed(1), IsFloor(VarCon("a"))))
    // Pull out min
    IsFloor(IsMin(Closed(1.9), VarCon("a"))) should be(IsMin(Closed(1), IsFloor(VarCon("a"))))
    // Cancel with another floor
    IsFloor(IsFloor(VarCon("a"))) should be(IsFloor(VarCon("a")))
    // No optimizations
    // (add), (mul), (pow)
    IsFloor(IsMul(Closed(2), VarCon("x"))).children should be(Vector(IsMul(Closed(2), VarCon("x"))))
    IsFloor(IsPow(VarCon("x"))).children should be(Vector(IsPow(VarCon("x"))))
    IsFloor(IsAdd(Closed(1), VarCon("x"))).children should be(Vector(IsAdd(Closed(1), VarCon("x"))))
  }

}
