{-# OPTIONS --safe #-}

module Data.List.Membership where

open import Prelude

infixr 5 _∈_ _∈!_
_∈_ : A  List A  Type _
x  xs = Fibre (xs !!_ ) x

_∈!_ : A  List A  Type _
x ∈! xs = isContr (x  xs)

infixr 5 _∉_
_∉_ : A  List A  Type _
x  xs = ¬ (x  xs)

private variable n : 

tabulate-∈ : (f : Fin n  A) (x : Fin n)  f x  tabulate f
tabulate-∈ {n = suc n} f zero    = zero , refl
tabulate-∈ {n = suc n} f (suc x) =
  let i , p = tabulate-∈ (f  suc) x
  in suc i , p

allFins : List (Fin n)
allFins = tabulate id

∈-allFins : (i : Fin n)  i  allFins
∈-allFins = tabulate-∈ id

NoDup : {A : Type a}  List A  Type a
NoDup [] = Poly-⊤
NoDup (x  xs) = (x  xs) × NoDup xs

module _ (_≟_ : Discrete A) where
  module _ (x : A) where
    find :  xs  Dec (x  xs)
    find [] = no λ ()
    find (y  xs) with x  y | find xs
    ... | yes x≡y | _ = yes (zero , sym x≡y)
    ... | no _    | yes (i , x∈xs) = yes (suc i , x∈xs)
    ... | no x≢y  | no x∉xs = no λ { (zero , x∈xs)  x≢y (sym x∈xs) ; (suc i , x∈xs)  x∉xs (i , x∈xs) }

  NoDups? : (xs : List A)  Dec (NoDup xs)
  NoDups? [] = yes Poly-tt
  NoDups? (x  xs) with find x xs | NoDups? xs
  NoDups? (x  xs) | yes x∈xs | _ = no ((_$ x∈xs)  fst)
  NoDups? (x  xs) | no x∉xs | yes nds = yes (x∉xs , nds)
  NoDups? (x  xs) | no x∉xs | no ¬nds = no (¬nds  snd)

isPropNoDup : (xs : List A)  isProp (NoDup xs)
isPropNoDup [] _ _ = refl
isPropNoDup (x  xs) (l , ls) (r , rs) = cong₂ _,_ (isProp¬ l r) (isPropNoDup xs ls rs)

NoDup⇒Inj : (xs : List A)  NoDup xs  Injective (xs !!_)
NoDup⇒Inj (x  xs) nd {zero} {zero} p = refl
NoDup⇒Inj (x  xs) (_ , nd) {suc i} {suc j} p = cong suc (NoDup⇒Inj xs nd p)
NoDup⇒Inj (x  xs) (n , _) {zero} {suc j} p = absurd (n (j , sym p))
NoDup⇒Inj (x  xs) (n , _) {suc i} {zero} p = absurd (n (i , p))

module _ {x : A} where
  infixr 5 _++◇_ _◇++_
  _++◇_ :  {xs} ys  x  xs  x  (ys ++ xs)
  [] ++◇ p = p
  (x  ys) ++◇ p = map-Σ suc id (ys ++◇ p)

  _◇++_ :  xs {ys}  x  xs  x  (xs ++ ys)
  (x  xs) ◇++ (zero , p) = zero , p
  (x  xs) ◇++ (suc i , p) = map-Σ suc id (xs ◇++ (i , p))

cong-∈ : (f : A  B) (xs : List A) {x : A}  x  xs  f x  mapl f xs
cong-∈ f (x  xs) (zero , p) = zero , cong f p
cong-∈ f (x  xs) (suc i , p) = map-Σ suc id (cong-∈ f xs (i , p))

here! :  {x y : A} {xs}  isContr (x  y)  y  xs  y ∈! x  xs
here! Px p∉xs .fst = zero , Px .fst
here! Px p∉xs .snd (zero  , p∈xs) = ΣPathP (refl , Px .snd _)
here! Px p∉xs .snd (suc n , p∈xs) = absurd (p∉xs (n , p∈xs))
open import Data.Fin.Properties using (suc-injective)

module MembershipManips where
  pull :  {x y : A} {xs}  x  y  y  x  xs  y  xs
  pull x≢y (zero  , y∈xxs) = absurd (x≢y y∈xxs)
  pull x≢y (suc n , y∈xxs) = n , y∈xxs
  
  push :  {x y : A} {xs}  y  xs  y  x  xs
  push (n , p) = suc n , p
  
  pull! :  {x y : A} {xs}  x  y  y ∈! x  xs  y ∈! xs
  pull! x≢y ((zero  , y∈xxs) , c) = absurd (x≢y y∈xxs)
  pull! x≢y ((suc n , y∈xxs) , c) .fst = (n , y∈xxs)
  pull! x≢y ((suc n , y∈xxs) , c) .snd (m , q) i = pull x≢y (c (suc m , q) i)
  
  push! :  {x y : A} {xs}  x  y  y ∈! xs  y ∈! x  xs
  push! ¬Px x∈!xs .fst = push (x∈!xs .fst)
  push! ¬Px x∈!xs .snd (zero   , px) = absurd (¬Px px)
  push! ¬Px x∈!xs .snd (suc n  , y∈xs) i = push (x∈!xs .snd (n , y∈xs) i)

open MembershipManips

module _ {a} {A : Type a} (_≟_ : Discrete A) where
  isSet⟨A⟩ : isSet A
  isSet⟨A⟩ = Discrete→isSet _≟_

  infixl 6 _\\_
  _\\_ : List A  A  List A
  xs \\ x = foldr f [] xs
    where
    f : A  List A  List A
    f y xs with x  y
    ... | yes p = xs
    ... | no  p = y  xs

  uniques : List A  List A
  uniques = foldr f []
    where
    f : A  List A  List A
    f x xs = x  (xs \\ x)

  x∉xs\\x :  x xs  x  xs \\ x
  x∉xs\\x x (y  xs) (n , x∈xs) with x  y
  x∉xs\\x x (y  xs) (n      , x∈xs)  | yes p = x∉xs\\x x xs (n , x∈xs)
  x∉xs\\x x (y  xs) (zero   , y≡x)   | no ¬p = ¬p (sym y≡x)
  x∉xs\\x x (y  xs) (suc n  , x∈xs)  | no ¬p = x∉xs\\x x xs (n , x∈xs)

  x∈!x∷xs\\x :  x xs  x ∈! x  xs \\ x
  x∈!x∷xs\\x x xs = here! (refl ,  isSet⟨A⟩ _ _ _) (x∉xs\\x x xs)

  x∉xs⇒x∉xs\\y :  (x : A) y xs  x  xs  x  xs \\ y
  x∉xs⇒x∉xs\\y x y (z  xs) x∉xs x∈xs\\y with y  z
  x∉xs⇒x∉xs\\y x y (z  xs) x∉xs x∈xs\\y | yes p =
    x∉xs⇒x∉xs\\y x y xs (x∉xs  map-Σ suc id) x∈xs\\y
  x∉xs⇒x∉xs\\y x y (z  xs) x∉xs (zero  , x∈xs\\y) | no ¬p =
    x∉xs (zero , x∈xs\\y)
  x∉xs⇒x∉xs\\y x y (z  xs) x∉xs (suc n , x∈xs\\y) | no ¬p =
    x∉xs⇒x∉xs\\y x y xs (x∉xs  map-Σ suc id) (n , x∈xs\\y)

  open import Data.Fin.Properties using (zero≢suc ; suc≢zero)

  x∈!xs⇒x∈!xs\\y :  (x : A) y xs  y  x  x ∈! xs  x ∈! xs \\ y
  x∈!xs⇒x∈!xs\\y x y (z  xs) y≢x x∈!xs with y  z
  x∈!xs⇒x∈!xs\\y x y (z  xs) y≢x x∈!xs | yes p =
    x∈!xs⇒x∈!xs\\y x y xs y≢x (pull! (y≢x ∘′ (p ;_)) x∈!xs)
  x∈!xs⇒x∈!xs\\y x y (z  xs) y≢x ((zero  , x∈xs) , uniq) | no ¬p =
    here! (x∈xs , isSet⟨A⟩ _ _ _) (x∉xs⇒x∉xs\\y x y xs (zero≢suc ∘′ cong fst ∘′ uniq ∘′ push))
  x∈!xs⇒x∈!xs\\y x y (z  xs) y≢x ((suc n , x∈xs) , uniq) | no ¬p =
    push! z≢x (x∈!xs⇒x∈!xs\\y x y xs y≢x (pull! z≢x ((suc n , x∈xs) , uniq)))
    where z≢x = suc≢zero  cong fst ∘′ uniq ∘′ (zero ,_)

  ∈⇒∈! : (x : A) (xs : List A)  x  xs  x ∈! uniques xs
  ∈⇒∈! x (y  xs) (zero  , x∈xs) =
    subst (_∈! (y  uniques xs \\ y)) x∈xs (x∈!x∷xs\\x y (uniques xs))
  ∈⇒∈! x (y  xs) (suc n , x∈xs) with y  x
  ... | yes p = subst (_∈! (y  uniques xs \\ y)) p (x∈!x∷xs\\x y (uniques xs))
  ... | no ¬p = push! ¬p (x∈!xs⇒x∈!xs\\y x y (uniques xs) ¬p (∈⇒∈! x xs (n , x∈xs)))