{-# OPTIONS --without-K --safe #-}
module Categories.Category.Species.Constructions where

-- Construction of basic species

open import Level
open import Data.Empty
open import Data.Fin.Base as Fin using (Fin)
open import Data.Fin.Properties using (¬Fin0)
open import Data.Nat.Base using (; suc; zero)
open import Data.Product as × using (Σ; proj₁; proj₂; _,_)
open import Data.Product.Relation.Binary.Pointwise.NonDependent using (×-setoid)
open import Data.Sum.Base as  using (inj₁; inj₂)
open import Data.Sum.Relation.Binary.Pointwise using (_⊎ₛ_; inj₁; inj₂)
open import Data.Unit.Polymorphic using (; tt)
open import Function.Base using () renaming (id to id→)
open import Function.Bundles using (Func; _⟨$⟩_)
open import Relation.Nullary using (Dec; yes; no)
open import Relation.Binary.Bundles using (Setoid)
import Relation.Binary.PropositionalEquality as 

open import Categories.Category.Core using (Category)
open import Categories.Category.Construction.ObjectRestriction using (ObjectRestriction)
open import Categories.Category.Construction.Core using (Core)
open import Categories.Category.Instance.Setoids using (Setoids)
open import Categories.Category.Instance.FinSetoids using (FinSetoids; IsFiniteSetoid)
open import Categories.Category.Product
open import Categories.Functor.Core using (Functor)
open import Categories.Functor.Construction.Constant using (const)
open import Categories.Functor.Hom
open import Categories.Morphism.IsoEquiv using (_≃_)
open import Categories.NaturalTransformation.NaturalIsomorphism using (NaturalIsomorphism)
open import Categories.Category.Species

import Categories.Morphism as Mor

-- Some useful preliminary definitions.  Should be in stdlib
module _ {o  : Level} where
  -- the Setoid version of ⊥
  ⊥-Setoid : Setoid o 
  ⊥-Setoid = record
    { Carrier = Lift o 
    ; _≈_ = λ ()
    ; isEquivalence = record { refl = λ { {()} } ; sym = λ { {()} } ; trans = λ { {()} } } }

  ⊤-Setoid : Setoid o 
  ⊤-Setoid = record { Carrier =  ; _≈_ = λ _ _  }

  ⊤-FinSetoid : Σ (Setoid o ) IsFiniteSetoid
  ⊤-FinSetoid =
    ⊤-Setoid ,
    1 ,
    record
      { to = λ _  Fin.zero
      ; from = λ _  tt
      ; to-cong = λ _  ≡.refl
      ; from-cong = λ _  tt
      ; inverse =  { {Fin.zero} _  ≡.refl }) , λ _  tt
      }

-- We could have 4 levels, and still define Zero and One′.
-- But X needs o ≡ o′ and ℓ ≡ ℓ′.  And then the 'proper' definition of One makes that all the same.
module _ (o : Level) where
  -- Setoid o ℓ is used a lot, name it too
  S = Setoid o o
  FinSet = FinSetoids o o
  𝔹 = Core FinSet
  Sp = Species o o o o

  open Category Sp
  open Mor FinSet
  open _≅_

  -- convenient alias: a structure is an Object of Species
  -- (which is of course a functor)
  Structure = Obj

  Zero : Structure
  Zero = const ⊥-Setoid

  -- One can be specified in two ways.  The traditional one (which doesn't generalize as well)
  -- uses 'counting' directly. Don't even try it here, it just leads to much pain.
  
  -- There is a much nicer specification.
  One : Structure
  One = Hom[ 𝔹 ][ ⊤-FinSetoid ,-]

  -- the 'identity' Functor isn't really, it needs to forget the proof.
  X : Structure
  X = record
    { F₀ = proj₁
    ; F₁ = λ f  record
        { to = from f ⟨$⟩_
        ; cong = Func.cong (from f)
        }
    ; identity = λ { {(S , _)}  Setoid.refl S }
    ; homomorphism = λ {_} {_} {Z}  Setoid.refl (proj₁ Z)
    ; F-resp-≈ = _≃_.from-≈
    }

  -- generally this should be defined elsewhere
  _+_ : Structure  Structure  Structure
  A + B = record
    { F₀ = λ x  A.₀ x ⊎ₛ B.₀ x
    ; F₁ = λ X≅Y  record
      { to = ⊎.map (A.₁ X≅Y ⟨$⟩_) (B.₁ X≅Y ⟨$⟩_)
      ; cong = λ { (inj₁ x≈y)  inj₁ (Func.cong (A.₁ X≅Y) x≈y )
                 ; (inj₂ x≈y)  inj₂ (Func.cong (B.₁ X≅Y) x≈y)}
      }
    ; identity = λ { {S , n , pf} {inj₁ x}  inj₁ (A.identity {x = x})
                   ; {S , n , pf} {inj₂ y}  inj₂ B.identity} 
    ; homomorphism = λ { {x = inj₁ x}  inj₁ A.homomorphism
                       ; {x = inj₂ y}  inj₂ B.homomorphism}
    ; F-resp-≈ = λ { f≈g {inj₁ x}  inj₁ (A.F-resp-≈ f≈g)
                   ; f≈g {inj₂ y}  inj₂ (B.F-resp-≈ f≈g)}
    }
    where
    module A = Functor A
    module B = Functor B

  -- Hadamard product
  _×_ : Structure  Structure  Structure
  A × B = record
    { F₀ = λ x  ×-setoid (A.₀ x) (B.₀ x)
    ; F₁ = λ X≅Y  record
      { to = ×.map (A.₁ X≅Y ⟨$⟩_) (B.₁ X≅Y ⟨$⟩_)
      ; cong = ×.map (Func.cong (A.₁ X≅Y)) (Func.cong (B.₁ X≅Y))
      }
    ; identity = A.identity , B.identity
    ; homomorphism = A.homomorphism , B.homomorphism
    ; F-resp-≈ = λ f≈g  (A.F-resp-≈ f≈g) , (B.F-resp-≈ f≈g)
    }
    where
    module A = Functor A
    module B = Functor B