{-# LANGUAGE DeriveGeneric #-}
-- | Hacks that haven't found their home yet.
module Game.LambdaHack.Common.Misc
  ( FontDefinition(..), HintingMode(..), FontSet(..)
  , makePhrase, makeSentence, squashedWWandW
  , appDataDir
  , xM, xD, minusM, minusM1, minusM2, oneM, tenthM
  , show64With2
  , workaroundOnMainThreadMVar
  ) where

import Prelude ()

import Game.LambdaHack.Core.Prelude

import           Control.Concurrent
import           Control.DeepSeq
import           Data.Binary
import qualified Data.Char as Char
import           Data.Int (Int64)
import qualified Data.Map as M
import           GHC.Generics (Generic)
import qualified NLP.Miniutter.English as MU
import           System.Directory (getAppUserDataDirectory)
import           System.Environment (getProgName)
import           System.IO.Unsafe (unsafePerformIO)

data FontDefinition =
    FontProportional Text Int HintingMode  -- ^ filename, size, hinting mode
  | FontMonospace Text Int HintingMode
  | FontMapScalable Text Int HintingMode Int  -- ^ extra cell extension
  | FontMapBitmap Text Int  -- ^ size ignored for bitmap fonts and no hinting
  deriving (Show, Eq, Read, Generic)

instance NFData FontDefinition

instance Binary FontDefinition

data HintingMode =
    HintingHeavy  -- ^ current libfreetype6 default, thin, large letter spacing
  | HintingLight  -- ^ mimics OTF, blurry, thick, tight tracking, accurate shape
  deriving (Show, Eq, Read, Generic)

instance NFData HintingMode

instance Binary HintingMode

data FontSet = FontSet
  { fontMapScalable :: Text
  , fontMapBitmap   :: Text
  , fontPropRegular :: Text
  , fontPropBold    :: Text
  , fontMono        :: Text }
  deriving (Show, Eq, Read, Generic)

instance NFData FontSet

instance Binary FontSet

-- | Re-exported English phrase creation functions, applied to our custom
-- irregular word sets.
makePhrase, makeSentence :: [MU.Part] -> Text
makePhrase = MU.makePhrase irregular
makeSentence = MU.makeSentence irregular

irregular :: MU.Irregular
irregular = MU.Irregular
  { irrPlural =
      M.fromList
        [ ("merchandise", "merchandise")
        , ("Merchandise", "Merchandise")
        , ("stomach", "stomachs") ]
            -- this is both countable and uncountable, but I use it here
            -- only as uncountable, do I overwrite the default
      `M.union` MU.irrPlural MU.defIrregular
  , irrIndefinite = MU.irrIndefinite MU.defIrregular
  }

-- | Apply the @WWandW@ constructor, first representing repetitions
-- as @CardinalWs@.
-- The parts are not sorted, only grouped, to keep the order.
-- The internal structure of speech parts is compared, not their string
-- rendering, so some coincidental clashes are avoided (and code is simpler).
squashedWWandW :: [MU.Part] -> (MU.Part, MU.Person)
squashedWWandW parts =
  let repetitions = group parts
      f [] = error $ "empty group" `showFailure` parts
      f [part] = (part, MU.Sg3rd)  -- avoid prefixing hero names with "a"
      f l@(part : _) = (MU.CardinalWs (length l) part, MU.PlEtc)
      cars = map f repetitions
      person = case cars of
        [] -> error $ "empty cars" `showFailure` parts
        [(_, person1)] -> person1
        _ -> MU.PlEtc
  in (MU.WWandW $ map fst cars, person)

-- | Personal data directory for the game. Depends on the OS and the game,
-- e.g., for LambdaHack under Linux it's @~\/.LambdaHack\/@.
appDataDir :: IO FilePath
appDataDir = do
  progName <- getProgName
  let name = takeWhile Char.isAlphaNum progName
  getAppUserDataDirectory name

-- | Multiplies by a million.
xM :: Int -> Int64
xM k = into @Int64 k * 1000000

-- | Multiplies by a million, double precision.
xD :: Double -> Double
xD k = k * 1000000

minusM, minusM1, minusM2, oneM, tenthM :: Int64
minusM = xM (-1)
minusM1 = xM (-1) - 1
minusM2 = xM (-1) - 2
oneM = xM 1
tenthM = 100000

show64With2 :: Int64 -> Text
show64With2 n =
  let k = 100 * n `divUp` oneM
      l = k `div` 100
      x = k - l * 100
      y = x `div` 10
  in tshow l
     <> if | x == 0 -> ""
           | x == y * 10 -> "." <> tshow y
           | x < 10 -> ".0" <> tshow x
           | otherwise -> "." <> tshow x

-- Global variable for passing the action to run on main thread, if any.
workaroundOnMainThreadMVar :: MVar (IO ())
{-# NOINLINE workaroundOnMainThreadMVar #-}
workaroundOnMainThreadMVar = unsafePerformIO newEmptyMVar
