module FF.Json.Base where

open import Agda.Builtin.String
  using (String; primStringAppend; primShowNat; primShowString)

open import Cubical.Foundations.Prelude
  using (Level; Type; _≡_; ℓ-max; ℓ-suc)
open import Cubical.Data.Bool.Base
  using (Bool; true; false)
open import Cubical.Data.List.Base
  using (List; []; _∷_; _++_)
open import Cubical.Data.Maybe.Base
  using (Maybe; just; nothing)
open import Cubical.Data.Nat.Base
  using (ℕ; zero; suc)
open import Cubical.Data.Sigma.Base
  using (_×_; _,_)

infixr 5 _<>_

_<>_ : String → String → String
_<>_ = primStringAppend

record AtomUniverse (ℓ : Level) : Type (ℓ-suc ℓ) where
  constructor mkAtomUniverse
  field
    Code       : Type ℓ
    El         : Code → Type ℓ
    renderAtom : (c : Code) → El c → String

open AtomUniverse public

data Json {ℓ : Level} (U : AtomUniverse ℓ) : Type ℓ where
  atom   : (c : Code U) → El U c → Json U
  array  : List (Json U) → Json U
  object : List (String × Json U) → Json U

quoteString : String → String
quoteString = primShowString

showNat : ℕ → String
showNat = primShowNat

join : String → List String → String
join sep [] = ""
join sep (x ∷ []) = x
join sep (x ∷ y ∷ xs) = x <> sep <> join sep (y ∷ xs)

indent : ℕ → String
indent zero = ""
indent (suc n) = "  " <> indent n

mutual
  renderCompact : ∀ {ℓ} {U : AtomUniverse ℓ} → Json U → String
  renderCompact {U = U} (atom c x) = renderAtom U c x
  renderCompact (array xs) = "[" <> renderCompactList xs <> "]"
  renderCompact (object xs) = "{" <> renderCompactFields xs <> "}"

  renderCompactList : ∀ {ℓ} {U : AtomUniverse ℓ} → List (Json U) → String
  renderCompactList [] = ""
  renderCompactList (x ∷ []) = renderCompact x
  renderCompactList (x ∷ y ∷ xs) =
    renderCompact x <> "," <> renderCompactList (y ∷ xs)

  renderCompactFields : ∀ {ℓ} {U : AtomUniverse ℓ} →
                        List (String × Json U) → String
  renderCompactFields [] = ""
  renderCompactFields ((k , v) ∷ []) =
    quoteString k <> ":" <> renderCompact v
  renderCompactFields ((k , v) ∷ y ∷ xs) =
    quoteString k <> ":" <> renderCompact v <> "," <> renderCompactFields (y ∷ xs)

mutual
  renderPrettyAt : ∀ {ℓ} {U : AtomUniverse ℓ} → ℕ → Json U → String
  renderPrettyAt {U = U} depth (atom c x) = renderAtom U c x
  renderPrettyAt depth (array []) = "[]"
  renderPrettyAt depth (array (x ∷ xs)) =
    "[\n"
    <> renderPrettyItems depth (x ∷ xs)
    <> "\n"
    <> indent depth
    <> "]"
  renderPrettyAt depth (object []) = "{}"
  renderPrettyAt depth (object (x ∷ xs)) =
    "{\n"
    <> renderPrettyFields depth (x ∷ xs)
    <> "\n"
    <> indent depth
    <> "}"

  renderPrettyItems : ∀ {ℓ} {U : AtomUniverse ℓ} → ℕ → List (Json U) → String
  renderPrettyItems depth [] = ""
  renderPrettyItems depth (x ∷ []) =
    indent (suc depth) <> renderPrettyAt (suc depth) x
  renderPrettyItems depth (x ∷ y ∷ xs) =
    indent (suc depth) <> renderPrettyAt (suc depth) x
    <> ",\n"
    <> renderPrettyItems depth (y ∷ xs)

  renderPrettyFields : ∀ {ℓ} {U : AtomUniverse ℓ} →
                       ℕ → List (String × Json U) → String
  renderPrettyFields depth [] = ""
  renderPrettyFields depth ((k , v) ∷ []) =
    indent (suc depth) <> quoteString k <> ": " <> renderPrettyAt (suc depth) v
  renderPrettyFields depth ((k , v) ∷ y ∷ xs) =
    indent (suc depth) <> quoteString k <> ": " <> renderPrettyAt (suc depth) v
    <> ",\n"
    <> renderPrettyFields depth (y ∷ xs)

renderPretty : ∀ {ℓ} {U : AtomUniverse ℓ} → Json U → String
renderPretty = renderPrettyAt zero

record ToJSON' {ℓA ℓU : Level} (U : AtomUniverse ℓU)
               (A : Type ℓA) : Type (ℓ-max ℓA ℓU) where
  constructor mkToJSON'
  field
    toJSON : A → Json U

open ToJSON' public

record FromJSON' {ℓA ℓU : Level} (U : AtomUniverse ℓU)
                 (A : Type ℓA) : Type (ℓ-max ℓA ℓU) where
  constructor mkFromJSON'
  field
    fromJSON : Json U → Maybe A

open FromJSON' public

record FromToJSON' {ℓA ℓU : Level} (U : AtomUniverse ℓU)
                   (A : Type ℓA) : Type (ℓ-max ℓA ℓU) where
  constructor mkFromToJSON'
  field
    to   : ToJSON' U A
    from : FromJSON' U A
    roundtrip : (x : A) → fromJSON from (toJSON to x) ≡ just x

open FromToJSON' public

encodeWith : ∀ {ℓA ℓU} {U : AtomUniverse ℓU} {A : Type ℓA} →
             ToJSON' U A → A → Json U
encodeWith codec x = toJSON codec x

decodeWith : ∀ {ℓA ℓU} {U : AtomUniverse ℓU} {A : Type ℓA} →
             FromJSON' U A → Json U → Maybe A
decodeWith codec x = fromJSON codec x

renderWith : ∀ {ℓA ℓU} {U : AtomUniverse ℓU} {A : Type ℓA} →
             ToJSON' U A → A → String
renderWith codec x = renderCompact (encodeWith codec x)

renderPrettyWith : ∀ {ℓA ℓU} {U : AtomUniverse ℓU} {A : Type ℓA} →
                   ToJSON' U A → A → String
renderPrettyWith codec x = renderPretty (encodeWith codec x)