import SilverSight.FixedPoint

open SilverSight.FixedPoint
open SilverSight.FixedPoint.Q16_16

-- C functions linked through extern_lib q16_canonical.
@[extern "q16_to_float"] opaque cQ16ToFloat (q : UInt32) : Float
@[extern "float_to_q16_nearbyint"] opaque cFloatToQ16 (f : Float) : UInt32
@[extern "q16_add"] opaque cQ16Add (a b : UInt32) : UInt32
@[extern "q16_sub"] opaque cQ16Sub (a b : UInt32) : UInt32

private def q16ToBits (q : Q16_16) : UInt32 := Q16_16.toBits q
private def bitsToQ16 (u : UInt32) : Q16_16 := Q16_16.ofBits u

private def floatEq (a b : Float) (ε : Float := 1e-9) : Bool :=
  let d := a - b
  d < ε && d > -ε

private def intCases : List Int :=
  [ 0, 1, 32768, 65536, 19661, -65536,
    -2147483648, 2147483647,
    -1000000, 1000000 ]

-- Values chosen so that floor and round-to-nearest-even agree at Q16.16 scale,
-- keeping Lean's floor-based `ofFloat` consistent with C's `nearbyint`.
private def floatCases : List Float :=
  [ 0.0, -0.0, 0.5, 1.0, -1.0, 2.5, -2.5,
    32767.5, -32768.0, 1e12, -1e12 ]

private def addCases : List (Int × Int) :=
  [ (0, 0), (1, 1), (32768, 32768), (65536, 65536),
    (2147483647, 1), (-2147483648, -1), (-65536, 65536),
    (1000000, 2000000) ]

private def subCases : List (Int × Int) :=
  [ (0, 0), (1, 0), (0, 1), (2147483647, -1),
    (-2147483648, 1), (65536, -65536) ]

private structure TestCase where
  name : String
  passed : Bool
  detail : String
  deriving Repr

private def runToFloatTests : List TestCase :=
  intCases.map fun raw =>
    let q := Q16_16.ofRawInt raw
    let cVal := cQ16ToFloat (q16ToBits q)
    let leanVal := q.toFloat
    let ok := floatEq cVal leanVal
    { name := s!"to_float {raw}", passed := ok,
      detail := s!"C={cVal} Lean={leanVal}" }

private def runFromFloatTests : List TestCase :=
  floatCases.map fun f =>
    let leanRaw := (Q16_16.ofFloat f).toInt
    let cRaw := (bitsToQ16 (cFloatToQ16 f)).toInt
    let ok := leanRaw == cRaw
    { name := s!"from_float {f}", passed := ok,
      detail := s!"C={cRaw} Lean={leanRaw}" }

private def runAddTests : List TestCase :=
  addCases.map fun (aRaw, bRaw) =>
    let a := Q16_16.ofRawInt aRaw
    let b := Q16_16.ofRawInt bRaw
    let leanRaw := (Q16_16.add a b).toInt
    let cRaw := (bitsToQ16 (cQ16Add (q16ToBits a) (q16ToBits b))).toInt
    let ok := leanRaw == cRaw
    { name := s!"add {aRaw} {bRaw}", passed := ok,
      detail := s!"C={cRaw} Lean={leanRaw}" }

private def runSubTests : List TestCase :=
  subCases.map fun (aRaw, bRaw) =>
    let a := Q16_16.ofRawInt aRaw
    let b := Q16_16.ofRawInt bRaw
    let leanRaw := (Q16_16.sub a b).toInt
    let cRaw := (bitsToQ16 (cQ16Sub (q16ToBits a) (q16ToBits b))).toInt
    let ok := leanRaw == cRaw
    { name := s!"sub {aRaw} {bRaw}", passed := ok,
      detail := s!"C={cRaw} Lean={leanRaw}" }

private def jBool (b : Bool) : String := if b then "true" else "false"

private def jStr (s : String) : String :=
  "\"" ++ (s.replace "\\" "\\\\" |>.replace "\"" "\\\"") ++ "\""

private def jCase (t : TestCase) : String :=
  s!"\{\"name\":{jStr t.name},\"passed\":{jBool t.passed},\"detail\":{jStr t.detail}}"

private def jCases (cs : List TestCase) : String :=
  "[" ++ String.intercalate "," (cs.map jCase) ++ "]"

def main : IO UInt32 := do
  let tests := runToFloatTests ++ runFromFloatTests ++ runAddTests ++ runSubTests
  let passed := tests.filter (·.passed)
  let failed := tests.filter (!·.passed)
  let ok := failed.isEmpty
  IO.println <|
    s!"\{\"schema\":\"q16_16_lean_c_roundtrip_v1\"," ++
    s!"\"ok\":{jBool ok}," ++
    s!"\"total\":{tests.length}," ++
    s!"\"passed\":{passed.length}," ++
    s!"\"failed\":{failed.length}," ++
    s!"\"tests\":{jCases tests}}"
  return if ok then 0 else 1
