-- | Embed data into the executable binary
module Haskus.Utils.Embed
   ( embedBytes
   -- | Raw text quasiquoter
   , raw
   , rawQ
   )
where

import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Data.Word

-- | Embed bytes in a C array, return an Addr#
embedBytes :: [Word8] -> Q Exp
embedBytes bs = return $ LitE (StringPrimL bs)

----------------------------------------------------------------------
-- Raw text quasiquoter (adapted from raw-strings-qq package (BSD3)
----------------------------------------------------------------------


-- |
--
-- A quasiquoter for raw string literals - that is, string literals that don't
-- recognise the standard escape sequences (such as @\'\\n\'@). Basically, they
-- make your code more readable by freeing you from the responsibility to escape
-- backslashes. They are useful when working with regular expressions, DOS/Windows
-- paths and markup languages (such as XML).
--
-- Don't forget the @LANGUAGE QuasiQuotes@ pragma if you're using this
-- module in your code.
--
-- Usage:
--
-- > :set -XQuasiQuotes
-- > import Haskus.Utils.Embed
-- > let s = [raw|\\w+\@[a-zA-Z_]+?\\.[a-zA-Z]{2,3}|]
-- > s
-- \"\\\\w+\@[a-zA-Z_]+?\\\\.[a-zA-Z]{2,3}\"
-- > [raw|C:\\Windows\\SYSTEM|] ++ [raw|\\user32.dll|]
-- \"C:\\\\Windows\\\\SYSTEM\\\\user32.dll\"
--
-- Multiline raw string literals are also supported:
--
-- @
--     multiline :: String
--     multiline = [raw|\<HTML\>
--     \<HEAD\>
--     \<TITLE\>Auto-generated html formated source\</TITLE\>
--     \<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=windows-1252\"\>
--     \</HEAD\>
--     \<BODY LINK=\"#0000ff\" VLINK=\"#800080\" BGCOLOR=\"#ffffff\"\>
--     \<P\> \</P\>
--     \<PRE\>|]
-- @
--
-- Caveat: since the @\"|]\"@ character sequence is used to terminate the
-- quasiquotation, you can't use it inside the raw string literal. Use 'rawQ' if you
-- want to embed that character sequence inside the raw string.
raw :: QuasiQuoter
raw = QuasiQuoter {
    quoteExp  = return . LitE . StringL . normaliseNewlines,

    quotePat  = \_ -> fail "illegal raw string QuasiQuote \
                           \(allowed as expression only, used as a pattern)",
    quoteType = \_ -> fail "illegal raw string QuasiQuote \
                           \(allowed as expression only, used as a type)",
    quoteDec  = \_ -> fail "illegal raw string QuasiQuote \
                           \(allowed as expression only, used as a declaration)"
}

-- | A variant of 'raw' that interprets the @\"|~]\"@ sequence as @\"|]\"@,
-- @\"|~~]\"@ as @\"|~]\"@ and, in general, @\"|~^n]\"@ as @\"|~^(n-1)]\"@
-- for n >= 1.
--
-- Usage:
--
--
-- > [rawQ||~]|~]|]
-- \"|]|]\"
--
-- > [rawQ||~~]|]
-- \"|~]\"
--
-- > [rawQ||~~~~]|]
-- \"|~~~]\"
--
rawQ :: QuasiQuoter
rawQ = QuasiQuoter {
    quoteExp  = return . LitE . StringL . escape_rQ . normaliseNewlines,

    quotePat  = \_ -> fail "illegal raw string QuasiQuote \
                           \(allowed as expression only, used as a pattern)",
    quoteType = \_ -> fail "illegal raw string QuasiQuote \
                           \(allowed as expression only, used as a type)",
    quoteDec  = \_ -> fail "illegal raw string QuasiQuote \
                           \(allowed as expression only, used as a declaration)"
}

escape_rQ :: String -> String
escape_rQ [] = []
escape_rQ ('|':'~':xs) =
  let (tildas, rest) = span (== '~') xs
  in case rest of
    []       -> '|':'~':tildas
    (']':rs) -> '|':tildas ++ ']':escape_rQ rs
    rs       -> '|':'~':tildas ++ escape_rQ rs
escape_rQ (x:xs) = x : escape_rQ xs

-- See https://github.com/23Skidoo/raw-strings-qq/issues/1 and
-- https://ghc.haskell.org/trac/ghc/ticket/11215.
normaliseNewlines :: String -> String
normaliseNewlines []             = []
normaliseNewlines ('\r':'\n':cs) = '\n':normaliseNewlines cs
normaliseNewlines (c:cs)         = c:normaliseNewlines cs