{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverlappingInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE StandaloneDeriving #-}



module Forml.Parser.Utils where

import Control.Applicative
import Control.Monad.Identity
import Control.Monad.State hiding (lift)

import Text.Parsec hiding ((<|>), State, many, spaces, parse, label)
import Text.Parsec.Pos
import qualified Text.Parsec as P
import Text.Parsec.Indent  hiding (same)

import qualified Data.Map as M
import qualified Data.List as L

import Data.String.Utils
import qualified Data.Serialize as S

import GHC.Generics

import qualified Data.Text as T
import System.IO.Unsafe (unsafePerformIO)




data Addr a = Addr SourcePos SourcePos a deriving (Eq, Generic)

instance (S.Serialize a) => S.Serialize (Addr a)
instance S.Serialize SourcePos where

    get = return $ newPos "Serialized Optimization" 0 0
    put _ = return ()


instance Functor Addr where

    fmap f (Addr s e a) = Addr s e $ f a

addr :: Parser a -> Parser (Addr a)
addr p = do x <- getPosition
            y <- p
            z <- getPosition
            return$ Addr x z y


instance (Show a) => Show (Addr a) where
    show (Addr _ _ x) = show x

instance (Monad m) => Stream T.Text m Char where
    uncons = return . T.uncons

type Parser a = ParsecT T.Text () (StateT SourcePos Identity) a

class Syntax a where
    syntax :: Parser a

instance Syntax Double where
    syntax = read <$> do x <- many1 digit
                         string "."
                         y <- many1 digit
                         return $ x ++ "." ++ y

instance Syntax Int where
    syntax = read <$> many1 digit

instance Syntax String where
    syntax = do char '"'
                (escaped_char <|> anyChar) `manyTill` char '"'

        where escaped_char = do char '\\'
                                x <- oneOf "tnr\\"
                                case x of
                                  'r' -> return '\r'
                                  'n' -> return '\n'
                                  't' -> return '\t'
                                  '\\' -> return '\\'
                                  _   -> error "Unimplemented"


parse :: Parser a -> SourceName -> String -> Either ParseError a
parse parser sname input = runIndent sname $ runParserT parser () sname (T.pack input)

whitespace :: Parser String
whitespace = many $ oneOf "\t "

whitespace1 :: Parser String
whitespace1 = space >> whitespace

same :: Parser ()
same = spaces >> do pos <- getPosition
                    s <- get
                    if (sourceColumn pos) /= (sourceColumn s)
                       then parserFail $ "indented to exactly " ++ show (sourceColumn s + 1)
                       else do put $ setSourceLine s (sourceLine pos)
                               return ()

set_indentation :: (Int -> Int) -> Parser ()
set_indentation f = do s <- get
                       put$ setSourceColumn s (f (sourceColumn s))

spaces :: Parser ()
spaces = try emptyline `manyTill` try line_start >> return ()

    where emptyline  = whitespace >> newline
          line_start = whitespace >> notFollowedBy newline >> in_block

          in_block = do pos <- getPosition
                        s <- get
                        if (sourceColumn pos) < (sourceColumn s)
                           then parserFail $ "indented to at least " ++ show (sourceColumn s + 1)
                           else do put $ setSourceLine s (sourceLine pos)
                                   return ()


comment :: Parser String
comment = try empty_line <|> try commented_code <|> try code <|> markdown_comment

    where markdown_comment = anyChar `manyTill` newline *> return "\n"
          empty_line = whitespace *> newline *> return "\n"
          code = (\x y -> x ++ rstrip y ++ "\n")
              <$> string "    " <*> (anyChar `manyTill` newline)

          commented_code = do

              string "    "
              x <- noneOf "\n" `manyTill` try (string "--")
              anyChar `manyTill` newline
              return $ if length (strip x) > 0
                       then "    " ++ rstrip x ++ "\n"
                       else "\n"

sep_with :: Show a => String -> [a] -> String
sep_with x = concat . L.intersperse x . fmap show

unsep_with :: forall a b. (Show a, Show b) => String -> (M.Map b a) -> String
unsep_with z = concat . L.intersperse ", " . fmap (\(x, y) -> concat [show x, z, show y]) . M.toAscList

(<:>) :: Parser a -> Parser [a] -> Parser [a]
(<:>) x y = (:) <$> x <*> y

operator_dict :: M.Map Char String
operator_dict = M.fromList [ ('!', "_bang"),
                             ('@', "_at"),
                             ('#', "_hash"),
                             ('$', "$"),
                             ('%', "_perc"),
                             ('^', "_exp"),
                             ('&', "_and"),
                             ('|', "_or"),
                             ('<', "_less"),
                             ('>', "_grea"),
                             ('?', "_ques"),
                             ('/', "_forw"),
                             ('=', "_eq"),
                             (':', "_col"),
                             ('\\', "_back"),
                             ('~', "_tild"),
                             ('+', "_plus"),
                             ('-', "_minu"),
                             ('\'', "_tick"),
                             ('*', "_star"),
                             (',', "_comm"),
                             ('\'', "_apos"),
                             ('.', "_comp") ]

operator :: Parser Char
operator = oneOf (fst . unzip . M.toList $ operator_dict)

not_reserved :: Parser String -> Parser String
not_reserved x = do y <- x
                    if y `elem` reserved_words
                       then parserFail "non-reserved word"
                       else return y

    where reserved_words = [ "if", "then", "else", "let", "when", "with", "and", "or", "do", "do!", "var",
                             "module", "open", "yield", "lazy", "inline", "in", "is", "isnt", "where",
                             "|", "\\", "=", ":", ",", "->", "<-" ]

not_system :: Parser String -> Parser String
not_system x = not_reserved$ do y <- x
                                if y `elem` reserved_words
                                    then parserFail "non-reserved word"
                                    else return y

    where reserved_words = [ "==", "<=", ">=", "!=", "<", ">", "||", "&&", ".", ","  ]

valid_partial_op :: Parser String -> Parser String
valid_partial_op x = not_reserved$

                             do y <- x
                                if y `elem` reserved_words
                                    then parserFail "non-reserved word"
                                    else return y

    where reserved_words = [ ".", ","  ]


type_sep     :: Parser Char
indentPairs  :: String -> Parser a -> String -> Parser a
not_comma    :: Parser ()
comma        :: Parser ()
optional_sep :: ParsecT T.Text () (StateT SourcePos Identity) ()

type_sep          = try (spaces *> char '|' <* whitespace)
not_comma         = whitespace >> newline >> spaces >> notFollowedBy (string "}")
comma             = P.spaces *> string "," *> P.spaces
optional_sep      = try (try comma <|> not_comma)

indentPairs a p b = string a *> P.spaces *> (try p <|> withPosTemp p) <* P.spaces <* string b

indentAsymmetricPairs :: String -> Parser a -> Parser b -> Parser a
indentAsymmetricPairs a p b = string a *> P.spaces *> withPosTemp p <* P.spaces <* b

withPosTemp :: Parser a -> Parser a
withPosTemp p = do x <- get
                   p' <- try (Just <$> withPos p) <|> return Nothing
                   put x
                   case p' of
                       Just p' -> return p'
                       Nothing -> parserFail ("expression continuation indented to " ++ show x)


db :: Show a => a -> a
db x = unsafePerformIO $ do putStrLn$ "-- " ++ (show x)
                            return x

