{-# OPTIONS_GHC -Wno-orphans #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE FlexibleInstances #-}

{-|
Module      : GHCup.Types.Stack
Description : GHCup types.Stack
Copyright   : (c) Julian Ospald, 2023
License     : LGPL-3.0
Maintainer  : hasufell@hasufell.de
Stability   : experimental
Portability : portable
-}
module GHCup.Types.Stack where

import           GHCup.Types.JSON.Versions ()

import           Control.Applicative
import           Control.DeepSeq                ( NFData )
import           Data.ByteString
import           Data.Aeson
import           Data.Aeson.Types
import           Data.Map.Strict                ( Map )
import           Data.Text                      ( Text )
import           Data.Text.Encoding
import           Data.Versions

import qualified Data.Map as Map
import qualified GHC.Generics                  as GHC


    --------------------------------------
    --[ Stack download info copy pasta ]--
    --------------------------------------

data SetupInfo = SetupInfo
  { siSevenzExe :: Maybe DownloadInfo
  , siSevenzDll :: Maybe DownloadInfo
  , siMsys2     :: Map Text VersionedDownloadInfo
  , siGHCs      :: Map Text (Map Version GHCDownloadInfo)
  , siStack     :: Map Text (Map Version DownloadInfo)
  }
  deriving (Show, Eq, GHC.Generic)

instance NFData SetupInfo

instance FromJSON SetupInfo where
  parseJSON = withObject "SetupInfo" $ \o -> do
    siSevenzExe <- o .:? "sevenzexe-info"
    siSevenzDll <- o .:? "sevenzdll-info"
    siMsys2     <- o .:? "msys2"          .!= mempty
    siGHCs      <- o .: "ghc"
    siStack     <- o .:? "stack"          .!= mempty
    pure SetupInfo {..}

instance ToJSON SetupInfo where
  toJSON (SetupInfo {..}) = object [ "sevenzexe-info" .= siSevenzExe
                                   , "sevenzdll-info" .= siSevenzDll
                                   , "msys2"          .= siMsys2
                                   , "ghc"            .= siGHCs
                                   , "stack"          .= siStack
                                   ]

-- | For the @siGHCs@ field maps are deeply merged. For all fields the values
-- from the first @SetupInfo@ win.
instance Semigroup SetupInfo where
  l <> r =
    SetupInfo
    { siSevenzExe = siSevenzExe l <|> siSevenzExe r
    , siSevenzDll = siSevenzDll l <|> siSevenzDll r
    , siMsys2     = siMsys2 l <> siMsys2 r
    , siGHCs      = Map.unionWith (<>) (siGHCs l) (siGHCs r)
    , siStack     = Map.unionWith (<>) (siStack l) (siStack r) }

instance Monoid SetupInfo where
  mempty =
    SetupInfo
    { siSevenzExe = Nothing
    , siSevenzDll = Nothing
    , siMsys2     = Map.empty
    , siGHCs      = Map.empty
    , siStack     = Map.empty
    }
  mappend = (<>)

-- | Build of the compiler distribution (e.g. standard, gmp4, tinfo6)
-- | Information for a file to download.
data DownloadInfo = DownloadInfo
  { downloadInfoUrl           :: Text
    -- ^ URL or absolute file path
  , downloadInfoContentLength :: Maybe Int
  , downloadInfoSha1          :: Maybe ByteString
  , downloadInfoSha256        :: Maybe ByteString
  }
  deriving (Show, Eq, GHC.Generic)

instance ToJSON DownloadInfo where
  toJSON (DownloadInfo {..}) = object [ "url"            .= downloadInfoUrl
                                      , "content-length" .= downloadInfoContentLength
                                      , "sha1"           .= (decodeUtf8 <$> downloadInfoSha1)
                                      , "sha256"         .= (decodeUtf8 <$> downloadInfoSha256)
                                      ]

instance NFData DownloadInfo

instance FromJSON DownloadInfo where
  parseJSON = withObject "DownloadInfo" parseDownloadInfoFromObject

-- | Parse JSON in existing object for 'DownloadInfo'
parseDownloadInfoFromObject :: Object -> Parser DownloadInfo
parseDownloadInfoFromObject o = do
  url           <- o .: "url"
  contentLength <- o .:? "content-length"
  sha1TextMay   <- o .:? "sha1"
  sha256TextMay <- o .:? "sha256"
  pure
    DownloadInfo
    { downloadInfoUrl           = url
    , downloadInfoContentLength = contentLength
    , downloadInfoSha1          = fmap encodeUtf8 sha1TextMay
    , downloadInfoSha256        = fmap encodeUtf8 sha256TextMay
    }

data VersionedDownloadInfo = VersionedDownloadInfo
  { vdiVersion      :: Version
  , vdiDownloadInfo :: DownloadInfo
  }
  deriving (Show, Eq, GHC.Generic)

instance ToJSON VersionedDownloadInfo where
  toJSON (VersionedDownloadInfo {vdiDownloadInfo = DownloadInfo{..}, ..})
    = object [ "version"        .= vdiVersion
             , "url"            .= downloadInfoUrl
             , "content-length" .= downloadInfoContentLength
             , "sha1"           .= (decodeUtf8 <$> downloadInfoSha1)
             , "sha256"         .= (decodeUtf8 <$> downloadInfoSha256)
             ]

instance NFData VersionedDownloadInfo

instance FromJSON VersionedDownloadInfo where
  parseJSON = withObject "VersionedDownloadInfo" $ \o -> do
    ver'         <- o .: "version"
    downloadInfo <- parseDownloadInfoFromObject o
    pure VersionedDownloadInfo
      { vdiVersion      = ver'
      , vdiDownloadInfo = downloadInfo
      }

data GHCDownloadInfo = GHCDownloadInfo
  { gdiConfigureOpts :: [Text]
  , gdiConfigureEnv  :: Map Text Text
  , gdiDownloadInfo  :: DownloadInfo
  }
  deriving (Show, Eq, GHC.Generic)

instance NFData GHCDownloadInfo

instance ToJSON GHCDownloadInfo where
  toJSON (GHCDownloadInfo {gdiDownloadInfo = DownloadInfo {..}, ..})
    = object [ "configure-opts" .= gdiConfigureOpts
             , "configure-env"  .= gdiConfigureEnv
             , "url"            .= downloadInfoUrl
             , "content-length" .= downloadInfoContentLength
             , "sha1"           .= (decodeUtf8 <$> downloadInfoSha1)
             , "sha256"         .= (decodeUtf8 <$> downloadInfoSha256)
             ]

instance FromJSON GHCDownloadInfo where
  parseJSON = withObject "GHCDownloadInfo" $ \o -> do
    configureOpts <- o .:? "configure-opts" .!= mempty
    configureEnv  <- o .:? "configure-env"  .!= mempty
    downloadInfo  <- parseDownloadInfoFromObject o
    pure GHCDownloadInfo
      { gdiConfigureOpts = configureOpts
      , gdiConfigureEnv  = configureEnv
      , gdiDownloadInfo  = downloadInfo
      }

