-- |
-- Module      :  Robotics.ROS.Pkg.Parser
-- Copyright   :  Alexander Krupenkin 2016
-- License     :  BSD3
--
-- Maintainer  :  mail@akru.me
-- Stability   :  experimental
-- Portability :  POSIX / WIN32
--
-- This module contains simple 'TagSoup' based XML parser.
-- It used for parsing @package.xml@ file with common
-- ROS package information.
--
{-# LANGUAGE CPP #-}
module Robotics.ROS.Pkg.Parser (parse) where

import Text.StringLike (StringLike, toString)
import Data.ByteString as BS (readFile)
import System.Directory (doesFileExist)
import System.FilePath (takeDirectory)
import Text.HTML.TagSoup
import Data.Text (Text)

import Robotics.ROS.Pkg.Types

#ifdef FAST_PARSER
import Text.HTML.TagSoup.Fast
#else
import Data.Text.Encoding (decodeUtf8)
import Data.ByteString (ByteString)

-- | Parse with default UTF8 encoding
parseTagsT :: ByteString -> [Tag Text]
parseTagsT = fmap (fmap decodeUtf8) . parseTags
#endif

-- | Parse package.xml file
parse :: FilePath -> IO (Either String Package)
parse pkgFile = do
    exist <- doesFileExist pkgFile
    if not exist
    then return (Left $ "No such file: " ++ pkgFile)
    else do content <- parseTagsT <$> BS.readFile pkgFile
            return (Package pkgDir <$> packageMeta content)
  where pkgDir = takeDirectory pkgFile

-- | Tag-based parser
packageMeta :: [Tag Text] -> Either String PackageMeta
packageMeta tags =
    PackageMeta <$> takeText "name"
                <*> takeText "version"
                <*> takeText "description"
                <*> takeText "license"
                <*> takeTexts "build_depend"
                <*> takeTexts "run_depend"
  where takeText   = fmap innerText . slice1 
        takeTexts  = fmap (fmap innerText) . sliceN tags 
        slice1     = fmap snd . slice' tags 
        sliceN t n = case slice' t n of
                        Left _ -> return []
                        Right (xs, r) -> do 
                                r' <- sliceN xs n
                                return (r : r')

-- | Slice tags from Open-tag to Close-tag with same name
slice' :: StringLike a => [Tag a] -> a -> Either String ([Tag a], [Tag a])
slice' tags tagName | length sliceTags > 0 = Right (freeTags, sliceTags) 
                    | otherwise = Left $ "Not found tag name: "
                                        ++ toString tagName
  where sliceTags = takeTags (dropTags tags) 
        freeTags  = drop (length sliceTags) (dropTags tags)
        dropTags  = dropWhile (not . isTagOpenName tagName)
        takeTags  = takeWhile (not . isTagCloseName tagName)