module DynamicallyChecked.Lens where

open import DynamicallyChecked.Partiality
open import DynamicallyChecked.Utilities

open import Function
open import Data.Product
open import Relation.Nullary
open import Relation.Binary
open import Relation.Binary.PropositionalEquality


record Lens (S V : Set) : Set₁ where
  field
    put : S  V  Par S
    get : S  Par V
    PutGet : {s : S} {v : V} {s' : S}  put s v  s'  get s'  v
    GetPut : {s : S} {v : V}  get s  v  put s v  s

infix 1 _⇆_

_⇆_ : Set  Set  Set₁
_⇆_ = Lens

_≼ᴾ_ : {A : Set}  Par A  Par A  Set₁
mx ≼ᴾ my =  {a}  mx  a  my  a

_≈ᴾ_ : {A : Set}  Par A  Par A  Set₁
mx ≈ᴾ my = (mx ≼ᴾ my) × (my ≼ᴾ mx)

uniqueness-lemma : {S V : Set} (l l' : S  V) {s : S} {v : V} 
                   ({s : S} {v : V}  Lens.put l s v ≼ᴾ Lens.put l' s v)  Lens.get l s  v  Lens.get l' s  v
uniqueness-lemma l l' put-≼ = Lens.PutGet l'  put-≼  Lens.GetPut l

uniqueness : {S V : Set} (l l' : S  V) 
             ({s : S} {v : V}  Lens.put l s v ≈ᴾ Lens.put l' s v)  {s : S}  Lens.get l s ≈ᴾ Lens.get l' s
uniqueness l l' put-eq = uniqueness-lemma l l' (proj₁ put-eq) , uniqueness-lemma l' l (proj₂ put-eq)

iso-lens : {A B : Set}  A  B  A  B
iso-lens iso = record
  { put = const (Iso.from iso)
  ; get = Iso.to iso
  ; PutGet = Iso.from-to-inverse iso
  ; GetPut = Iso.to-from-inverse iso }

skip-lens : {S V : Set}  Decidable (_≡_ {A = V})  (S  V)  S  V
skip-lens {S} {V} _≟_ f = record
  { put = put
  ; get = return  f
  ; PutGet = λ put↦  return (PutGet put↦)
  ; GetPut = λ { {_} {._} (return refl)  GetPut } }
  where  
    put : S  V  Par S
    put s v with f s  v
    put s v | yes _ = return s
    put s v | no  _ = fail

    PutGet : {s : S} {v : V} {s' : S}  put s v  s'  f s'  v
    PutGet {s} {v} put↦          with f s  v
    PutGet         (return refl) | yes refl = refl
    PutGet         ()            | no  _
    
    GetPut : {s : S}  put s (f s)  s
    GetPut {s} with f s  f s
    GetPut {s} | yes _  = return refl
    GetPut {s} | no neq with neq refl
    GetPut {s} | no neq | ()

infixr 3 _↔_ _↕_ _◂_ _▸_

_↔_ : {A B C : Set}  A  B  B  C  A  C
l  r = record
  { put = λ a c  Lens.get l a >>= λ b  Lens.put r b c >>= Lens.put l a
  ; get = Lens.get r <=< Lens.get l
  ; PutGet = λ { (get-l-a↦b >>= put-r-b↦b' >>= put-l-a-b'↦a')  Lens.PutGet l put-l-a-b'↦a' >>= Lens.PutGet r put-r-b↦b' }
  ; GetPut = λ { (get-l-a↦b >>= get-r-b↦c)  get-l-a↦b >>= Lens.GetPut r get-r-b↦c >>= Lens.GetPut l get-l-a↦b } }

_◂_ : {A B C : Set}  A  B  B  C  A  C
l  iso = record
  { put = λ a c  Iso.from iso c >>= Lens.put l a
  ; get = Iso.to iso <=< Lens.get l
  ; PutGet = λ { (from-v↦v' >>= put-s-v'↦s')  Lens.PutGet l put-s-v'↦s' >>= Iso.from-to-inverse iso from-v↦v' }
  ; GetPut = λ { (get-s↦v >>= to-v↦v')  Iso.to-from-inverse iso to-v↦v' >>= Lens.GetPut l get-s↦v } } 

_▸_ : {A B C : Set}  A  B  B  C  A  C
iso  l = iso-lens iso  l

_↕_ : {A B C D : Set}  A  B  C  D  A × C  B × D
l  r = record
  { put = λ { (a , c) (b , d)  liftPar₂ _,_ (Lens.put l a b) (Lens.put r c d) }
  ; get = λ { (a , c)  liftPar₂ _,_ (Lens.get l a) (Lens.get r c) }
  ; PutGet = λ { (put-l↦ >>= put-r↦ >>= return refl)  Lens.PutGet l put-l↦ >>= Lens.PutGet r put-r↦ >>= return refl }
  ; GetPut = λ { (get-l↦ >>= get-r↦ >>= return refl)  Lens.GetPut l get-l↦ >>= Lens.GetPut r get-r↦ >>= return refl } }