module System.Miniplex.Sekrit (
    pathFromTag
    , reallySend
    , reallyRecv
    , bytesFromInt
    , intFromBytes
    , closeOnExec
    , enoents
    , eexists
    , mode644
) where

import Control.Exception
import Control.Monad

import Foreign.C.Error

import Network.Socket

import Data.Bits

import System.Directory
import System.IO.Error
import System.Posix.Files
import System.Posix.IO
import System.Posix.Types

good :: String -> Bool
good s = not (null s) && head s /= '.' && all (`elem` chs) s
    where
    chs =
        ['a' .. 'z'] ++ ['A' .. 'Z'] ++ ['0' .. '9'] ++ ",=-#%.+_^&'@~()[]{}"

pathFromTag :: String -> String -> IO (String, String, String)
pathFromTag context what
    | not $ good what =
        ioError $ errnoToIOError context eADDRNOTAVAIL Nothing (Just what)
    | otherwise = do
        dir <- getAppUserDataDirectory "miniplex"
        createDirectoryIfMissing False dir
        let d x =  dir ++ "/" ++ x
        return (d what, d $ "L$$", d $ "R$" ++ what)

reallySend :: Socket -> String -> IO ()
reallySend sock = step
    where
    step "" = return ()
    step msg = do
        w <- send sock msg
        step (drop w msg)

reallyRecv :: Socket -> Int -> IO String
reallyRecv sock = step
    where
    step n
        | n <= 0 = return ""
        | otherwise = do
            (s, r) <- recvLen sock n
            t <- step (n - r)
            return (s ++ t)

bytesFromInt :: Int -> String
bytesFromInt n = map (toEnum . (.&.) 255 . shiftR n . (*) 8) . reverse $ [0 .. 3]

intFromBytes :: String -> Int
intFromBytes = sum . zipWith (\n c -> fromEnum c `shiftL` (8 * n)) (reverse [0 .. 3])

closeOnExec :: Socket -> IO ()
closeOnExec (MkSocket fd _ _ _ _) =
    setFdOption (Fd fd) CloseOnExec True

{-
killSocket :: Socket -> IO ()
killSocket s = do
    bracket
        (openFd "/dev/null" WriteOnly Nothing defaultFileFlags)
        closeFd
        (`dupTo` Fd (fdSocket s))
    return ()
-}

enoents :: Exception -> Maybe Exception
enoents e = do
    io <- ioErrors e
    guard $ isDoesNotExistError io
    return e

eexists :: Exception -> Maybe Exception
eexists e = do
    io <- ioErrors e
    guard $ isAlreadyExistsError io
    return e

mode644 :: FileMode
mode644 = ownerReadMode .|. ownerWriteMode .|. groupReadMode .|. otherReadMode