{-# LANGUAGE TypeOperators , MultiParamTypeClasses , FlexibleInstances, FlexibleContexts, GeneralizedNewtypeDeriving , TypeFamilies #-}
module Data.Yall.Iso (
 {- |
  Iso is similar but more flexible than Lens in that they have no dependency on
  context. This flexibility affords a number of nice class instances that we
  don't get with Lens, so these can be quite useful in combination. See 'isoL'
  for converting to 'Lens'.

  A less imprecise name for the code here might be @Bijection@ but no one wants
  to type that.
 -}
    Iso(..)
  , inverseI
  -- * Convenient Iso types
  -- ** Pure isomorphisms
  , (:<->)
  , iso
  , ($-), (-$)
  -- *** Wrapped pure Iso
  , IsoPure(..), ifmap, fromPure
  -- *** Pre-defined isomorphisms
  {- | Note: while all of these are pure and could be expressed as '(:<->)', we
     define them polymorphically in @Monad@ for maximum flexibility in
     composing with other @Lens@ or @Iso@.

     Also note that for most of these @apply i . unapply i@ is not strictly
     @id@, e.g. @zipI@ obviously truncates lists of differing length, etc.
     This is officially not something I'm concerned about.
  -}
  , wordsI, showI, linesI, curryI, enumI, integerI, rationalI, zipI
  , incrementI, incrementByI, consI
  , distributeI, factorI

  -- ** Partial isomorphisms
  , (:<~>)
  )  where



import Prelude hiding ((.),id)
import Control.Category
import Data.Functor.Identity
import Control.Monad

-- from 'categories':
import qualified Control.Categorical.Functor as C
import Control.Categorical.Bifunctor
import Control.Category.Associative
import Control.Category.Braided
import Control.Category.Monoidal
import Control.Category.Distributive


-- | An Isomorphism or one-to-one mapping between types. These are very similar
-- to a 'Lens', but are not dependent on context, making them more flexible. The
-- functions also alow a Monadic context, supporting partial isomorphisms, and 
-- other interesting functionality.
data Iso w m a b = Iso { apply   :: a -> m b
                       , unapply :: b -> w a }

instance (Monad m, Monad w)=> Category (Iso w m) where
    id = iso id id
    g . f = Iso (apply f >=> apply g) (unapply g >=> unapply f)

-- | A wrapper for a more @(->)@-like Functor instances
newtype IsoPure a b = IsoPure { isoPure :: Iso Identity Identity a b }
    deriving (Category) 

-- ghetto deriving:
pureWrapped :: (Iso Identity Identity a1 b1 -> Iso Identity Identity a b)
                              -> IsoPure a1 b1
                              -> IsoPure a b
pureWrapped2 ::                (Iso Identity Identity a1 b1
                                -> Iso Identity Identity a2 b2
                                -> Iso Identity Identity a b)
                               -> IsoPure a1 b1
                               -> IsoPure a2 b2
                               -> IsoPure a b
pureWrapped2 f a b = IsoPure $ f (isoPure a) (isoPure b)
pureWrapped f = IsoPure . f . isoPure

instance PFunctor (,) IsoPure IsoPure where
    first = pureWrapped first
instance QFunctor (,) IsoPure IsoPure where
    second = pureWrapped second
instance Bifunctor (,) IsoPure IsoPure IsoPure where
    bimap = pureWrapped2 bimap
instance PFunctor Either IsoPure IsoPure where
    first = pureWrapped first
instance QFunctor Either IsoPure IsoPure where
    second = pureWrapped second
instance Bifunctor Either IsoPure IsoPure IsoPure where
    bimap = pureWrapped2 bimap

instance Associative IsoPure (,) where
    associate = IsoPure associate
instance Associative IsoPure Either where
    associate = IsoPure associate
instance Disassociative IsoPure (,) where
    disassociate = IsoPure disassociate
instance Disassociative IsoPure Either where
    disassociate = IsoPure disassociate

instance Braided IsoPure (,) where
    braid = IsoPure braid
instance Braided IsoPure Either where
    braid = IsoPure braid
instance Symmetric IsoPure Either where
instance Symmetric IsoPure (,) where

type instance Id IsoPure (,) = ()
instance Monoidal IsoPure (,) where
    idl = IsoPure idl
    idr = IsoPure idr
instance Comonoidal IsoPure (,) where
    coidl = IsoPure coidl
    coidr = IsoPure coidr


-- | A more categorical 'fmap', with wrapping / unwrapping for convenience. See
-- also the 'C.Functor' instances for 'Iso'.
--
-- > ifmap = fromPure . C.fmap . IsoPure
ifmap :: (Monad w, Monad m, C.Functor f IsoPure IsoPure)=> Iso Identity Identity a b -> Iso w m (f a) (f b)
ifmap = fromPure . C.fmap . IsoPure

-- | Unwrap and make polymorphic an 'IsoPure'
fromPure :: (Monad w, Monad m)=> IsoPure a b -> Iso w m a b
fromPure (IsoPure (Iso f g)) = iso (fmap runIdentity f) (fmap runIdentity g)


-- Control.Categorical.Functor
instance (Functor f)=> C.Functor f IsoPure IsoPure where
    fmap (IsoPure (Iso f g)) = 
        IsoPure $ iso (fmap $ fmap runIdentity f) (fmap $ fmap runIdentity g)

instance (Monad m)=> C.Functor m (Iso m m) (Iso Identity Identity) where
    fmap (Iso f g) = iso (>>= f) (>>= g)



-- Control.Categorical.Bifunctor
instance (Monad m, Monad w)=> PFunctor (,) (Iso w m) (Iso w m) where
    first = firstDefault
instance (Monad m, Monad w)=> QFunctor (,) (Iso w m) (Iso w m) where
    second = secondDefault

instance (Monad m, Monad w)=> Bifunctor (,) (Iso w m) (Iso w m) (Iso w m) where
    bimap (Iso f g) (Iso f' g') = Iso (bimapM f f') (bimapM' g g')
        -- WHY DOES TypeFamilies CAUSE PROBLEMS WITH THIS?:
        where bimapM x = fmap extractJoinT . bimap x
              bimapM' x = fmap extractJoinT . bimap x

instance (Monad m, Monad w)=> PFunctor Either (Iso w m) (Iso w m) where
    first = firstDefault
instance (Monad m, Monad w)=> QFunctor Either (Iso w m) (Iso w m) where
    second = secondDefault

instance (Monad m, Monad w)=> Bifunctor Either (Iso w m) (Iso w m) (Iso w m) where
    bimap (Iso f g) (Iso f' g') = Iso (bimapM f f') (bimapM' g g')
        where bimapM x = fmap extractJoinE . bimap x
              bimapM' x = fmap extractJoinE . bimap x


-- Does this already exist in Categories? 
--  :: k (m a) (m b) -> m (k a b)
--   For k = Either / (,)
--       m = any Monad
extractJoinE :: (Monad m)=> Either (m a) (m b) -> m (Either a b)
extractJoinE = either (liftM Left) (liftM Right)
extractJoinT :: (Monad m)=> (m a, m b) -> m (a,b)
extractJoinT = uncurry $ liftM2 (,)

-- Control.Category.Associative
instance (Monad m, Monad w)=> Associative (Iso w m) (,) where
    associate = iso associate disassociate

instance (Monad m, Monad w)=> Associative (Iso w m) Either where
    associate = iso associate disassociate
    
instance (Monad m, Monad w)=> Disassociative (Iso w m) (,) where
    disassociate = iso disassociate associate

instance (Monad m, Monad w)=> Disassociative (Iso w m) Either where
    disassociate = iso disassociate associate

-- Control.Category.Braided
instance (Monad m, Monad w)=> Braided (Iso w m) (,) where
    braid = iso braid braid

instance (Monad m, Monad w)=> Braided (Iso w m) Either where
    braid = iso braid braid

instance (Monad m, Monad w)=> Symmetric (Iso w m) (,) where
instance (Monad m, Monad w)=> Symmetric (Iso w m) Either where

distributeI :: (Monad m, Monad w)=> Iso w m (a, Either b c) (Either (a,b) (a,c))
distributeI = iso distribute factor

factorI :: (Monad m, Monad w)=> Iso w m (Either (a,b) (a,c)) (a, Either b c)
factorI = iso factor distribute

-- Control.Category.Monoidal
type instance Id (Iso w m) (,) = ()

instance (Monad m, Monad w)=> Monoidal (Iso w m) (,) where
    idl = iso idl coidl 
    idr = iso idr coidr

instance (Monad m, Monad w)=> Comonoidal (Iso w m) (,) where
    coidl =  iso coidl idl 
    coidr = iso coidr idr 


-- | See also an Iso wrapped in 'Dual'
inverseI :: (Monad m, Monad w)=> Iso w m a b -> Iso m w b a
inverseI (Iso f g) = Iso g f

-- | a partial Isomorphism
type a :<~> b = Iso Maybe Maybe a b

-- | pure Iso
type a :<-> b = Iso Identity Identity a b

iso :: (Monad m, Monad w)=> (a -> b) -> (b -> a) -> Iso w m a b
iso f g = Iso (fmap return f) (fmap return g)

-- | apply the forward function
-- 
-- > i $- a = runIdentity $ apply i a
($-) :: (a :<-> b) -> a -> b
i $- a = runIdentity $ apply i a

-- | apply the backward function
-- 
-- > i -$ b = runIdentity $ unapply i b
(-$) :: (a :<-> b) -> b -> a
i -$ b = runIdentity $ unapply i b


------------- 
wordsI :: (Monad m, Monad w)=> Iso w m String [String]
wordsI = iso words unwords

linesI :: (Monad m, Monad w)=> Iso w m String [String]
linesI = iso lines unlines

showI :: (Read s, Show s, Monad w, Monad m)=> Iso w m s String
showI = iso show read

-- TODO or leave this as the instance above???
curryI :: (Monad m, Monad w)=> Iso w m ((a,b) -> c) (a -> b -> c)
curryI = iso curry uncurry

enumI :: (Enum a, Monad m, Monad w)=> Iso w m Int a
enumI = iso toEnum fromEnum

integerI :: (Integral a, Monad m, Monad w)=> Iso w m a Integer
integerI = iso toInteger fromInteger

rationalI :: (Real a, Fractional a, Monad m, Monad w)=> Iso w m a Rational
rationalI = iso toRational fromRational

zipI :: (Monad m, Monad w)=> Iso w m ([a],[b]) [(a,b)]
zipI = iso (uncurry zip) unzip

incrementI :: (Monad m, Monad w, Num a)=> Iso w m a a
incrementI = incrementByI 1

incrementByI :: (Monad m, Monad w, Num a)=> a -> Iso w m a a
incrementByI n = iso (+n) (subtract n)

-- | Calls 'fail' on the empty list.
consI :: (Monad m, Monad w)=> Iso w m (a,[a]) [a]
consI = Iso (\(a,as)-> return (a:as)) unconsI
    where unconsI [] = fail "empty list"
          unconsI (a:as) = return (a,as)