// See LICENSE for license details.

package firrtl.constraint

import firrtl.ir.Closed

object IsMul {
  def apply(left: Constraint, right: Constraint): Constraint =
    (left, right) match {
      case (l: IsKnown, r: IsKnown) => l * r
      case _ => apply(Seq(left, right))
    }
  def apply(children: Seq[Constraint]): Constraint = {
    children
      .foldLeft(new IsMul(None, Vector())) { (add, c) =>
        add.addChild(c)
      }
      .reduce()
  }
}

case class IsMul private (known: Option[IsKnown], others: Vector[Constraint]) extends MultiAry {

  def op(b1: IsKnown, b2: IsKnown): IsKnown = b1 * b2

  lazy val children: Vector[Constraint] = if (known.nonEmpty) known.get +: others else others

  def addChild(x: Constraint): IsMul =
    x match {
      case k:   IsKnown => new IsMul(known = merge(Some(k), known), others)
      case mul: IsMul   => new IsMul(merge(known, mul.known), others ++ mul.others)
      case other => new IsMul(known, others :+ other)
    }

  override def reduce(): Constraint = {
    if (children.size == 1) children.head
    else {
      (known, others) match {
        case (Some(Closed(x)), _) if x == BigDecimal(1) => new IsMul(None, others).reduce()
        case (Some(Closed(x)), _) if x == BigDecimal(0) => Closed(0)
        case (Some(Closed(x)), Vector(m: IsMax)) if x > 0 =>
          IsMax(m.children.map { c => IsMul(Closed(x), c) })
        case (Some(Closed(x)), Vector(m: IsMax)) if x < 0 =>
          IsMin(m.children.map { c => IsMul(Closed(x), c) })
        case (Some(Closed(x)), Vector(m: IsMin)) if x > 0 =>
          IsMin(m.children.map { c => IsMul(Closed(x), c) })
        case (Some(Closed(x)), Vector(m: IsMin)) if x < 0 =>
          IsMax(m.children.map { c => IsMul(Closed(x), c) })
        case _ => this
      }
    }
  }

  override def map(f: Constraint => Constraint): Constraint = IsMul(children.map(f))

  override def serialize: String = "(" + children.map(_.serialize).mkString(" * ") + ")"
}
