{-# OPTIONS_GHC -fglasgow-exts -fallow-undecidable-instances -fallow-overlapping-instances #-}

module MO.Compile.Class where

import MO.Base ()
import MO.Compile
import MO.Compile.Attribute
import MO.Compile.Role
import MO.Run
import MO.Util
import Data.Typeable (Typeable1, Typeable(..), typeOf1, mkTyCon, mkTyConApp)
import Control.Monad (liftM)
import Data.Monoid
import qualified Data.Typeable as Typeable

import qualified MO.Util.C3 as C3 (linearize)

import qualified Data.Map as Map

type ClassName = Atom

class (Typeable1 m, Monad m, Typeable c, Eq c) => Class m c | c -> m where
    class_name               :: c -> ClassName
    superclasses             :: c -> [AnyClass m]

    -- These three methods below are shared between all C3-happy classes.
    class_precedence_list    :: c -> [AnyClass m]
    class_precedence_list cls = case C3.linearize (Just . superclasses) (MkClass cls) of
        Just ok -> ok
        _       -> error "..."

    all_attributes        :: c -> [Attribute m]
    all_attributes c
        = concatMap attributes (class_precedence_list c)
        ++ concatMap allRoleAttributes (roles c)
        where
        allRoleAttributes r = role_attributes r ++ concatMap allRoleAttributes (parent_roles r)

    all_attribute_methods :: c -> [AnyMethod m]
    all_attribute_methods c = shadow (from_c ++ [from_r])
        where
        from_c = map attribute_methods (class_precedence_list c)
        from_r = all_using_role_shadowing (merged_roles c) role_attribute_methods
        -- Take all public attributes of this class and make read-only accessor for them
        attribute_methods = cmap makeAccessorMethod . newCollection' a_accessor_name . attributes
        role_attribute_methods = cmap makeAccessorMethod . newCollection' a_accessor_name . r_attributes
        makeAccessorMethod attr = MkMethod $ MkSimpleMethod
            { sm_name        = a_accessor_name attr
            , sm_definition  = MkMethodCompiled $ error . show . getInvocant
            }

    all_methods     :: c -> [AnyMethod m]
    all_methods cls = all_attribute_methods cls ++ all_regular_methods cls

    all_regular_methods :: c -> [AnyMethod m]
    all_regular_methods c = shadow (from_c ++ [from_r])
      where from_c = map public_methods (class_precedence_list c)
            from_r = all_using_role_shadowing
                         (merged_roles c) role_public_methods

    roles                    :: c -> [Role m]
    merged_roles             :: c -> Role m
    merged_roles c           = emptyRole { r_roles = roles c }
    
--  attribute_grammars       :: c -> [AttributeGrammar]
    attributes               :: c -> [Attribute m]
    public_methods           :: c -> Collection (AnyMethod m)
    private_methods          :: c -> Collection (AnyMethod m)

    class_interface :: c -> AnyResponder m
    class_interface = MkResponder
                       . (fromMethodList :: [(MethodName, MethodCompiled m)] -> m (MethodTable m))
                       . map (\m -> (methodName m, methodCompile m))
                       . all_methods

data AnyClass m = forall c. Class m c => MkClass !c

instance (Typeable1 m, Monad m) => Typeable (AnyClass m) where
    typeOf _ = mkTyConApp (mkTyCon "AnyClass") [typeOf1 (undefined :: m ())]

instance (Typeable1 m, Monad m) => Eq (AnyClass m) where
    MkClass x == MkClass y = case Typeable.cast y of
        Just y' -> x == y'  -- same type, compare with its Eq
        _       -> False    -- not same type, never eq

instance (Typeable1 m, Monad m) => Show (AnyClass m) where
    show = show . class_name

-- TODO: How hackish is instantiating the AnyMoose for the class Moose?
-- Could it cause serious problems? Well, there's a DRY problem here, but
-- what else?
instance (Typeable1 m, Monad m) => Class m (AnyClass m) where
    class_name              (MkClass c) = class_name c
    superclasses            (MkClass c) = superclasses c
    class_precedence_list   (MkClass c) = class_precedence_list c
    all_methods             (MkClass c) = all_methods c
    roles                   (MkClass c) = roles c
--  attribute_grammars      (MkClass c) = attribute_grammars c
    attributes              (MkClass c) = attributes c
    public_methods          (MkClass c) = public_methods c
    private_methods         (MkClass c) = private_methods c
    class_interface         (MkClass c) = class_interface c

-- FIXME: hmm.. how to do Subclassing properly, ie. have MOClass and MOClass share about
-- everything except for just a couple of things? Type-classes doesn't seem to
-- match right, specially because I want nice constructors via record syntax.

data (Monad m, Typeable1 m) => MOClass m
    = MkMOClass
        { moc_parents                :: [AnyClass m]
        , moc_roles                  :: [Role m]
--      , moc_attribute_grammar      :: [AttributeGrammar]
        , moc_attributes             :: [Attribute m]
        , moc_public_methods         :: Collection (AnyMethod m)
        , moc_private_methods        :: Collection (AnyMethod m)
        , moc_name                   :: ClassName
        }
        -- deriving (Eq)

instance (Typeable1 m, Monad m) => Show (MOClass m) where
    show = ('^':) . fromAtom . moc_name
instance (Typeable1 m, Monad m) => Ord (MOClass m) where
    compare = compare `on` moc_name
instance (Typeable1 m, Monad m) => Eq (MOClass m) where
    (==) = (==) `on` moc_name
instance (Typeable1 m, Monad m) => Typeable (MOClass m) where
    typeOf _ = mkTyConApp (mkTyCon "MOClass") [typeOf1 (undefined :: m ())]

emptyMOClass :: (Typeable1 m, Monad m) => MOClass m
emptyMOClass = MkMOClass
    { moc_parents         = []
    , moc_roles           = []
    , moc_attributes      = []
    , moc_public_methods  = newCollection []
    , moc_private_methods = newCollection []
    , moc_name            = mempty
    }

_bless :: MethodName
_bless = toAtom "bless"

-- FIXME: Method then AnyMethod then MethodAttached then AnyMethod again is ugly
newMOClass :: (Typeable1 m, Monad m) => MOClass m -> MOClass m
newMOClass old = new
    where attach        = MkMethod . MkMethodAttached new
          withBless     = insert _bless (blessMOClass new)
          withCreate    = id -- insert "CREATE"   (createMOClass new)
          new           = old { moc_public_methods = cmap attach . withBless . withCreate $ moc_public_methods old }

blessMOClass :: Class m c => c -> AnyMethod m
blessMOClass c = MkMethod $ MkSimpleMethod
    { sm_name        = _bless
    , sm_definition  = MkMethodCompiled constructor
    }
    where
    -- Here we generate a structure from some layout.  The "params" here 
    -- contains initial values of those attributes.
    constructor params = do
        -- For each attribute, create a new instance of it.
        structure <- liftM Map.fromList . (`mapM` all_attributes c) $ \attr -> do
            let name = a_name attr
                userDefinedVal = namedArg params name
            val <- case userDefinedVal of
                Just obj    -> return obj
                _           -> a_default attr
            return (a_name attr, val)
        return $ MkInvocant structure (class_interface c)

instance (Typeable1 m, Monad m) => Class m (MOClass m) where
    class_name               = moc_name
    superclasses             = moc_parents
    roles                    = moc_roles
    attributes               = moc_attributes
    public_methods           = moc_public_methods
    private_methods          = moc_private_methods

--    add_class_method c@MkMOClass{siClassMethods = ms} m =
--        c {siClassMethods = m:ms}


-- MethodAttached 
data MethodAttached m
    = forall c a. (Class m c, Method m a) => MkMethodAttached
        !c       -- Origin
        !a       -- Method

instance Monad m => Method m (MethodAttached m) where
    methodName (MkMethodAttached _ m) = methodName m
    methodCompile (MkMethodAttached _ m) = methodCompile m