/-
Copyright (c) 2023 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Marc Huisinga
-/
module

prelude
public import Lean.Data.Json.FromToJson.Basic

public section

namespace Lean

/-- Restriction of `DataValue` that covers exactly those cases that Lean is able to handle when passed via the `-D` flag. -/
inductive LeanOptionValue where
  | ofString (s : String)
  | ofBool   (b : Bool)
  | ofNat    (n : Nat)
  deriving Inhabited, Repr

def LeanOptionValue.ofDataValue? : DataValue → Option LeanOptionValue
  | .ofString s => some (.ofString s)
  | .ofBool b   => some (.ofBool b)
  | .ofNat n    => some (.ofNat n)
  | _           => none

def LeanOptionValue.toDataValue : LeanOptionValue → DataValue
  | .ofString s => .ofString s
  | .ofBool b   => .ofBool b
  | .ofNat n    => .ofNat n

instance : KVMap.Value LeanOptionValue where
  toDataValue  := LeanOptionValue.toDataValue
  ofDataValue? := LeanOptionValue.ofDataValue?

instance : Coe String LeanOptionValue where
  coe := LeanOptionValue.ofString

instance : Coe Bool LeanOptionValue where
  coe := LeanOptionValue.ofBool

instance : Coe Nat LeanOptionValue where
  coe := LeanOptionValue.ofNat

instance : FromJson LeanOptionValue where
  fromJson?
    | (s : String) => Except.ok s
    | (b : Bool)   => Except.ok b
    | (n : Nat)    => Except.ok n
    | _ => Except.error "invalid LeanOptionValue type"

instance : ToJson LeanOptionValue where
  toJson
    | (s : String) => s
    | (b : Bool)   => b
    | (n : Nat)    => n

/-- Formats the lean option value as a CLI flag argument. -/
def LeanOptionValue.asCliFlagValue : (v : LeanOptionValue) → String
  | (s : String) => s!"\"{s}\""
  | (b : Bool)   => toString b
  | (n : Nat)    => toString n

/-- An option that is used by Lean as if it was passed using `-D`. -/
structure LeanOption where
  /-- The option's name. -/
  name  : Lean.Name
  /-- The option's value. -/
  value : Lean.LeanOptionValue
  deriving Inhabited, Repr

/-- Formats the lean option as a CLI argument using the `-D` flag. -/
def LeanOption.asCliArg (o : LeanOption) : String :=
  s!"-D{o.name}={o.value.asCliFlagValue}"

/-- Options that are used by Lean as if they were passed using `-D`. -/
structure LeanOptions where
  values : NameMap LeanOptionValue
  deriving Inhabited, Repr

instance : EmptyCollection LeanOptions := ⟨⟨∅⟩⟩

def LeanOptions.ofArray (opts : Array LeanOption) : LeanOptions :=
  ⟨opts.foldl (fun m {name, value} => m.insert name value) {}⟩

/-- Add the options from `new`, overriding those in `self`. -/
protected def LeanOptions.append (self new : LeanOptions) : LeanOptions :=
  ⟨self.values.mergeWith (fun _ _ b => b) new.values⟩

instance : Append LeanOptions := ⟨LeanOptions.append⟩

/-- Add the options from `new`, overriding those in `self`. -/
def LeanOptions.appendArray (self : LeanOptions) (new : Array LeanOption) : LeanOptions :=
  ⟨new.foldl (fun m {name, value} => m.insert name value) self.values⟩

instance : HAppend LeanOptions (Array LeanOption) LeanOptions := ⟨LeanOptions.appendArray⟩

def LeanOptions.toOptions (leanOptions : LeanOptions) : Options := Id.run do
  let mut options := KVMap.empty
  for ⟨name, optionValue⟩ in leanOptions.values do
    options := options.insert name optionValue.toDataValue
  return options

def LeanOptions.fromOptions? (options : Options) : Option LeanOptions := do
  let mut values := Std.TreeMap.empty
  for ⟨name, dataValue⟩ in options do
    let optionValue ← LeanOptionValue.ofDataValue? dataValue
    values := values.insert name optionValue
  return ⟨values⟩

instance : FromJson LeanOptions where
  fromJson? j := LeanOptions.mk <$> fromJson? j

instance : ToJson LeanOptions where
  toJson options := toJson options.values

end Lean
