module Fixme.Comment
  ( 
    Comment
  , parseComments
  , readComments
  , commentText
  , filename
  , startLine
  , endLine
    
  , Located
  , locatedValue
    
  , newComment
    
  , Language
  , languageForFile
    
  , highlightCode
  ) where
import Protolude
import qualified Data.ByteString as ByteString
import GHC.IO (FilePath)
import Text.Highlighter
  ( lexerFromFilename
  , runLexer
  , Lexer
  , Token(..)
  , TokenType(..)
  )
parseComments :: Filename -> Language -> ByteString -> [Comment]
parseComments filename language = parseComments' filename . highlightCode language
parseComments' :: Filename -> [Token] -> [Comment]
parseComments' filename =
  coalesce appendComment . mapMaybe getComment . locateTokens filename
  where
    coalesce :: (a -> a -> Maybe a) -> [a] -> [a]
    coalesce maybeAppend (x:y:ys) =
      case x `maybeAppend` y of
        Nothing -> x:(coalesce maybeAppend (y:ys))
        Just z -> (coalesce maybeAppend (z:ys))
    coalesce _ xs = xs
readComments :: FilePath -> Maybe (IO [Comment])
readComments filename =
  case languageForFile (toS filename) of
    Nothing -> Nothing
    Just language -> Just $ do
      contents <- ByteString.readFile filename
      pure (parseComments (Just (toS filename)) language contents)
data Located a = Located { filename :: Filename
                         , startLine :: Int
                         , locatedValue :: a
                         } deriving (Eq, Show)
instance Functor Located where
  fmap f (Located fn start x) = Located fn start (f x)
type Filename = Maybe Text
locateTokens :: Filename -> [Token] -> [Located Token]
locateTokens fn tokens =
  [ Located fn i t | (i, t) <- zip startLines tokens ]
  where
    startLines = scanl (+) 0 [ numLines value | Token _ value <- tokens ]
numLines :: ByteString -> Int
numLines = ByteString.count newline
  where newline = fromIntegral (ord '\n')
type Comment = Located ByteString
commentText :: Comment -> ByteString
commentText = locatedValue
newComment :: Filename -> Int -> ByteString -> Comment
newComment fn i text = Located fn i text
endLine :: Comment -> Int
endLine (Located _ startLine c) = startLine + (numLines c)
appendComment :: Comment -> Comment -> Maybe Comment
appendComment x y
  | filename x /= filename y = Nothing
  | startLine y - endLine x > 1 = Nothing
  | startLine y - endLine x == 1 = Just (newComment (filename x) (startLine x) (commentText x <> "\n" <> commentText y))
  | otherwise = Just (newComment (filename x) (startLine x) (commentText x <> commentText y))
getComment :: Located Token -> Maybe Comment
getComment (Located filename lineNum token) =
  case getComment' token of
    Nothing -> Nothing
    Just comment -> Just $ Located filename lineNum comment
  where
    getComment' :: Token -> Maybe ByteString
    getComment' (Token tType value) = bool Nothing (Just value) (isComment tType)
    isComment Comment = True
    isComment (Arbitrary "Comment") = True
    isComment (x :. y) = isComment x || isComment y
    isComment _ = False
type Language = Lexer
languageForFile :: FilePath -> Maybe Language
languageForFile = lexerFromFilename
highlightCode :: Language -> ByteString -> [Token]
highlightCode language code =
  case runLexer language code of
    Left _ -> []
    Right tokens -> tokens