{-# LANGUAGE NoImplicitPrelude   #-}
{-# LANGUAGE NoFieldSelectors    #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings   #-}

module Stack.Types.DependencyTree
  ( DependencyTree (..)
  , DotPayload (..)
  , licenseText
  , versionText
  ) where

import           Data.Aeson ( ToJSON (..), Value, (.=), object )
import qualified Data.Set as Set
import qualified Data.Map as Map
import qualified Data.Text as Text
import           Distribution.License ( License (..), licenseFromSPDX )
import qualified Distribution.SPDX.License as SPDX
import           Distribution.Text ( display )
import           Stack.Prelude hiding ( Display (..), pkgName, loadPackage )

-- | Information about a package in the dependency graph, when available.
data DotPayload = DotPayload
  { version :: Maybe Version
    -- ^ The package version.
  , license :: Maybe (Either SPDX.License License)
    -- ^ The license the package was released under.
  , location :: Maybe PackageLocation
    -- ^ The location of the package.
  }
  deriving (Eq, Show)

data DependencyTree =
  DependencyTree (Set PackageName)
                 (Map PackageName (Set PackageName, DotPayload))

instance ToJSON DependencyTree where
  toJSON (DependencyTree _ dependencyMap) =
    toJSON $ foldToList dependencyToJSON dependencyMap

foldToList :: (k -> a -> b) -> Map k a -> [b]
foldToList f = Map.foldrWithKey (\k a bs -> bs ++ [f k a]) []

dependencyToJSON :: PackageName -> (Set PackageName, DotPayload) -> Value
dependencyToJSON pkg (deps, payload) =
  let fieldsAlwaysPresent = [ "name" .= packageNameString pkg
                            , "license" .= licenseText payload
                            , "version" .= versionText payload
                            , "dependencies" .= Set.map packageNameString deps
                            ]
      loc = catMaybes
              [("location" .=) . pkgLocToJSON <$> payload.location]
  in  object $ fieldsAlwaysPresent ++ loc

pkgLocToJSON :: PackageLocation -> Value
pkgLocToJSON (PLMutable (ResolvedPath _ dir)) = object
  [ "type" .= ("project package" :: Text)
  , "url" .= ("file://" ++ toFilePath dir)
  ]
pkgLocToJSON (PLImmutable (PLIHackage pkgid _ _)) = object
  [ "type" .= ("hackage" :: Text)
  , "url" .= ("https://hackage.haskell.org/package/" ++ display pkgid)
  ]
pkgLocToJSON (PLImmutable (PLIArchive archive _)) =
  let url = case archiveLocation archive of
              ALUrl u -> u
              ALFilePath (ResolvedPath _ path) ->
                Text.pack $ "file://" ++ toFilePath path
  in  object
        [ "type" .= ("archive" :: Text)
        , "url" .= url
        , "sha256" .= archiveHash archive
        , "size" .= archiveSize archive
        ]
pkgLocToJSON (PLImmutable (PLIRepo repo _)) = object
  [ "type" .= case repoType repo of
                RepoGit -> "git" :: Text
                RepoHg -> "hg" :: Text
  , "url" .= repoUrl repo
  , "commit" .= repoCommit repo
  , "subdir" .= repoSubdir repo
  ]

licenseText :: DotPayload -> Text
licenseText payload =
  maybe "<unknown>" (Text.pack . display . either licenseFromSPDX id)
                    payload.license

versionText :: DotPayload -> Text
versionText payload =
  maybe "<unknown>" (Text.pack . display) payload.version
