anchor.diff:  ﻿module TypeShape.Benchmarks.UnionEncoder
   
   open System
  -open System.Runtime.Serialization
   open FSharp.Reflection
   open TypeShape.UnionContract
   open BenchmarkDotNet.Attributes
  
positive.code:         CreditsUsed { id = "customerId" ; value = true }
          InlinedCase 42
          CartCreated { id = "cartId" ; client_id = "clientId" }
          CashUsed { id = "cashId" ; value = false } 
          Old_Stuff { id = "cartId" ; retail_sku_id = "" ; date = DateTimeOffset() } |]
  
  let inline testEncoderRoundtrip (encoder : IUnionEncoder) =
      for v in testValues do
          let e = encoder.Encode v
          let _ = encoder.Decode e
          ()
  
  [<MemoryDiagnoser>]
  type UnionContractBenchmark() =
      
      [<Benchmark(Description = "Baseline Union Encoder", Baseline = true)>]
      member _.Baseline() = testEncoderRoundtrip baselineEncoder
      [<Benchmark(Description = "Reflection Union Encoder")>]
      member _.Reflection() = testEncoderRoundtrip reflectionEncoder
      [<Benchmark(Description = "TypeShape Union Encoder")>]
      member _.TypeShape() = testEncoderRoundtrip typeShapeEncoder
  
negative.code:     | CreditsUsed of CreditsUsed
      | CartItemRemoved of CartItemRemoved
      | InlinedCase of value:int
  with
      interface IUnionContract
  
  // baseline bespoke implementation
  type BaselineEncoder () =
      member _.Encode(sum : TestUnionContract): EncodedUnion<obj> = 
          let inline mkEnc name payload = { CaseName = name ; Payload = box payload }
          match sum with
          | NullaryEvent () -> mkEnc "NullaryEvent" ()
          | CartCreated c -> mkEnc "CartCreated" c
          | AddressUpdated c -> mkEnc "AddressUpdated" c
          | ItemReturnWaived c -> mkEnc "ItemReturnWaived" c
          | CashUsed c -> mkEnc "CashUsed" c
          | CreditsUsed c -> mkEnc "CreditsUsed" c
          | CartItemRemoved c -> mkEnc "CartItemRemoved" c
          | InlinedCase c -> mkEnc "InlinedCase" c
  
      member _.Decode(e : EncodedUnion<obj>): TestUnionContract = 
          let inline getValue() = e.Payload :?> 'a
  
          match e.CaseName with
          | "NullaryEvent" -> NullaryEvent(getValue())
          | "CartCreated"  -> CartCreated(getValue())
          | "AddressUpdated" -> AddressUpdated(getValue())
          | "ItemReturnWaived" -> ItemReturnWaived(getValue())
          | "CashUsed" -> CashUsed(getValue())
          | "CreditsUsed" -> CreditsUsed(getValue())
          | "CartItemRemoved" -> CartItemRemoved(getValue())
          | "InlinedCase" -> InlinedCase(getValue())
          | _ -> failwith "unrecognized case name"
  
  // reflection-driven union encoder
  type ReflectionEncoder() =
      let tagReader = FSharpValue.PreComputeUnionTagReader(typeof<TestUnionContract>)
      let ucis = FSharpType.GetUnionCases(typeof<TestUnionContract>)
      let ctors = ucis |> Array.map FSharpValue.PreComputeUnionConstructor
      let readers = ucis |> Array.map FSharpValue.PreComputeUnionReader
  
      member _.Encode(sum : TestUnionContract): EncodedUnion<obj> = 
          let tag = tagReader sum
          let name = ucis.[tag].Name
          let reader = readers.[tag]
          let values = reader sum
          { CaseName = name ; Payload = values.[0] }
  
      member _.Decode(e : EncodedUnion<obj>): TestUnionContract =
          let tag = ucis |> Array.findIndex (fun u -> e.CaseName = u.Name)
          let ctor = ctors.[tag]
          let values = [|e.Payload|]
          ctor values :?> TestUnionContract
  
  // typeshape-driven encoder
  let mkTypeShapeEncoder() = UnionContractEncoder.Create<TestUnionContract,_>(BoxEncoder()) 
  
  [<Literal>]
  let TestValueCount = 10
  let mkTestValues () =
      [|  NullaryEvent ()
          CartCreated { id = "cartId" ; client_id = "clientId" }
          AddressUpdated { id = "clientId" ; address = "address" }
  