{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DatatypeContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | Modules are a generalization of vector spaces

module HLearn.Algebra.Structures.Modules
    where

import qualified Control.ConstraintKinds as CK
import Data.List
import qualified Data.Map as Map
import HLearn.Algebra.Structures.Groups

-------------------------------------------------------------------------------
-- Operators

class (LeftOperator r m, RightOperator r m) => Operator r m
instance (LeftOperator r m, RightOperator r m) => Operator r m

class LeftOperator r m | m -> r where
    infixl 7 .*
    (.*) :: r -> m -> m

class RightOperator r m | m -> r where
    infixl 7 *.
    (*.) :: m -> r -> m

instance RightOperator Integer Integer where (*.) = (*)
instance LeftOperator Integer Integer where (.*) = (*)

instance RightOperator Int Int where (*.) = (*)
instance LeftOperator Int Int where (.*) = (*)

instance RightOperator Float Float where (*.) = (*)
instance LeftOperator Float Float where (.*) = (*)

instance RightOperator Double Double where (*.) = (*)
instance LeftOperator Double Double where (.*) = (*)

instance (RightOperator a b) => RightOperator a [b] where bs *. a = fmap (*.a) bs
instance (LeftOperator a b) => LeftOperator a [b] where a .* bs = fmap (a.*) bs

-------------------------------------------------------------------------------
-- FreeOp

-- | Bug: Technically, the free operator should just require that r be a semigroup and use (<>) to chain the r's together.  But this would make things awkward because the number types aren't instances of semigroup.  Constraining r to be of type Num reduces our generality but makes FreeOp easier to work with in most practical use cases.

-- newtype (Num r) => FreeOp r a = FreeOp [(r,a)]
--     deriving (Read,Show)
-- 
-- instance (Num r) => Functor (FreeOp r) where
--     fmap f (FreeOp xs) = FreeOp $ map (\(r,a) -> (r,f a)) xs
-- 
-- instance (Num r) => LeftOperator r (FreeOp r m) where
--     r <| (FreeOp xs) = FreeOp $ map (\(r2,m) -> (r*r2,m)) xs
--     
-- instance (Num r) => RightOperator r (FreeOp r m) where
--     m |> r = r <| m
--     
-- list2freeop :: (Num r) => [a] -> FreeOp r a
-- list2freeop = FreeOp . map (\x -> (1,x))



-------------------------------------------------------------------------------
-- Modules

-- | Bug: The module classes have the constraint that r be of type Num.  Technically, this should be a Ring.  But creating a Ring class would be awkward because it would conflict with the Num class and require importing a different Prelude.

class (LeftModule r g, RightModule r g) => Module r g
instance (LeftModule r g, RightModule r g) => Module r g

class (LeftOperator r g, Num r, Group g, Abelian g) => LeftModule r g
instance (LeftOperator r g, Num r, Group g, Abelian g) => LeftModule r g

class (RightOperator r g, Num r, Group g, Abelian g) => RightModule r g
instance (RightOperator r g, Num r, Group g, Abelian g) => RightModule r g

-------------------------------------------------------------------------------
-- FreeModule

data FreeModParams = FreeModParams

newtype (Num r, Ord a) => FreeMod r a = FreeMod (Map.Map a r)
    deriving (Read,Show,Eq)

instance CK.Functor (FreeMod r) where
    type FunctorConstraint (FreeMod r) a = (Num r, Ord a)
    fmap f (FreeMod m) = FreeMod $ Map.mapKeysWith (+) f m

instance CK.Foldable (FreeMod r) where
    type FoldableConstraint (FreeMod r) a = (Num r, Ord a, Operator r a)
--     foldr f b (FreeMod m) = Map.foldrWithKey (\a r b -> f (r .* a) b) b m
    foldr f b (FreeMod m) = foldr (\(a,r) b -> f (r .* a) b) b $ Map.toList m
    foldl f b (FreeMod m) = foldl (\b (a,r) -> f b (r .* a)) b $ Map.toList m
    foldl' f b (FreeMod m) = foldl' (\b (a,r) -> f b (r .* a)) b $ Map.toList m
    
    foldr1 f (FreeMod m) = foldr1 f $ map (\(a,r) -> r.*a) $ Map.toList m
    foldl1 f (FreeMod m) = foldl1 f $ map (\(a,r) -> r.*a) $ Map.toList m

instance (Num r, Ord a) => Abelian (FreeMod r a)
instance (Num r, Ord a) => Semigroup (FreeMod r a) where
    (FreeMod m1) <> (FreeMod m2) = FreeMod $ Map.unionWith (+) m1 m2

instance (Num r, Ord a) => Monoid (FreeMod r a) where
    mempty = FreeMod mempty
    mappend = (<>)
    
instance (Num r, Ord a) => RegularSemigroup (FreeMod r a) where
    inverse (FreeMod m) = FreeMod $ Map.map negate m

instance (Num r, Ord a) => LeftModule r (FreeMod r a)
instance (Num r, Ord a) => LeftOperator r (FreeMod r a) where
    r .* (FreeMod m) = FreeMod $ Map.map (r*) m
    
instance (Num r, Ord a) => RightModule r (FreeMod r a)
instance (Num r, Ord a) => RightOperator r (FreeMod r a) where
    a *. r = r .* a

list2module :: (Num r, Ord r, Ord a) => [a] -> FreeMod r a
list2module xs = FreeMod $ Map.fromList $ go 0 (head sorted) [] sorted
    where
        sorted = sort xs
        
        go n x retL [] = (x,n):retL
        go n x retL xs = if (head xs) == x
            then go (n+1) x retL (tail xs)
            else go 1 (head xs) ((x,n):retL) (tail xs)