------------------------------------------------------------------------
--                                                                    --
--  Agda code accompanying the article                                --
--                                                                    --
--    Programming metamorphic algorithms:                             --
--    An experiment in type-driven algorithm design                   --
--                                                                    --
--    https://doi.org/10.22152/programming-journal.org/2021/5/7       --
--                                                                    --
--  by Hsiang-Shang ‘Josh’ Ko                                         --
--    (Institute of Information Science, Academia Sinica, Taiwan)     --
--                                                                    --
--  Checked with Agda version 2.6.1 and Standard Library version 1.3  --
--                                                                    --
------------------------------------------------------------------------


open import Function
open import Data.Product
open import Data.Sum
open import Data.Maybe
open import Relation.Binary.PropositionalEquality


data List (A : Set) : Set where
  []  : List A
  _∷_ : A → List A → List A

foldr : {A S : Set} → (A → S → S) → S → List A → S
foldr _◁_ e []       = e
foldr _◁_ e (a ∷ as) = a ◁ foldr _◁_ e as

foldl : {A S : Set} → (S → A → S) → S → List A → S
foldl _▷_ e []       = e
foldl _▷_ e (a ∷ as) = foldl _▷_ (e ▷ a) as

mutual

  record CoList (B : Set) : Set where
    coinductive
    field
      decon : CoListF B

  data CoListF (B : Set) : Set where
    []  : CoListF B
    _∷_ : B → CoList B → CoListF B

