{-# LANGUAGE NoImplicitPrelude #-}

module Language.Haskell.Brittany.Internal.Obfuscation where

import Data.Char
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.Text as Text
import qualified GHC.OldList as List
import Language.Haskell.Brittany.Internal.Prelude
import Language.Haskell.Brittany.Internal.PreludeUtils
import System.Random



obfuscate :: Text -> IO Text
obfuscate input = do
  let predi x = isAlphaNum x || x `elem` "_'"
  let groups = List.groupBy (\a b -> predi a && predi b) (Text.unpack input)
  let idents = Set.toList $ Set.fromList $ filter (all predi) groups
  let
    exceptionFilter x | x `elem` keywords = False
    exceptionFilter x | x `elem` extraKWs = False
    exceptionFilter x = not $ null $ drop 1 x
  let filtered = filter exceptionFilter idents
  mappings <- fmap Map.fromList $ filtered `forM` \x -> do
    r <- createAlias x
    pure (x, r)
  let groups' = groups <&> \w -> fromMaybe w (Map.lookup w mappings)
  pure $ Text.concat $ fmap Text.pack groups'

keywords :: [String]
keywords =
  [ "case"
  , "class"
  , "data"
  , "default"
  , "deriving"
  , "do"
  , "mdo"
  , "else"
  , "forall"
  , "if"
  , "import"
  , "in"
  , "infix"
  , "infixl"
  , "infixr"
  , "instance"
  , "let"
  , "module"
  , "newtype"
  , "of"
  , "qualified"
  , "then"
  , "type"
  , "where"
  , "_"
  , "foreign"
  , "ccall"
  , "as"
  , "safe"
  , "unsafe"
  , "hiding"
  , "proc"
  , "rec"
  , "family"
  ]

extraKWs :: [String]
extraKWs = ["return", "pure", "Int", "True", "False", "otherwise"]

createAlias :: String -> IO String
createAlias xs = go NoHint xs
 where
  go _hint "" = pure ""
  go hint (c : cr) = do
    c' <- case hint of
      VocalHint | isUpper c -> randomFrom $ "AAAEEEOOOIIIUUU" ++ ['A' .. 'Z']
      _ | isUpper c -> randomFrom ['A' .. 'Z']
      VocalHint | isLower c -> randomFrom $ "aaaeeeoooiiiuuu" ++ ['a' .. 'z']
      _ | isLower c -> randomFrom ['a' .. 'z']
      _ -> pure c
    cr' <- go (if c' `elem` "aeuioAEUIO" then NoVocalHint else VocalHint) cr
    pure (c' : cr')

data Hint = NoHint | VocalHint | NoVocalHint

_randomRange :: Random a => a -> a -> IO a
_randomRange lo hi = do
  gen <- getStdGen
  let (x, gen') = randomR (lo, hi) gen
  setStdGen gen'
  pure x

randomFrom :: [a] -> IO a
randomFrom l = do
  let hi = length l - 1
  gen <- getStdGen
  let (x, gen') = randomR (0, hi) gen
  setStdGen gen'
  pure $ l List.!! x
