{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeSynonymInstances #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Sindre.Lib
-- License     :  MIT-style (see LICENSE)
--
-- Stability   :  provisional
-- Portability :  portable
--
-- Library routines and helper functions for the Sindre programming
-- language.
--
-----------------------------------------------------------------------------
module Sindre.Lib ( stdFunctions
                  , ioFunctions
                  , ioGlobals
                  , LiftFunction(..)
                  , KeyLike(..)
                  )
    where

import Sindre.Sindre
import Sindre.Compiler
import Sindre.Runtime
import Sindre.Util

import System.Environment
import System.Exit
import System.IO
import System.Process hiding (env)
import Text.Regex.PCRE

import Control.Monad
import Control.Monad.Trans
import Data.Char
import Data.List
import qualified Data.Map as M
import qualified Data.Set as S

lengthFun :: Value -> Integer
lengthFun (Dict m) = fi $ M.size m
lengthFun v = maybe 0 genericLength (mold v :: Maybe String)

builtin :: LiftFunction im m a => a -> Compiler im ([Value] -> m im Value)
builtin f = return $ function f

-- | A set of pure functions that can work with any Sindre backend.
-- Includes the functions @abs@, @atan2@, @cos@, @sin@, @exp@, @log@,
-- @int@, @sqrt@, @length@, @substr@, @index@, @match@, @sub@, @gsub@,
-- @tolower@, and @toupper@.
stdFunctions :: forall im. MonadBackend im => FuncMap im
stdFunctions = M.fromList
               [ ("abs" , builtin $ return' . (abs :: Int -> Int))
               , ("atan2", builtin $ \(x::Double) (y::Double) ->
                    return' $ atan2 x y)
               , ("cos", builtin $ return' . (cos :: Double -> Double))
               , ("sin", builtin $ return' . (sin :: Double -> Double))
               , ("exp", builtin $ return' . (exp :: Double -> Double))
               , ("log", builtin $ return' . (log :: Double -> Double))
               , ("int", builtin $ return' . (floor :: Double -> Integer))
               , ("sqrt", builtin $ return' . (sqrt :: Double -> Double))
               , ("length", builtin $ return' . lengthFun)
               , ("substr", builtin $ \(s::String) m n ->
                   return' $ take n $ drop (m-1) s)
               , ("index",  builtin $ \(s::String) t ->
                   return' $ maybe 0 (1+) $ findIndex (isPrefixOf t) $ tails s)
               , ("match", do
                     rstart  <- setValue "RSTART"
                     rlength <- setValue "RLENGTH"
                     return $ function $ \(s::String) (r::String) -> do
                       let (stt, len) = s =~ r :: (Int, Int)
                       execute_ $ do rstart $ unmold (stt+1)
                                     rlength $ unmold len
                       return' $ unmold (stt+1))
               , ("sub", builtin sub)
               , ("gsub", builtin gsub)
               , ("tolower", builtin $ return' . map toLower)
               , ("toupper", builtin $ return' . map toUpper)
               ]
    where return' :: Mold a => a -> Sindre im a
          return' = return
          sub (r::String) t (s::String) =
            case s =~ r of
              (-1,_) -> return' s
              (i,n)  -> return' $ take i s ++ t ++ drop (i+n) s
          gsub (r::String) t (s::String) =
            case s =~ r of
              (-1,_) -> return' s
              (i,n)  -> do s' <- gsub r t $ drop (i+n) s
                           return' $ take i s ++ t ++ s'
-- | A set of impure functions that only work in IO backends.
-- Includes the @system@ function.
ioFunctions :: (MonadIO m, MonadBackend m) => FuncMap m
ioFunctions = M.fromList
              [ ("system", do
                   exitval <- setValue "EXITVAL"
                   builtin $ \s -> do
                     c <- io $ system s
                     let v = case c of ExitSuccess   -> 0
                                       ExitFailure e -> e
                     execute_ $ exitval $ unmold v
                     return' v)
              , ("osystem", do
                    exitval <- setValue "EXITVAL"
                    return $ function $ \s -> do
                      (Just inh, Just outh, _, pid) <-
                        io $ createProcess (shell s) { std_in  = CreatePipe,
                                                       std_out = CreatePipe,
                                                       std_err = Inherit }
                      io $ hClose inh
                      output <- io $ hGetContents outh
                      ex <- io $ waitForProcess pid
                      execute_ $ exitval $ unmold $ case ex of
                        ExitSuccess   -> 0
                        ExitFailure r -> r
                      return' output)
              ]
    where return' :: Mold a => a -> Sindre im a
          return' = return

-- | Global variables that require an IO backend.  Includes the
-- @ENVIRON@ global.
ioGlobals :: MonadIO im => M.Map Identifier (im Value)
ioGlobals = M.fromList [("ENVIRON", do
                           env <- io getEnvironment
                           let f (k, s) = (unmold k, unmold s)
                           return $ Dict $ M.fromList $ map f env)
                       ]

-- | A class making it easy to adapt Haskell functions as Sindre
-- functions that take and return 'Value's.
class (MonadBackend im, MonadSindre im m) => LiftFunction im m a where
  function :: a -> [Value] -> m im Value
  -- ^ @function f@ is a monadic function that accepts a list of
  -- 'Value's and returns a 'Value'.  If the list does not contain the
  -- number, or type, of arguments expected by @f@, 'fail' will be
  -- called with an appropriate error message.

instance (Mold a, MonadSindre im m) => LiftFunction im m (m im a) where
  function x [] = liftM unmold x
  function _ _ = fail "Too many arguments"

instance (Mold a, LiftFunction im m b, MonadSindre im m)
    => LiftFunction im m (a -> b) where
  function f (x:xs) = case mold x of
                        Nothing -> fail "Cannot mold argument"
                        Just x' -> f x' `function` xs
  function _ [] = fail "Not enough arguments"

-- | Convenience class for writing 'Chord' values.
class KeyLike a where
  chord :: [KeyModifier] -> a -> Chord
  -- ^ Given a list of modifiers and either a 'char' or a 'String',
  -- yield a 'Chord'.  If given a character, the Chord will contain a
  -- 'CharKey', if given a string, it will contain a 'CtrlKey'.

instance KeyLike Char where
  chord ms c = (S.fromList ms, CharKey c)

instance KeyLike String where
  chord ms s = (S.fromList ms, CtrlKey s)