content: {-# OPTIONS -fno-warn-missing-methods #-}
  {-# LANGUAGE DeriveDataTypeable #-}
  {-# LANGUAGE EmptyDataDecls #-}
  {-# LANGUAGE NoImplicitPrelude #-}
  {-# LANGUAGE NoRebindableSyntax #-}
  {-# LANGUAGE PackageImports #-}
  {-# LANGUAGE StandaloneDeriving #-}
  
  -- | Compatible API with the `text' package.
  
  module Data.Text
    ( Text
    -- * Creation and elimination
    , pack
    , unpack
    , fromString
    , empty
    -- * Conversions
    , showInt
    , toShortest
    -- * I/O
    , putStrLn
    -- * Breaking into many substrings
    , splitOn
    , stripSuffix
    -- * Basic interface
    , cons
    , snoc
    , append
    , (<>)
    , uncons
    , head
    , init
    , last
    , tail
    , null
    , length
    -- * Special folds
    , maximum
    , all
    , any
    , concatMap
    , concat
    , minimum
    -- * Case conversion
    , toLower
    , toUpper
    -- * Transformations
    , map
    , intercalate
    , intersperse
    , reverse
    -- * Predicates
    , isPrefixOf
    -- * Substrings
    , drop
    , take
    -- * Breaking into lines and words
    , unlines
    , lines
    ) where
  
  import Data.Data
  import FFI
  import Data.Nullable (fromNullable)
  import Prelude (Eq,String,Int,Bool,Char,Maybe,Double,Ord,Show,error)
  import qualified "base" Data.String as B (IsString (..))
  
  -- | A space efficient, packed, unboxed Unicode text type.
  data Text
  deriving instance Eq Text
  deriving instance Data Text
  deriving instance Typeable Text
  deriving instance Show Text
  instance Ord Text
  instance B.IsString Text where fromString = error "the method fromString can never be called"
  
  -- | O(n) The intercalate function takes a Text and a list of Texts and
  -- concatenates the list after interspersing the first argument
  -- between each element of the list.
  intercalate :: Text -> [Text] -> Text
  intercalate = ffi "%2.join(%1)"
  
  -- | Convert from a string to text.
  fromString :: String -> Text
  fromString = ffi "%1"
  
  -- | O(n) Adds a character to the end of a Text. This copies the
  -- entire array in the process, unless fused. Subject to
  -- fusion. Performs replacement on invalid scalar values.
  snoc :: Text -> Char -> Text
  snoc = ffi "%1 + %2"
  
  -- | O(n) Adds a character to the front of a Text. This function is
  -- more costly than its List counterpart because it requires copying a
  -- new array. Subject to fusion. Performs replacement on invalid
  -- scalar values.
  cons :: Char -> Text -> Text
  cons = ffi "%1 + %2"
  
  -- | O(n) Convert a String into a Text. Subject to fusion. Performs
  -- replacement on invalid scalar values.
  pack :: String -> Text
  pack = ffi "%1"
  
  -- | O(n) Convert a Text into a String. Subject to fusion.
  unpack :: Text -> String
  unpack = ffi "%1"
  
  -- | O(n) Appends one Text to the other by copying both of them into a
  -- new Text. Subject to fusion.
  append :: Text -> Text -> Text
  append = ffi "%1 + %2"
  
  -- | Append two texts.
  (<>) :: Text -> Text -> Text
  (<>) = ffi "%1 + %2"
  
  -- | O(n) Returns the number of characters in a Text. Subject to
  -- fusion.
  length :: Text -> Int
  length = ffi "%1.length"
  
  -- | O(1) Tests whether a Text is empty or not. Subject to fusion.
  null :: Text -> Bool
  null = ffi "%1.length == 0"
  
  -- | O(n) take n, applied to a Text, returns the prefix of the Text of
  -- length n, or the Text itself if n is greater than the length of the
  -- Text. Subject to fusion.
  take :: Int -> Text -> Text
  take = ffi "%2.substring(0,%1)"
  
  -- | O(n) drop n, applied to a Text, returns the suffix of the Text
  -- after the first n characters, or the empty Text if n is greater
  -- than the length of the Text. Subject to fusion.
  drop :: Int -> Text -> Text
  drop = ffi "%2.substring(%1)"
  
  -- | O(1) The empty Text.
  
  -- Basic interface
  empty :: Text
  empty = ffi "\"\""
  
  -- | O(n) Breaks a Text up into a list of Texts at newline Chars. The
  -- resulting strings do not contain newlines.
  lines :: Text -> [Text]
  lines = ffi "%1.split('\\n')"
  
  -- | O(n) Joins lines, after appending a terminating newline to each.
  unlines :: [Text] -> Text
  unlines = ffi "%1.join('\\n')"
  
  -- | O(n) The isPrefixOf function takes two Texts and returns True iff
  -- the first is a prefix of the second. Subject to fusion.
  -- http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html
  isPrefixOf :: Text -> Text -> Bool
  isPrefixOf = ffi "%2.lastIndexOf(%1, 0) == 0"
  
  -- | O(n) The intersperse function takes a character and places it
  -- between the characters of a Text.  Subject to fusion. Performs
  -- replacement on invalid scalar values.
  intersperse :: Char -> Text -> Text
  intersperse = ffi "%2.split('').join(%1)"
  
  -- | O(n) Reverse the characters of a string. Subject to fusion.
  reverse :: Text -> Text
  reverse = ffi "%1.split('').reverse().join('')"
  
  -- | O(n) Return the prefix of the second string if its suffix matches
  -- the entire first string.
  stripSuffix :: Text -- ^ Suffix.
                  -> Text -- ^ Text.
                  -> Maybe Text
  stripSuffix prefix text =
    fromNullable (extract prefix text)
    where extract :: Text -> Text -> Nullable Text
          extract =
            ffi "(function(suffix,text){ return text.substring(text.length - suffix.length) == suffix? text.substring(0,text.length - suffix.length) : null; })(%1,%2)"
  
  -- | O(m+n) Break a Text into pieces separated by the first Text
  -- argument, consuming the delimiter. An empty delimiter is
  -- invalid, and will cause an error to be raised.
  splitOn :: Text -> Text -> [Text]
  splitOn = ffi "%2.split(%1)"
  
  -- |
  putStrLn :: Text -> Fay ()
  putStrLn = ffi "console.log('%%s',%1)"
  
  -- |
  toShortest :: Double -> Text
  toShortest = ffi "%1.toString()"
  
  -- |
  showInt :: Int -> Text
  showInt = ffi "%1.toString()"
  
  -- | O(1) Returns the first character and rest of a Text, or Nothing
  -- if empty. Subject to fusion.
  uncons :: Text -> Maybe (Char, Text)
  uncons = ffi "%1[0] ? { instance: 'Just', slot1 : [%1[0],%1.slice(1)] } : { instance : 'Nothing' }"
  
  -- | O(1) Returns the first character of a Text, which must be
  -- non-empty. Subject to fusion.
  head :: Text -> Char
  head = ffi "%1[0] || (function () {throw new Error('Data.Text.head: empty Text'); }())"
  
  -- | O(1) Returns the last character of a Text, which must be
  -- non-empty. Subject to fusion.
  last :: Text -> Char
  last = ffi "%1.length ? %1[%1.length-1] : (function() { throw new Error('Data.Text.last: empty Text') })()"
  
  -- | O(1) Returns all characters after the head of a Text, which must
  -- be non-empty. Subject to fusion.
  tail :: Text -> Text
  tail = ffi "%1.length ? %1.slice(1) : (function () { throw new Error('Data.Text.tail: empty Text') })()"
  
  -- | O(1) Returns all but the last character of a Text, which must be
  -- non-empty. Subject to fusion.
  init :: Text -> Text
  init = ffi "%1.length ? %1.slice(0,-1) : (function () { throw new Error('Data.Text.init: empty Text') })()"
  
  -- | O(n) map f t is the Text obtained by applying f to each element
  -- of t. Subject to fusion. Performs replacement on invalid scalar
  -- values.
  map :: (Char -> Char) -> Text -> Text
  map = ffi "[].map.call(%2, %1).join('')"
  
  -- | O(n) Convert a string to lower case, using simple case
  -- conversion. The result string may be longer than the input
  -- string. For instance, "İ" (Latin capital letter I with dot above,
  -- U+0130) maps to the sequence "i" (Latin small letter i, U+0069)
  -- followed by " ̇" (combining dot above, U+0307).
  toLower :: Text -> Text
  toLower = ffi "%1.toLowerCase()"
  
  -- | O(n) Convert a string to upper case, using simple case
  -- conversion. The result string may be longer than the input
  -- string. For instance, the German "ß" (eszett, U+00DF) maps to the
  -- two-letter sequence "SS".
  toUpper :: Text -> Text
  toUpper = ffi "%1.toUpperCase()"
  
  -- | O(n) Concatenate a list of Texts.
  concat :: [Text] -> Text
  concat = ffi "%1.join('')"
  
  -- | O(n) Map a function over a Text that results in a Text, and
  -- concatenate the results.
  concatMap :: (Char -> Text) -> Text -> Text
  concatMap = ffi "[].map.call(%2, %1).join('')"
  
  -- | O(n) any p t determines whether any character in the Text t
  -- satisifes the predicate p. Subject to fusion.
  any :: (Char -> Bool) -> Text -> Bool
  any = ffi "[].filter.call(%2, %1).length > 0"
  
  -- | O(n) all p t determines whether all characters in the Text t
  -- satisify the predicate p. Subject to fusion.
  all :: (Char -> Bool) -> Text -> Bool
  all = ffi "[].filter.call(%2, %1).length == %1.length"
  
  -- | O(n) maximum returns the maximum value from a Text, which must be
  -- non-empty. Subject to fusion.
  maximum :: Text -> Char
  maximum = ffi "(function (s) { \
    \   if (s === '') { throw new Error('Data.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)"
  
  -- | O(n) minimum returns the minimum value from a Text, which must be
  -- non-empty. Subject to fusion.
  minimum :: Text -> Char
  minimum = ffi "(function (s) { \
    \   if (s === '') { throw new Error('Data.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)"
  