///|
// Chain scattering convention [b1,a1]^T = T [a2,b2]^T. Multiplication is
// left-to-right along the signal path; unlike Z this handles an ideal thru.
fn to_chain(m : Array[Complex]) -> Array[Complex] raise {
  let inverse = complex(1.0).divide(m[2])
  [
    checked(m[1].sub(m[0].mul(m[3]).mul(inverse))),
    checked(m[0].mul(inverse)),
    checked(m[3].scale(-1.0).mul(inverse)),
    inverse,
  ]
}

///|
fn from_chain(t : Array[Complex]) -> Array[Complex] raise {
  let inverse = complex(1.0).divide(t[3])
  [
    checked(t[1].mul(inverse)),
    checked(t[0].sub(t[1].mul(t[2]).mul(inverse))),
    inverse,
    checked(t[2].scale(-1.0).mul(inverse)),
  ]
}

///|
fn matching_grid(a : Network, b : Network) -> Unit raise {
  if a.n != 2 || b.n != 2 || a.frequencies.length() != b.frequencies.length() {
    raise Failure("two-port matching frequency grids required")
  }
  for i, f in a.frequencies {
    if b.frequencies[i] != f {
      raise Failure("frequency grids differ; interpolate explicitly")
    }
  }
}

///|
/// Cascade this two-port followed by right. Connected references must match.
/// Requires nonzero forward transmission; unilateral reverse transmission is OK.
pub fn Network::cascade(self : Network, right : Network) -> Network raise {
  matching_grid(self, right)
  if self.reference[1] != right.reference[0] {
    raise Failure("connected port references differ; renormalize explicitly")
  }
  let a = self.convert("S")
  let b = right.convert("S")
  let values = Array::makei(a.values.length(), p => {
    from_chain(product(2, to_chain(a.values[p]), to_chain(b.values[p])))
  })
  network(2, self.frequencies, values, [self.reference[0], right.reference[1]])
}

///|
/// measured = left * DUT * right. Return DUT using fixture inner references.
/// Fixture chain matrices must be invertible and grids exactly equal.
pub fn Network::deembed(
  self : Network,
  left : Network,
  right : Network,
) -> Network raise {
  matching_grid(self, left)
  matching_grid(self, right)
  if self.reference[0] != left.reference[0] ||
    self.reference[1] != right.reference[1] {
    raise Failure("fixture external references differ from measurement")
  }
  let measured = self.convert("S")
  let l = left.convert("S")
  let r = right.convert("S")
  let values = Array::makei(self.values.length(), p => {
    let lm = invert_matrix(2, to_chain(l.values[p]))
    let rm = invert_matrix(2, to_chain(r.values[p]))
    from_chain(product(2, product(2, lm, to_chain(measured.values[p])), rm))
  })
  network(2, self.frequencies, values, [left.reference[1], right.reference[0]])
}