-------------------------------------------------------------------------------------
-- |
-- Copyright   : (c) Hans Hoglund 2012
--
-- License     : BSD-style
--
-- Maintainer  : hans@hanshoglund.se
-- Stability   : experimental
-- Portability : portable
--
-------------------------------------------------------------------------------------

module Data.Music.Abc.Parser (
    parse
  ) where

import Data.Monoid   
import Data.Either
import Control.Monad
import Control.Applicative hiding ((<|>), optional, many)

import Text.Parsec hiding (parse)
import Text.Parsec.Token
import Text.Parsec.String

import Data.Music.Abc

-- TODO information field verification (header/body)

-- ## Limitations:
--
--  * Limited support for *volatile* features
--  * Limited support for text strings (§8.2)
--    * No mnemonics
--    * No entities
--    * Unicode escapes are supported
--  * No support for macros (§9)
--  * No support for outdated syntax (§10)
--  * Stylesheet directives are ignored (§11)
--  * Typeset text is ignored (§2.2.3)
--  * Strict interpretation assumed (§12)


-- |
-- Parse a module description, returning an error if unsuccessful.
--
parse :: String -> Either ParseError AbcFile
parse = runParser abcFile () ""
                 
-------------------------------------------------------------------------------------
-- Parsers
-------------------------------------------------------------------------------------

abcFile :: Parser AbcFile
abcFile = do                      
    -- optional byteOrderMark
    string "%abc"
    optional $ string "-" >> version
    optional $ fileHeader    
    fileBody
    return undefined

fileHeader :: Parser FileHeader
fileHeader = fmap (uncurry FileHeader . partitionEithers) $ many1 $ mzero 
    <|> fmap Left informationField 
    <|> fmap Right styleSheetDirective

fileBody :: Parser [Element]
fileBody = (flip sepBy) emptyLine $ mzero
    <|> fmap Tune abcTune
    <|> fmap FreeText freeText 
    <|> fmap TypesetText typeSetText

informationField :: Parser Information
informationField = do
    letter
    char ':'
    -- TODO anything not \n
    char '\n'              
    return undefined

-- Not parsed, see Limitations
styleSheetDirective :: Parser Directive
styleSheetDirective = mzero

byteOrderMark :: Parser ()
byteOrderMark = do
    char '\xFFFE' <|> char '\xFEFF'
    return ()

version :: Parser Double
version = undefined

abcTune :: Parser AbcTune
abcTune = undefined

freeText :: Parser String
freeText = undefined

-- Not parsed, see Limitations
typeSetText :: Parser String
typeSetText = mzero


-------------------------------------------------------------------------------------
-- Lexer
-------------------------------------------------------------------------------------

lexer :: TokenParser ()
lexer = makeTokenParser $ LanguageDef {
    commentStart    =  "[r:",
    commentEnd      =  "]",
    commentLine     =  "%",
    nestedComments  =  False,
    identStart      =  (letter <|> char '_'),
    identLetter     =  (alphaNum <|> char '_'),
    opStart         =  mzero,
    opLetter        =  mzero,
    reservedNames   =  reservedNames,
    reservedOpNames =  mzero,
    caseSensitive   =  True
    }
    where
        reservedNames = []

-- Convenient synonyms, not exported
llex   = lexeme lexer
lnat   = natural lexer
lstr   = stringLiteral lexer
lname  = identifier lexer
lres   = reserved lexer
lspace = whiteSpace lexer

emptyLine = newLine >> newLine
newLine = string "\r\n" <|> string "\n"

single x = [x]

notSupported x = error $ "Not supported yet: " ++ x