{-# LANGUAGE CPP                #-}
{-# LANGUAGE DeriveDataTypeable #-}

-- | Module to be shared between server and client.
--
-- This module must be valid for both GHC and Fay.
--
-- For GHC this is an alias for Data.Text, for Fay it's an opaque data type represented by JavaScript strings.
--

module Fay.Text
  ( module Fay.Text.Type
  , empty
  , cons
  , snoc
  , append
  , uncons
  , head
  , last
  , tail
  , init
  , null
  , length
  , map
  , intercalate
  , intersperse
  , reverse
  , toLower
  , toUpper
  , concat
  , concatMap
  , any
  , all
  , maximum
  , minimum
  ) where

import           Fay.Text.Type
import           Prelude (Bool, Char, Int, Maybe)

#ifdef FAY
import           Data.Data
import           FFI

empty :: Text
empty = ffi "''"

cons :: Char -> Text -> Text
cons = ffi "%1 + %2"

snoc :: Text -> Char -> Text
snoc = ffi "%1 + %2"

append :: Text -> Text -> Text
append = ffi "%1 + %2"

uncons :: Text -> Maybe (Char, Text)
uncons = ffi "%1[0] ? { instance: 'Just', slot1 : [%1[0],%1.slice(1)] } : { instance : 'Nothing' }"

head :: Text -> Char
head = ffi "%1[0] || (function () {throw new Error('Fay.Text.head: empty Text'); }())"

last :: Text -> Char
last = ffi "%1.length ? %1[%1.length-1] : (function() { throw new Error('Fay.Text.last: empty Text') })()"

tail :: Text -> Text
tail = ffi "%1.length ? %1.slice(1) : (function () { throw new Error('Fay.Text.tail: empty Text') })()"

init :: Text -> Text
init = ffi "%1.length ? %1.slice(0,-1) : (function () { throw new Error('Fay.Text.init: empty Text') })()"

null :: Text -> Bool
null = ffi "!(%1.length)"

length :: Text -> Int
length = ffi "%1.length"

map :: (Char -> Char) -> Text -> Text
map = ffi "[].map.call(%2, %1).join('')"

intercalate :: Text -> [Text] -> Text
intercalate = ffi "%2.join(%1)"

intersperse :: Char -> Text -> Text
intersperse = ffi "%2.split('').join(%1)"

-- TODO transpose

reverse :: Text -> Text
reverse = ffi "%1.split('').reverse().join('')"


-- TODO replace: Can't use String:replace.

-- TODO toCaseFold

toLower :: Text -> Text
toLower = ffi "%1.toLowerCase()"

toUpper :: Text -> Text
toUpper = ffi "%1.toUpperCase()"

-- TODO justifyLeft, justifyRight, center

-- TODO foldl, foldl' foldl1', foldr, foldr1

concat :: [Text] -> Text
concat = ffi "%1.join('')"

concatMap :: (Char -> Text) -> Text -> Text
concatMap = ffi "[].map.call(%2, %1).join('')"

any :: (Char -> Bool) -> Text -> Bool
any = ffi "[].filter.call(%2, %1).length > 0"

all :: (Char -> Bool) -> Text -> Bool
all = ffi "[].filter.call(%2, %1).length == %1.length"

maximum :: Text -> Char
maximum = ffi "(function (s) { \
  \   if (s === '') { throw new Error('Fay.Text.maximum: empty string'); } \
  \   var max = s[0]; \
  \   for (var i = 1; i < s.length; s++) { \
  \     if (s[i] > max) { max = s[i]; } \
  \   } \
  \   return max; \
  \ })(%1)"

minimum :: Text -> Char
minimum = ffi "(function (s) { \
  \   if (s === '') { throw new Error('Fay.Text.maximum: empty string'); } \
  \   var min = s[0]; \
  \   for (var i = 1; i < s.length; s++) { \
  \     if (s[i] < min) { min = s[i]; } \
  \   } \
  \   return min; \
  \ })(%1)"

-- TODO scanl, scanl1, scanr, scanr1
-- TODO mapAccumL, mapAccumR

-- TODO replicate, unfoldr, unforldrN, take, drop, takeWhile, dropWhile, dropWhileEnd, dropAround, strip, stripStart, stripEnd, splitAt, breakOn, breakOnEnd, break, span, group, groupBy, inits, tails

-- TODO splitOn, split, chunksOf

-- TODO lines, words, unlines, unwords

-- TODO isPrefixOf, isSuffixOf, isInfixOf

-- TODO stripPrefix, stripSuffix, commonPrefixes

-- TODO filter, breakOnAll, find, partition

-- TODO index, findIndex, count

-- TODO zip, zipWith

#else

import qualified Data.Text as T

empty       :: Text
empty       = T.empty
cons        :: Char -> Text -> Text
cons        = T.cons
snoc        :: Text -> Char -> Text
snoc        = T.snoc
append      :: Text -> Text -> Text
append      = T.append
uncons      :: Text -> Maybe (Char, Text)
uncons      = T.uncons
head        :: Text -> Char
head        = T.head
last        :: Text -> Char
last        = T.last
tail        :: Text -> Text
tail        = T.tail
init        :: Text -> Text
init        = T.init
null        :: Text -> Bool
null        = T.null
length      :: Text -> Int
length      = T.length
map         :: (Char -> Char) -> Text -> Text
map         = T.map
intercalate :: Text -> [Text] -> Text
intercalate = T.intercalate
intersperse :: Char -> Text -> Text
intersperse = T.intersperse
reverse     :: Text -> Text
reverse     = T.reverse
toLower     :: Text -> Text
toLower     = T.toLower
toUpper     :: Text -> Text
toUpper     = T.toUpper
concat      :: [Text] -> Text
concat      = T.concat
concatMap   :: (Char -> Text) -> Text -> Text
concatMap   = T.concatMap
any         :: (Char -> Bool) -> Text -> Bool
any         = T.any
all         :: (Char -> Bool) -> Text -> Bool
all         = T.all
maximum     :: Text -> Char
maximum     = T.maximum
minimum     :: Text -> Char
minimum     = T.minimum

#endif