reprimComplex ::
  FreshName n =>
  MonadFresh m =>
  a ->
  [n] ->
  a ->
  Core.Prim ->
  [Atom n a] ->
  ExceptT (ReprimError n a) m (Term k X64.Prim n a -> Term k X64.Prim n a)
reprimComplex an ns ap p xs =
  case (ns, p, xs) of

    -- Arithmetic --

    ([dst], Core.Mul, [x, y]) -> do
      ignore <- newFresh
      pure .
        Let an [dst, ignore] $
        Prim ap X64.Imul [x, y]

    ([dst], Core.Div, [x, y]) -> do
      ignore <- newFresh
      high_x <- newFresh
      pure $
        Let ap [high_x]
          (Prim ap X64.Cqto [x]) .
        Let an [dst, ignore]
          (Prim ap X64.Idiv [x, Variable ap high_x, y])

    ([dst], Core.Mod, [x, y]) -> do
      ignore <- newFresh
      high_x <- newFresh
      pure $
        Let ap [high_x]
          (Prim ap X64.Cqto [x]) .
        Let an [ignore, dst]
          (Prim ap X64.Idiv [x, Variable ap high_x, y])

    -- Relations --

    ([dst], prim, [x, y])
      | Just cc <- takeCC prim
      -> do
        flags <- freshen dst
        dst8 <- freshen dst
        pure $
          Let an [flags]
            (Prim ap X64.Cmp [x, y]) .
          Let an [dst8]
            (Prim ap (X64.Set cc) [Variable ap flags]) .
          Let an [dst]
            (Prim ap X64.Movzbq [Variable ap dst8])

    _ ->
      throwE $ ReprimInvalidPrim ns p xs
reprimComplex ::
  FreshName n =>
  MonadFresh m =>
  a ->
  [n] ->
  a ->
  Core.Prim ->
  [Atom n a] ->
  ExceptT (ReprimError n a) m (Term k X64.Prim n a -> Term k X64.Prim n a)
reprimComplex an ns ap p xs =
  case (ns, p, xs) of

    -- Arithmetic --

    ([dst], Core.Mul, [x, y]) -> do
      ignore <- newFresh
      pure .
        Let an [dst, ignore] $
        Prim ap X64.Imul [x, y]

    ([dst], Core.Div, [x, y]) -> do
      ignore <- newFresh
      high_x <- newFresh
      pure $
        Let ap [high_x]
          (Prim ap X64.Cqto [x]) .
        Let an [dst, ignore]
          (Prim ap X64.Idiv [x, Variable ap high_x, y])

    ([dst], Core.Mod, [x, y]) -> do
      ignore <- newFresh
      high_x <- newFresh
      pure $
        Let ap [high_x]
          (Prim ap X64.Cqto [x]) .
        Let an [ignore, dst]
          (Prim ap X64.Idiv [x, Variable ap high_x, y])

    -- Relations --

    ([dst], prim, [x, y])
      | Just cc <- takeCC prim
      -> do
        flags <- freshen dst
        dst8 <- freshen dst
        pure $
          Let an [flags]
            (Prim ap X64.Cmp [x, y]) .
          Let an [dst8]
            (Prim ap (X64.Set cc) [Variable ap flags]) .
          Let an [dst]
            (Prim ap X64.Movzbq [Variable ap dst8])

    _ ->
      throwE $ ReprimInvalidPrim ns p xs
reprimComplex an ns ap p xs =
  case (ns, p, xs) of

    -- Arithmetic --

    ([dst], Core.Mul, [x, y]) -> do
      ignore <- newFresh
      pure .
        Let an [dst, ignore] $
        Prim ap X64.Imul [x, y]

    ([dst], Core.Div, [x, y]) -> do
      ignore <- newFresh
      high_x <- newFresh
      pure $
        Let ap [high_x]
          (Prim ap X64.Cqto [x]) .
        Let an [dst, ignore]
          (Prim ap X64.Idiv [x, Variable ap high_x, y])

    ([dst], Core.Mod, [x, y]) -> do
      ignore <- newFresh
      high_x <- newFresh
      pure $
        Let ap [high_x]
          (Prim ap X64.Cqto [x]) .
        Let an [ignore, dst]
          (Prim ap X64.Idiv [x, Variable ap high_x, y])

    -- Relations --

    ([dst], prim, [x, y])
      | Just cc <- takeCC prim
      -> do
        flags <- freshen dst
        dst8 <- freshen dst
        pure $
          Let an [flags]
            (Prim ap X64.Cmp [x, y]) .
          Let an [dst8]
            (Prim ap (X64.Set cc) [Variable ap flags]) .
          Let an [dst]
            (Prim ap X64.Movzbq [Variable ap dst8])

    _ ->
      throwE $ ReprimInvalidPrim ns p xs