{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
{-|
Module      : HsLua.Packaging.ModuleTests
Copyright   : © 2019-2023 Albert Krewinkel
License     : MIT
Maintainer  : Albert Krewinkel <tarleb@hslua.org>
Stability   : alpha
Portability : Requires GHC 8 or later.

Tests creating and loading of modules with Haskell.
-}
module HsLua.Packaging.ModuleTests (tests) where

import HsLua.Core
import HsLua.Marshalling
  ( forcePeek, peekFieldRaw, peekIntegral, peekList, peekName, peekString
  , pushIntegral, pushText)
import HsLua.Packaging.Documentation
import HsLua.Packaging.Function
import HsLua.Packaging.Module
import HsLua.Packaging.UDType (deftype, initType)
import Test.Tasty.HsLua ((=:), shouldBeResultOf)
import Test.Tasty (TestTree, testGroup)

import qualified HsLua.Core as Lua

-- | Specifications for Attributes parsing functions.
tests :: TestTree
tests = testGroup "Module"
  [ testGroup "creation helpers"
    [ "create produces a table" =:
      Lua.TypeTable `shouldBeResultOf` do
        Lua.newtable
        Lua.ltype Lua.top
    ]
  , testGroup "module type"
    [ "register module" =:
      1 `shouldBeResultOf` do
        Lua.openlibs
        old <- Lua.gettop
        registerModule mymath
        new <- Lua.gettop
        return (new - old)

    , "call module function" =:
      24 `shouldBeResultOf` do
        Lua.openlibs
        registerModule mymath
        _ <- Lua.dostring $ mconcat
             [ "local mymath = require 'mymath'\n"
             , "return mymath.factorial(4)"
             ]
        forcePeek $ peekIntegral @Prelude.Integer Lua.top

    , "call module as function" =:
      "call me maybe" `shouldBeResultOf` do
        Lua.openlibs
        registerModule mymath
        _ <- Lua.dostring "return (require 'mymath')()"
        forcePeek $ peekString Lua.top

    , "access name in docs" =:
      "mymath" `shouldBeResultOf` do
        Lua.openlibs
        registerModule mymath
        TypeTable <- getdocumentation top
        forcePeek $ peekFieldRaw peekString "name" Lua.top

    , "first function name in docs" =:
      "factorial" `shouldBeResultOf` do
        Lua.openlibs
        registerModule mymath
        TypeTable <- getdocumentation top
        TypeTable <- getfield top "functions"
        TypeTable <- rawgeti top 1
        forcePeek $ peekFieldRaw peekString "name" Lua.top

    , "function doc is shared" =:
      True `shouldBeResultOf` do
        Lua.openlibs
        registerModule mymath
        pushvalue top
        setglobal "mymath"
        -- get doc table via module docs
        TypeTable <- getdocumentation top
        TypeTable <- getfield top "functions"
        TypeTable <- rawgeti top 1
        -- get doc table via function
        OK <- dostring "return mymath.factorial"
        TypeTable <- getdocumentation top
        -- must be the same
        rawequal (nth 1) (nth 3)

    , "first field name in docs" =:
      "unit" `shouldBeResultOf` do
        Lua.openlibs
        registerModule mymath
        TypeTable <- getdocumentation top
        TypeTable <- getfield top "fields"
        TypeTable <- rawgeti top 1
        forcePeek $ peekFieldRaw peekString "name" Lua.top

    , "document object has associated types" =:
      ["Void"] `shouldBeResultOf` do
        Lua.openlibs
        registerModule mymath
        TypeTable <- getdocumentation top
        TypeFunction <- getfield top "types"
        call 0 1
        forcePeek $ peekList peekName top
    ]
  ]

mymath :: Module Lua.Exception
mymath = Module
  { moduleName = "mymath"
  , moduleDescription = "A math module."
  , moduleFields = [
      Field "unit" "integer" "additive unit" (pushinteger 1)
    ]
  , moduleFunctions = [factorial]
  , moduleOperations =
    [ (,) Call $ lambda
      ### (1 <$ pushText "call me maybe")
      =?> "call result"
    ]
  , moduleTypeInitializers = [initType (deftype "Void" [] [])]
  }

factorial :: DocumentedFunction Lua.Exception
factorial =
  defun "factorial"
  ### liftPure (\n -> product [1..n])
  <#> factorialParam
  =#> factorialResult

factorialParam :: Parameter Lua.Exception Prelude.Integer
factorialParam =
  parameter peekIntegral "integer"
    "n"
    "number for which the factorial is computed"

factorialResult :: FunctionResults Lua.Exception Prelude.Integer
factorialResult = functionResult pushIntegral "integer" "factorial"
