```{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE DatatypeContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE UndecidableInstances #-}

-- | A \"morphism\" is a general term for a function that converts one data structure into another.  The HLearn library uses this module to allow arbitrary morphisms to be declared, chained together, and assigned properties like surjectivity and homomorphic.
--
-- NOTE: This module is still under heavy development and will probably change drastically!

module HLearn.Algebra.Morphism
(
-- * Morphisms
Morphism (..)
, DefaultMorphism (..)

-- * Morphism properties
, Surjective (..)
, Injective (..)
, Homomorphism (..)

-- * Chaining morphisms
, MorphismComposition (..)
)
where

import qualified Control.ConstraintKinds as CK
import HLearn.Algebra.Models
import HLearn.Algebra.Structures.Groups

-- | A Morphism converts from a domain to a codomain using the given parameters.  We perform the actual conversion using the @\$>@ operator.

class Morphism domain params codomain | params -> codomain where
morph' :: domain -> params -> codomain
morph' = (\$>)

(\$>) :: domain -> params -> codomain
(\$>) = morph'

(<.>) :: params -> domain -> codomain
(<.>) = flip morph'

-- | This data structure allow us to chain arbitrary morphisms together to generate a new morphism.
data
( Morphism domain params1 interdomain
, Morphism interdomain params2 codomain
) => MorphismComposition domain params1 interdomain params2 codomain = (:.) params2 params1

instance
( Morphism domain params1 interdomain
, Morphism interdomain params2 codomain
) => Morphism domain (MorphismComposition domain params1 interdomain params2 codomain) codomain
where
morph' x (params2 :. params1) = morph' (morph' x params1) params2

-- | If there is a reasonable default parameter (or no parameters at all) for performing the morphism, this class provides a shortcut syntax.
class (Morphism domain params codomain) => DefaultMorphism domain params codomain | domain codomain -> params  where
defMorphParams :: domain -> codomain -> params

morph :: domain -> codomain
morph domain = (morph' :: domain -> params -> codomain) domain (defMorphParams (undefined :: domain) (undefined :: codomain))

-------------------------------------------------------------------------------

-- data HLearn params domain codomain = HLearn params
-- instance Category (MorphismComposition' params1 interdomain params2) where
--     id a = a
--     (.) =

-------------------------------------------------------------------------------
-- Properties

class (Morphism domain params codomain) => Surjective domain params codomain
instance
( Surjective domain params1 interdomain
, Surjective interdomain params2 codomain
) => Surjective domain (MorphismComposition domain params1 interdomain params2 codomain) codomain

class (Morphism domain params codomain) => Injective domain params codomain
instance
( Injective domain params1 interdomain
, Injective interdomain params2 codomain
) => Injective domain (MorphismComposition domain params1 interdomain params2 codomain) codomain

class (Morphism domain params codomain) => Homomorphism domain params codomain
instance
( Homomorphism domain params1 interdomain
, Homomorphism interdomain params2 codomain
) => Homomorphism domain (MorphismComposition domain params1 interdomain params2 codomain) codomain

-------------------------------------------------------------------------------
-- Training

-- instance
--     ( HomTrainer modelparams datapoint model
--     , CK.Foldable container
--     , CK.FoldableConstraint container model
--     , CK.Functor container
--     , CK.FunctorConstraint container datapoint
--     , CK.FunctorConstraint container model
--     ) => Morphism (container datapoint) modelparams model
--     where
--         morph' input params = train' params input
--
-- instance
--     ( Model params1 interdomain
--     , Model params2 codomain
--     ) => Model (MorphismComposition domain params1 interdomain params2 codomain) codomain
--     where
--         getparams model = undefined

-- instance
--     ( DefaultModel params1 interdomain
--     , DefaultModel params2 codomain
--     ) => DefaultModel (MorphismComposition domain params1 interdomain params2 codomain) codomain
--     where
--         defparams = undefined
{-
instance
( CK.FunctorConstraint container model
, CK.FunctorConstraint container datapoint
, CK.FoldableConstraint container model
, CK.Foldable container
, CK.Functor container
, Model (MorphismComposition (container datapoint) params1 interdomain params2 codomain) codomain
, HomTrainer params1 datapoint interdomain
, Morphism (container datapoint) params1 interdomain
, Morphism interdomain params2 codomain
, Monoid codomain
, Semigroup codomain
, Model (MorphismComposition domain params1 interdomain params2 codomain) codomain
) => HomTrainer (MorphismComposition (container datapoint) params1 interdomain params2 codomain) datapoint codomain
where
train1dp' (params2 :. params1) dp = params2 <.> (train1dp' params1 dp)-}
```