unfoldr : {B S : Set} → (S → Maybe (B × S)) → S → CoList B
CoList.decon (unfoldr g s) with g s
CoList.decon (unfoldr g s) | nothing       = []
CoList.decon (unfoldr g s) | just (b , s') = b ∷ unfoldr g s'

data AlgList (A {S} : Set) (_◁_ : A → S → S) (e : S) : S → Set where
  []  : AlgList A _◁_ e e
  _∷_ : (a : A) → {s : S} → AlgList A _◁_ e s → AlgList A _◁_ e (a ◁ s)

mutual

  record CoalgList (B {S} : Set) (g : S → Maybe (B × S)) (s : S) : Set where
    coinductive
    field
        decon : CoalgListF B g s

  data CoalgListF (B {S} : Set) (g : S → Maybe (B × S)) : S → Set where
    ⟨_⟩    : {s : S} → g s ≡ nothing → CoalgListF B g s
    _∷⟨_⟩_ : (b : B) → {s s' : S} → g s ≡ just (b , s') → CoalgList B g s' → CoalgListF B g s

open CoalgList

from-left-alg : {A S : Set} → (S → A → S) → A → (S → S) → (S → S)
from-left-alg _▷_ a t = t ∘ flip _▷_ a

foldl-as-foldr : {A S : Set} {_▷_ : S → A → S} {e : S}
                 (as : List A) → foldl _▷_ e as ≡ foldr (from-left-alg _▷_) id as e
foldl-as-foldr []       = refl
foldl-as-foldr (a ∷ as) = foldl-as-foldr as

module _ {A B S : Set} where

  module ConsumingBeforeProducing
    (_▷_ : S → A → S) (g : S → Maybe (B × S))
    where

    cbp : (s : S) → {h : S → S} → AlgList A (from-left-alg _▷_) id h → CoalgList B g (h s)
    decon (cbp s []) with g s        | inspect g s
    decon (cbp s []) | nothing       | [ eq ] = ⟨ eq ⟩
    decon (cbp s []) | just (b , s') | [ eq ] = b ∷⟨ eq ⟩ cbp s' []
    cbp s (a ∷ as) = cbp (s ▷ a) as

  module Streaming
    (_▷_ : S → A → S) (g : S → Maybe (B × S))
    (streaming-condition :
       {a : A} {b : B} {s s' : S} → g s ≡ just (b , s') → g (s ▷ a) ≡ just (b , s' ▷ a))
    where

    streaming-lemma : {b : B} {s s' : S} {h : S → S} →
                      AlgList A (from-left-alg _▷_) id h → g s ≡ just (b , s') → g (h s) ≡ just (b , h s')
    streaming-lemma []       eq = eq
    streaming-lemma (a ∷ as) eq = streaming-lemma as (streaming-condition eq)

    stream : (s : S) → {h : S → S} → AlgList A (from-left-alg _▷_) id h → CoalgList B g (h s)
    decon (stream s as      ) with g s        | inspect g s
    decon (stream s []      ) | nothing       | [ eq ] = ⟨ eq ⟩
    decon (stream s (a ∷ as)) | nothing       | [ eq ] = decon (stream (s ▷ a) as)
    decon (stream s as      ) | just (b , s') | [ eq ] = b ∷⟨ streaming-lemma as eq ⟩ stream s' as

  cong-from-just : {X : Set} {x x' : X} → (Maybe X ∋ just x) ≡ just x' → x ≡ x'
  cong-from-just refl = refl

  module Jigsaw-Infinite
    (_◁_ : A → S → S) (e : S) (g∞ : S → B × S)
    (piece : A × B → B × A)
    (jigsaw-conditionᵢ : {a : A} {b : B} {s s' : S} →
                         g∞ s ≡ (b , s') → let (b' , a') = piece (a , b) in g∞ (a ◁ s) ≡ (b' , a' ◁ s'))
    (straight : B) (straight-production : g∞ e ≡ (straight , e))
    where

    fillᵢₕ : {s : S} → AlgList A _◁_ e s → Σ[ b ∈ B ] Σ[ t ∈ S ] AlgList A _◁_ e t × (g∞ s ≡ (b , t))
    fillᵢₕ []       = straight , _ , [] , straight-production
    fillᵢₕ (a ∷ as) with fillᵢₕ as
    fillᵢₕ (a ∷ as) | b , _ , as' , eq = let (b' , a') = piece (a , b)
                                         in  b' , _ , a' ∷ as' , jigsaw-conditionᵢ eq

    jigsawᵢₕ : {s : S} → AlgList A _◁_ e s → CoalgList B (just ∘ g∞) s
    decon (jigsawᵢₕ as) with fillᵢₕ as
    decon (jigsawᵢₕ as) | b , _ , as' , eq = b ∷⟨ cong just eq ⟩ jigsawᵢₕ as'
   
    fillᵢᵥ : {s : S} (a : A) → CoalgList B (just ∘ g∞) s → CoalgList B (just ∘ g∞) (a ◁ s)  
    decon (fillᵢᵥ a bs) with decon bs
    decon (fillᵢᵥ a bs) | ⟨ () ⟩
    decon (fillᵢᵥ a bs) | b ∷⟨ eq ⟩ bs' =
      let (b' , a') = piece (a , b)
      in  b' ∷⟨ cong just (jigsaw-conditionᵢ (cong-from-just eq)) ⟩ fillᵢᵥ a' bs'

    jigsawᵢᵥ : {s : S} → AlgList A _◁_ e s → CoalgList B (just ∘ g∞) s
    decon (jigsawᵢᵥ []) = straight ∷⟨ cong just straight-production ⟩ jigsawᵢᵥ []
    jigsawᵢᵥ (a ∷ as) = fillᵢᵥ a (jigsawᵢᵥ as)

  module Jigsaw-General
    (_◁_ : A → S → S) (e : S) (g : S → Maybe (B × S))
    (nothing-from-e : g e ≡ nothing)
    (piece : A × B → B × A)
    (flat? : (a : A) → ({s : S} → g s ≡ nothing → g (a ◁ s) ≡ nothing) ⊎
                       ({s : S} → g (a ◁ s) ≢ nothing))
    (straight : B)
    (jigsaw-condition :
       {a : A} {b : B} {s s' : S} →
       g s ≡ just (b , s') ⊎ (g s ≡ nothing × g (a ◁ s) ≢ nothing × b ≡ straight × s' ≡ s) →
       let (b' , a') = piece (a , b) in g (a ◁ s) ≡ just (b' , a' ◁ s'))
    where

    fill : {s : S} (a : A) → CoalgList B g s → CoalgList B g (a ◁ s)
    decon (fill a bs) with decon bs
    decon (fill a bs) | ⟨ eq ⟩ with flat? a 
    decon (fill a bs) | ⟨ eq ⟩ | inj₁ flat     = ⟨ flat eq ⟩
    decon (fill a bs) | ⟨ eq ⟩ | inj₂ not-flat =
      let (b' , a') = piece (a , straight)
      in  b' ∷⟨ jigsaw-condition (inj₂ (eq , not-flat , refl , refl)) ⟩ fill a' bs
    decon (fill a bs) | b ∷⟨ eq ⟩ bs' =
      let (b' , a') = piece (a , b)
      in  b' ∷⟨ jigsaw-condition (inj₁ eq) ⟩ fill a' bs'

    jigsaw : {s : S} → AlgList A _◁_ e s → CoalgList B g s
    decon (jigsaw []) = ⟨ nothing-from-e ⟩
    jigsaw (a ∷ as) = fill a (jigsaw as)

  module Jigsaw-Left-Infinite
    (_▷_ : S → A → S) (g : S → B × S)
    (piece : B × A → A × B)
    (jigsaw-condition : {a : A} {b : B} {s s' : S} →
                        g s ≡ (b , s') → let (a' , b') = piece (b , a) in  g (s ▷ a) ≡ (b' , s' ▷ a'))
    where

    fillₗᵢ : {s : S} → CoalgList B (just ∘ g) s → (a : A) → CoalgList B (just ∘ g) (s ▷ a)
    decon (fillₗᵢ bs a) with decon bs
    decon (fillₗᵢ bs a) | ⟨ () ⟩
    decon (fillₗᵢ bs a) | b ∷⟨ eq ⟩ bs' =
      let (a' , b') = piece (b , a)
      in  b' ∷⟨ cong just (jigsaw-condition (cong-from-just eq)) ⟩ fillₗᵢ bs' a'

    jigsawₗᵢ : {s : S} → CoalgList B (just ∘ g) s →
              {h : S → S} → AlgList A (from-left-alg _▷_) id h → CoalgList B (just ∘ g) (h s)
    jigsawₗᵢ bs []       = bs
    jigsawₗᵢ bs (a ∷ as) = jigsawₗᵢ (fillₗᵢ bs a) as