{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ViewPatterns #-}
module Hpack.Syntax.BuildTools (
  BuildTools(..)
, ParseBuildTool(..)
, SystemBuildTools(..)
) where

import           Data.Text (Text)
import qualified Data.Text as T
import           Data.Semigroup (Semigroup(..))
import           Data.Bifunctor
import           Control.Applicative
import qualified Distribution.Package as D
import           Data.Map.Lazy (Map)
import qualified Data.Map.Lazy as Map

import qualified Distribution.Types.ExeDependency as D
import qualified Distribution.Types.UnqualComponentName as D
import qualified Distribution.Types.LegacyExeDependency as D

import           Data.Aeson.Config.FromValue

import           Hpack.Syntax.DependencyVersion
import           Hpack.Syntax.Dependency (parseDependency)

newtype BuildTools = BuildTools {
  unBuildTools :: [(ParseBuildTool, DependencyVersion)]
} deriving (Show, Eq, Semigroup, Monoid)

data ParseBuildTool = QualifiedBuildTool String String | UnqualifiedBuildTool String
  deriving (Show, Eq)

instance FromValue BuildTools where
  fromValue v = case v of
    String s -> BuildTools . return <$> buildToolFromString s
    Array xs -> BuildTools <$> parseArray buildToolFromValue xs
    Object _ -> BuildTools . map (first nameToBuildTool) . Map.toList <$> fromValue v
    _ -> typeMismatch "Array, Object, or String" v

nameToBuildTool :: String -> ParseBuildTool
nameToBuildTool name = case break (== ':') name of
  (executable, "") -> UnqualifiedBuildTool executable
  (package, executable) -> QualifiedBuildTool package (drop 1 executable)

buildToolFromValue :: Value -> Parser (ParseBuildTool, DependencyVersion)
buildToolFromValue v = case v of
  String s -> buildToolFromString s
  Object o -> sourceDependency o
  _ -> typeMismatch "Object or String" v
  where
    sourceDependency o = (,) <$> (nameToBuildTool <$> name) <*> (SourceDependency <$> fromValue v)
      where
        name :: Parser String
        name = o .: "name"

buildToolFromString :: Text -> Parser (ParseBuildTool, DependencyVersion)
buildToolFromString s = parseQualifiedBuildTool s <|> parseUnqualifiedBuildTool s

parseQualifiedBuildTool :: Monad m => Text -> m (ParseBuildTool, DependencyVersion)
parseQualifiedBuildTool = fmap f . cabalParse "build tool" . T.unpack
  where
    f :: D.ExeDependency -> (ParseBuildTool, DependencyVersion)
    f (D.ExeDependency package executable version) = (
        QualifiedBuildTool (D.unPackageName package) (D.unUnqualComponentName executable)
      , dependencyVersionFromCabal version
      )

parseUnqualifiedBuildTool :: Monad m => Text -> m (ParseBuildTool, DependencyVersion)
parseUnqualifiedBuildTool = fmap (first UnqualifiedBuildTool) . parseDependency "build tool"

newtype SystemBuildTools = SystemBuildTools {
  unSystemBuildTools :: Map String DependencyVersion
} deriving (Show, Eq, Semigroup, Monoid)

instance FromValue SystemBuildTools where
  fromValue v = case v of
    String s -> fromList . return <$> parseSystemBuildTool s
    Array xs -> fromList <$> parseArray (withText parseSystemBuildTool) xs
    Object _ -> SystemBuildTools <$> fromValue v
    _ -> typeMismatch "Array, Object, or String" v
    where
      fromList :: [(String, DependencyVersion)] -> SystemBuildTools
      fromList = SystemBuildTools . Map.fromList

parseSystemBuildTool :: Monad m => Text -> m (String, DependencyVersion)
parseSystemBuildTool = fmap fromCabal . parseCabalBuildTool . T.unpack
  where
    fromCabal :: D.LegacyExeDependency -> (String, DependencyVersion)
    fromCabal (D.LegacyExeDependency name version) = (name, dependencyVersionFromCabal version)

    parseCabalBuildTool :: Monad m => String -> m D.LegacyExeDependency
    parseCabalBuildTool = cabalParse "system build tool"