module Data.GI.CodeGen.OverloadedMethods
    ( genMethodList
    , genMethodInfo
    , genUnsupportedMethodInfo
    ) where

import Control.Monad (forM, forM_, when)
import Data.Monoid ((<>))
import Data.Text (Text)
import qualified Data.Text as T

import Data.GI.CodeGen.API
import Data.GI.CodeGen.Callable (callableSignature, Signature(..),
                                 ForeignSymbol(..), fixupCallerAllocates)
import Data.GI.CodeGen.Code
import Data.GI.CodeGen.SymbolNaming (lowerName, upperName, qualifiedSymbol)
import Data.GI.CodeGen.Util (ucFirst)

-- | Qualified name for the info for a given method.
methodInfoName :: Name -> Method -> CodeGen Text
methodInfoName n method =
    let infoName = upperName n <> (ucFirst . lowerName . methodName) method
                   <> "MethodInfo"
    in qualifiedSymbol infoName n

-- | Appropriate instances so overloaded labels are properly resolved.
genMethodResolver :: Text -> CodeGen ()
genMethodResolver n = do
  group $ do
    line $ "instance (info ~ Resolve" <> n <> "Method t " <> n <> ", "
          <> "O.MethodInfo info " <> n <> " p) => O.IsLabelProxy t ("
          <> n <> " -> p) where"
    indent $ line $ "fromLabelProxy _ = O.overloadedMethod (O.MethodProxy :: O.MethodProxy info)"
  group $ do
    line $ "#if MIN_VERSION_base(4,9,0)"
    line $ "instance (info ~ Resolve" <> n <> "Method t " <> n <> ", "
          <> "O.MethodInfo info " <> n <> " p) => O.IsLabel t ("
          <> n <> " -> p) where"
    indent $ line $ "fromLabel _ = O.overloadedMethod (O.MethodProxy :: O.MethodProxy info)"
    line $ "#endif"

-- | Generate the `MethodList` instance given the list of methods for
-- the given named type.
genMethodList :: Name -> [(Name, Method)] -> CodeGen ()
genMethodList n methods = do
  let name = upperName n
  let filteredMethods = filter isOrdinaryMethod methods
      gets = filter isGet filteredMethods
      sets = filter isSet filteredMethods
      others = filter (\m -> not (isSet m || isGet m)) filteredMethods
      orderedMethods = others ++ gets ++ sets
  infos <- forM orderedMethods $ \(owner, method) ->
           do mi <- methodInfoName owner method
              return ((lowerName . methodName) method, mi)
  group $ do
    let resolver = "Resolve" <> name <> "Method"
    line $ "type family " <> resolver <> " (t :: Symbol) (o :: *) :: * where"
    indent $ forM_ infos $ \(label, info) -> do
        line $ resolver <> " \"" <> label <> "\" o = " <> info
    indent $ line $ resolver <> " l o = O.MethodResolutionFailed l o"

  genMethodResolver name

  where isOrdinaryMethod :: (Name, Method) -> Bool
        isOrdinaryMethod (_, m) = methodType m == OrdinaryMethod

        isGet :: (Name, Method) -> Bool
        isGet (_, m) = "get_" `T.isPrefixOf` (name . methodName) m

        isSet :: (Name, Method) -> Bool
        isSet (_, m) = "set_" `T.isPrefixOf` (name . methodName) m

-- | Generate the `MethodInfo` type and instance for the given method.
genMethodInfo :: Name -> Method -> ExcCodeGen ()
genMethodInfo n m =
    when (methodType m == OrdinaryMethod) $
      group $ do
        infoName <- methodInfoName n m
        let callable = fixupCallerAllocates (methodCallable m)
        sig <- callableSignature callable (KnownForeignSymbol undefined)
        bline $ "data " <> infoName
        -- This should not happen, since ordinary methods always
        -- have the instance as first argument.
        when (null (signatureArgTypes sig)) $
          error $ "Internal error: too few parameters! " ++ show m
        let (obj:otherTypes) = map snd (signatureArgTypes sig)
            sigConstraint = "signature ~ (" <> T.intercalate " -> "
              (otherTypes ++ [signatureReturnType sig]) <> ")"
        line $ "instance (" <> T.intercalate ", " (sigConstraint :
                                                   signatureConstraints sig)
                 <> ") => O.MethodInfo " <> infoName <> " " <> obj <> " signature where"
        let mn = methodName m
            mangled = lowerName (mn {name = name n <> "_" <> name mn})
        indent $ line $ "overloadedMethod _ = " <> mangled
        exportMethod (lowerName mn) infoName

-- | Generate a method info that is not actually callable, but rather
-- gives a type error when trying to use it.
genUnsupportedMethodInfo :: Name -> Method -> CodeGen ()
genUnsupportedMethodInfo n m = do
  infoName <- methodInfoName n m
  line $ "-- XXX: Dummy instance, since code generation failed.\n"
           <> "-- Please file a bug at http://github.com/haskell-gi/haskell-gi."
  bline $ "data " <> infoName
  line $ "instance (p ~ (), o ~ O.MethodResolutionFailed \""
           <> lowerName (methodName m) <> "\" " <> name n
           <> ") => O.MethodInfo " <> infoName <> " o p where"
  indent $ line $ "overloadedMethod _ = undefined"
  exportMethod "Unsupported methods" infoName
