anchor.diff:  ﻿module TypeShape.Benchmarks.UnionEncoder
   
   open System
  -open System.Runtime.Serialization
   open FSharp.Reflection
   open TypeShape.UnionContract
   open BenchmarkDotNet.Attributes
  
positive.code:     | CreditsUsed of CreditsUsed
      | CartItemRemoved of CartItemRemoved
      | InlinedCase of value:int
      | [<DataMember(Name = "Legacy")>] Old_Stuff of CartItemRemoved
  with
      interface IUnionContract
  
  
  /// Union encoder/decoder interface
  type IUnionEncoder =
      abstract Encode : TestUnionContract -> EncodedUnion<obj>
      abstract Decode : EncodedUnion<obj> -> TestUnionContract
  
  // baseline bespoke implementation
  let baselineEncoder =
      { new IUnionEncoder with
          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
              | Old_Stuff c -> mkEnc "Legacy" 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())
              | "Legacy" -> Old_Stuff(getValue())
              | _ -> failwith "unrecognized case name"
      }
  
  // reflection-driven union encoder
  let 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
      { new IUnionEncoder with
          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 typeShapeEncoder = 
      let encoder = UnionContractEncoder.Create<TestUnionContract,_>(BoxEncoder()) 
      { new IUnionEncoder with
          member _.Encode(sum : TestUnionContract): EncodedUnion<obj> = 
              encoder.Encode sum
  
          member _.Decode(e : EncodedUnion<obj>): TestUnionContract =
              encoder.Decode e }
  
  let testValues =
      [|  NullaryEvent ()
          CartCreated { id = "cartId" ; client_id = "clientId" }
          AddressUpdated { id = "clientId" ; address = "address" }
  
negative.code:         CreditsUsed { id = "customerId" ; value = true }
          InlinedCase 42
          CartCreated { id = "cartId" ; client_id = "clientId" }
          CashUsed { id = "cashId" ; value = false } |]
  
  [<MemoryDiagnoser>]
  type UnionContractBenchmark() =
      let baselineEncoder = new BaselineEncoder()
      let reflectionEncoder = new ReflectionEncoder()
      let typeShapeEncoder = mkTypeShapeEncoder()
  
      let testValues = mkTestValues()
      let encodedValues = testValues |> Array.map baselineEncoder.Encode
  
      do if testValues.Length <> TestValueCount then invalidOp $"please update the value of {nameof(TestValueCount)} to be {testValues.Length}."
      
      [<Benchmark(OperationsPerInvoke = TestValueCount)>]
      member _.Serialize_Baseline() = for v in testValues do baselineEncoder.Encode v |> ignore
      [<Benchmark(OperationsPerInvoke = TestValueCount)>]
      member _.Serialize_Reflection() = for v in testValues do reflectionEncoder.Encode v |> ignore
      [<Benchmark(OperationsPerInvoke = TestValueCount)>]
      member _.Serialize_TypeShape() = for v in testValues do typeShapeEncoder.Encode v |> ignore
  
      [<Benchmark(OperationsPerInvoke = TestValueCount)>]
      member _.Deserialize_Baseline() = for e in encodedValues do baselineEncoder.Decode e |> ignore
      [<Benchmark(OperationsPerInvoke = TestValueCount)>]
      member _.Deserialize_Reflection() = for e in encodedValues do reflectionEncoder.Decode e |> ignore
      [<Benchmark(OperationsPerInvoke = TestValueCount)>]
      member _.Deserialize_TypeShape() = for e in encodedValues do typeShapeEncoder.Decode e |> ignore
  