{-# LANGUAGE OverloadedLists, OverloadedStrings, QuasiQuotes,
             ScopedTypeVariables #-}
module Nirum.PackageSpec where

import Data.Either (isLeft, isRight)
import Data.Proxy (Proxy (Proxy))
import Data.Text
import System.IO.Error (isDoesNotExistError)

import qualified Data.SemVer as SV
import System.FilePath ((</>))
import Test.Hspec.Meta
import Text.InterpolatedString.Perl6 (qq)
import qualified Text.Parsec.Error as PE
import Text.Parsec.Pos (sourceColumn, sourceLine)

import Nirum.Constructs.Module
import Nirum.Constructs.ModulePath (ModulePath)
import Nirum.Package hiding (modules, target)
import Nirum.Package.Metadata ( Metadata ( Metadata
                                         , authors
                                         , target
                                         , version
                                         , description
                                         , license
                                         , keywords
                                         )
                              , MetadataError (FormatError)
                              , Target (targetName)
                              )
import Nirum.Package.MetadataSpec (DummyTarget (DummyTarget))
import Nirum.Package.ModuleSet ( ImportError (MissingModulePathError)
                               , fromList
                               )
import Nirum.Package.ModuleSetSpec (validModules)
import Nirum.Parser (parseFile)
import Nirum.Targets.Python (Python (Python))
import Nirum.Targets.Python.CodeGen (minimumRuntime)

createPackage :: Metadata t -> [(ModulePath, Module)] -> Package t
createPackage metadata' modules' =
    case fromList modules' of
        Right ms -> Package metadata' ms
        Left e -> error $ "errored: " ++ show e

createValidPackage :: t -> Package t
createValidPackage t = createPackage Metadata { version = SV.initial
                                              , authors = []
                                              , description = Nothing
                                              , license = Nothing
                                              , keywords = []
                                              , target = t
                                              } validModules

spec :: Spec
spec = do
    testPackage (Python "nirum-examples" minimumRuntime [] classifiers')
    testPackage DummyTarget
  where
    classifiers' :: [Text]
    classifiers' =
        [ "Development Status :: 3 - Alpha"
        , append "License :: OSI Approved :: "
                 "GNU General Public License v3 or later (GPLv3+)"
        ]

testPackage :: forall t . Target t => t -> Spec
testPackage target' = do
    let targetName' = targetName (Proxy :: Proxy t)
        validPackage = createValidPackage target'
    describe [qq|Package (target: $targetName')|] $ do
        specify "resolveModule" $ do
            resolveModule ["foo"] validPackage `shouldBe`
                Just (Module [] $ Just "foo")
            resolveModule ["foo", "bar"] validPackage `shouldBe`
                Just (Module [] $ Just "foo.bar")
            resolveModule ["qux"] validPackage `shouldBe`
                Just (Module [] $ Just "qux")
            resolveModule ["baz"] validPackage `shouldBe` Nothing
        describe "scanPackage" $ do
            it "returns Package value when all is well" $ do
                let path = "." </> "examples"
                package' <- scanPackage' path
                package' `shouldSatisfy` isRight
                let Right package = package'
                Right blockchainM <- parseFile (path </> "blockchain.nrm")
                Right builtinsM <- parseFile (path </> "builtins.nrm")
                Right productM <- parseFile (path </> "product.nrm")
                Right shapesM <- parseFile (path </> "shapes.nrm")
                Right countriesM <- parseFile (path </> "countries.nrm")
                Right addressM <- parseFile (path </> "address.nrm")
                Right pdfServiceM <- parseFile (path </> "pdf-service.nrm")
                Right geoM <- parseFile (path </> "geo.nrm")
                let modules = [ (["blockchain"], blockchainM)
                              , (["builtins"], builtinsM)
                              , (["geo"], geoM)
                              , (["product"], productM)
                              , (["shapes"], shapesM)
                              , (["countries"], countriesM)
                              , (["address"], addressM)
                              , (["pdf-service"], pdfServiceM)
                              ] :: [(ModulePath, Module)]
                    metadata' = Metadata { version = SV.version 0 3 0 [] []
                                         , authors = []
                                         , description = Nothing
                                         , license = Nothing
                                         , keywords = []
                                         , target = target'
                                         }
                metadata package `shouldBe` metadata'
                package `shouldBe` createPackage metadata' modules
            let testDir = "." </> "test"
            it "returns ScanError if the directory lacks package.toml" $ do
                scanResult <- scanPackage' $ testDir </> "scan_error"
                scanResult `shouldSatisfy` isLeft
                let Left (ScanError filePath ioError') = scanResult
                filePath `shouldBe` testDir </> "scan_error" </> "package.toml"
                ioError' `shouldSatisfy` isDoesNotExistError
            it "returns MetadataError if the package.toml is invalid" $ do
                scanResult <- scanPackage' $ testDir </> "metadata_error"
                scanResult `shouldSatisfy` isLeft
                let Left (MetadataError (FormatError e)) = scanResult
                sourceLine (PE.errorPos e) `shouldBe` 3
                sourceColumn (PE.errorPos e) `shouldBe` 14
            it "returns ImportError if a module imports an absent module" $ do
                scanResult <- scanPackage' $ testDir </> "import_error"
                scanResult `shouldSatisfy` isLeft
                let Left (ImportError l) = scanResult
                l `shouldBe` [MissingModulePathError ["import_error"] ["foo"]]
        specify "scanModules" $ do
            let path = "." </> "examples"
            mods' <- scanModules path
            mods' `shouldBe` [ (["blockchain"], path </> "blockchain.nrm")
                             , (["builtins"], path </> "builtins.nrm")
                             , (["geo"], path </> "geo.nrm")
                             , (["product"], path </> "product.nrm")
                             , (["shapes"], path </> "shapes.nrm")
                             , (["countries"], path </> "countries.nrm")
                             , (["address"], path </> "address.nrm")
                             , (["pdf-service"], path </> "pdf-service.nrm")
                             ]
  where
    scanPackage' :: FilePath -> IO (Either PackageError (Package t))
    scanPackage' = scanPackage
