-- This file comes from cabal-doctest:
-- https://github.com/phadej/cabal-doctest/blob/master/simple-example
--
-- It is needed so that doctest can be run with the same options as the modules
-- are compiled with.

{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedLists #-}
{-# OPTIONS_GHC -Wall #-}
module Main (main) where

import Data.Maybe (catMaybes)
import Distribution.PackageDescription (HookedBuildInfo, cppOptions, emptyBuildInfo)
import Distribution.Simple (UserHooks, defaultMainWithHooks, preBuild, preRepl, simpleUserHooks)
import Distribution.Simple.Program (configureProgram, defaultProgramDb, getDbProgramOutput, pkgConfigProgram)
import Distribution.Text (simpleParse)
import Distribution.Verbosity (normal)
import Distribution.Version (Version, mkVersion)

#ifndef MIN_VERSION_cabal_doctest
#define MIN_VERSION_cabal_doctest(x,y,z) 0
#endif

#if MIN_VERSION_cabal_doctest(1,0,0)

import Distribution.Extra.Doctest (addDoctestsUserHook)
main :: IO ()
main = do
  cppOpts <- getTermonadCPPOpts
  defaultMainWithHooks . addPkgConfigTermonadUserHook cppOpts $
    addDoctestsUserHook "doctests" simpleUserHooks

#else

#ifdef MIN_VERSION_Cabal
-- If the macro is defined, we have new cabal-install,
-- but for some reason we don't have cabal-doctest in package-db
--
-- Probably we are running cabal sdist, when otherwise using new-build
-- workflow
#warning You are configuring this package without cabal-doctest installed. \
         The doctests test-suite will not work as a result. \
         To fix this, install cabal-doctest before configuring.
#endif

main :: IO ()
main = do
  cppOpts <- getTermonadCPPOpts
  defaultMainWithHooks $ addPkgConfigTermonadUserHook cppOpts simpleUserHooks

#endif

-- | Add CPP macros representing the version of the GTK and VTE system
-- libraries.
addPkgConfigTermonadUserHook :: [String] -> UserHooks -> UserHooks
addPkgConfigTermonadUserHook cppOpts oldUserHooks = do
  oldUserHooks
    { preBuild = pkgConfigTermonadHook cppOpts $ preBuild oldUserHooks
    , preRepl = pkgConfigTermonadHook cppOpts (preRepl oldUserHooks)
    }

pkgConfigTermonadHook :: [String] -> (args -> flags -> IO HookedBuildInfo) -> args -> flags -> IO HookedBuildInfo
pkgConfigTermonadHook cppOpts oldFunc args flags = do
  (maybeOldLibHookedInfo, oldExesHookedInfo) <- oldFunc args flags
  case maybeOldLibHookedInfo of
    Just oldLibHookedInfo -> do
      let newLibHookedInfo =
            oldLibHookedInfo
              { cppOptions = cppOptions oldLibHookedInfo <> cppOpts
              }
      pure (Just newLibHookedInfo, oldExesHookedInfo)
    Nothing -> do
      let newLibHookedInfo =
            emptyBuildInfo
              { cppOptions = cppOpts
              }
      pure (Just newLibHookedInfo, oldExesHookedInfo)

getTermonadCPPOpts :: IO [String]
getTermonadCPPOpts = do
  gtk <- getGtkVersionCPPOpts
  vte <- getVteVersionCPPOpts
  pure $ gtk <> vte

getVteVersionCPPOpts :: IO [String]
getVteVersionCPPOpts = do
  maybeVers <- getPkgConfigVersionFor "vte-2.91"
  case maybeVers of
    Nothing -> pure []
    Just vers -> pure $ createVteVersionCPPOpts vers

getGtkVersionCPPOpts :: IO [String]
getGtkVersionCPPOpts = do
  maybeVers <- getPkgConfigVersionFor "gtk+-3.0"
  case maybeVers of
    Nothing -> pure []
    Just vers -> pure $ createGtkVersionCPPOpts vers

-- | Just like 'createGtkVersionCPPOpts' but for VTE instead of GTK.
createVteVersionCPPOpts
  :: Version
  -> [String]
createVteVersionCPPOpts vers =
  catMaybes $
    [ if vers >= mkVersion [0,44] then Just "-DVTE_VERSION_GEQ_0_44" else Nothing
    ]

-- | Based on the version of the GTK3 library as reported by @pkg-config@, return
-- a list of CPP macros that contain the GTK version.  These can be used in the
-- Haskell code to work around differences in the gi-gtk library Haskell
-- library when compiled against different versions of the GTK system library.
--
-- This list may need to be added to.
createGtkVersionCPPOpts
  :: Version  -- ^ 'Version' of the GTK3 library as reported by @pkg-config@.
  -> [String] -- ^ A list of CPP macros to show the GTK version.
createGtkVersionCPPOpts gtkVersion =
  catMaybes $
    [ if gtkVersion >= mkVersion [3,22] then Just "-DGTK_VERSION_GEQ_3_22" else Nothing
    ]

getPkgConfigVersionFor :: String -> IO (Maybe Version)
getPkgConfigVersionFor program = do
  pkgDb <- configureProgram normal pkgConfigProgram defaultProgramDb
  pkgConfigOutput <-
    getDbProgramOutput normal pkgConfigProgram pkgDb ["--modversion", program]
  -- Drop the newline on the end of the pkgConfigOutput.
  -- This should give us a version number like @3.22.11@.
  let rawProgramVersion = reverse $ drop 1 $ reverse pkgConfigOutput
  let maybeProgramVersion = simpleParse rawProgramVersion
  case maybeProgramVersion of
    Nothing -> do
      putStrLn $
        "In Setup.hs, in getPkgConfigVersionFor, could not parse " <>
        program <> " version: " <> show pkgConfigOutput
      putStrLn $
        "\nNot defining any CPP macros based on the version of the system " <>
        program <> " library."
      putStrLn "\nCompilation of termonad may fail."
      pure Nothing
    Just programVersion -> pure $ Just programVersion
