{-# LANGUAGE TypeOperators, GeneralizedNewtypeDeriving #-}

-------------------------------------------------------------------------------------
-- |
-- Copyright   : (c) Hans Hoglund 2012
--
-- License     : BSD-style
--
-- Maintainer  : hans@hanshoglund.se
-- Stability   : experimental
-- Portability : portable
--
-- A Haskell representation and parser for ABC notation. Based on the 2.1 standard. 
-- 
-- For more information see <http://abcnotation.com>.
--
-------------------------------------------------------------------------------------

module Music.Abc (
        -- * Abc format
        -- ** Files
        AbcFile(..),     

        -- *** File header
        FileHeader(..),
        Element(..),

        -- ** Tunes
        AbcTune(..),
        TuneHeader(..), 
        TuneBody(..), 

        -- * Music
        Music(..),
        
        -- ** Note stack
        Note(..),

        DecorationT(..),
        SlurT(..),
        BeamT(..),
        GraceT(..),
        TupletT(..),
        DurationT(..),
        RestT(..),
        (:|:),
        
        -- * Basic types
        -- ** Time
        Duration(..),

        -- ** Pitch
        PitchClass(..),
        Accidental(..),
        Octave(..),
        Pitch(..),

        -- ** Decorations (articulation, dynamics etc)
        Decoration(..),

        -- ** Structure
        Barline(..),
        MultiRest(..),

        -- ** Information etc
        Information(..),
        Directive(..),

        -- * Import and export
        readAbc,
        showAbc
  ) where

import Network.URI (URI)

-- | A full ABC file (2.2).
data AbcFile
    = AbcFile
        (Maybe String)
        (Maybe FileHeader)
        [Element]

-- | File header (2.2.2).
data FileHeader
    = FileHeader 
        [Information] 
        [Directive]
    deriving (Eq, Ord, Show)

-- | Either a tune, free text or typeset text (2.2.3).
data Element
    = Tune
        AbcTune                         -- ^ An Abc tune.
    | FreeText
        String                          -- ^ Free text (2.2.3).
    | TypesetText
        String                          -- ^ Typeset text (2.2.3).
    deriving (Eq, Ord, Show)

data AbcTune 
    = AbcTune 
        TuneHeader 
        TuneBody
    deriving (Eq, Ord, Show)

-- TODO verify X, T and K fields
data TuneHeader 
    = TuneHeader 
        [Information]
    deriving (Eq, Ord, Show)

type TuneBody = [Music]

-- One line of music code.
data Music 
    = Music [Note :|: MultiRest :|: Barline :|: ()]
    deriving (Eq, Ord, Show)

newtype MultiRest = MultiRest { getMultiRest :: Int }
    deriving (Eq, Ord, Show)

type Note = DecorationT (SlurT (BeamT (GraceT (TupletT (DurationT (RestT Pitch))))))

type DecorationT a  = ([Decoration], a)
type SlurT a        = (Bool, a, Bool)
type BeamT a        = (Bool, a, Bool)
type GraceT a       = (Bool, a)
type TupletT a      = (Duration, a)
type DurationT a    = (a, Duration)
type RestT a        = Maybe (Maybe a) -- invisible/visible

data Decoration
    = Trill                   -- "tr" (trill mark)
    | TrillBegin              -- start of an extended trill
    | TrillEnd                -- end of an extended trill
    | Lowermordent            -- short squiggle with a vertical line through it
    | Uppermordent            -- short squiggle
    | Roll                    -- a roll mark (arc) as used in Irish music
    | Turn                    -- a turn mark (also known as gruppetto)
    | Turnx                   -- a turn mark with a line through it
    | Invertedturn            -- an inverted turn mark
    | Invertedturnx           -- an inverted turn mark with a line through it
    | Arpeggio                -- vertical squiggle
    | Accent                  -- accent mark
    | Fermata Bool            -- fermata or hold (arc above dot), inverted?
    | Tenuto                  -- horizontal line to indicate holding note for full duration
    | Fingering Int           -- fingerings
    | Plus                    -- left-hand pizzicato, or rasp for French horns
    | Snap                    -- snap-pizzicato mark, visually similar to !thumb!
    | Slide                   -- slide up to a note, visually similar to a half slur
    | Wedge                   -- small filled-in wedge mark
    | Upbow                   -- V mark
    | Downbow                 -- squared n mark
    | Open                    -- small circle above note indicating open string or harmonic
    | Thumb                   -- cello thumb symbol
    | Breath                  -- a breath mark (apostrophe-like) after note
    -- !pppp! !ppp! !pp! !p!  dynamics marks
    -- !mp! !mf! !f! !ff!     more dynamics marks
    -- !fff! !ffff! !sfz!     more dynamics marks
    | Crescendo               -- start of a crescendo mark
    | EndCrescendo            -- end of a crescendo mark, placed after the last note
    | Diminuendo              -- start of a diminuendo mark
    | EndDiminuendo           -- end of a diminuendo mark, placed after the last note
    | Segno                   -- ornate s-like symbols separated by a diagonal line
    | Coda                    -- a ring with a cross in it
    | DaSegno                 -- the letters D.S. (=Da Segno)
    | DaCapo                  -- the letters D.C. (=either Da Coda or Da Capo)
    | Dacoda                  -- the word "Da" followed by a Coda sign
    | Fine                    -- the word "fine"
    | Shortphrase             -- vertical line on the upper part of the staff
    | Mediumphrase            -- same, but extending down to the centre line
    | Longphrase              -- same, but extending 3/4 of the way down
    deriving (Eq, Ord, Show)


-- Base types

newtype Duration = Duration { getDuration :: Rational }
    deriving (Eq, Ord, Show, Enum, Num, Real, Fractional, RealFrac)

data PitchClass = C | D | E | F | G | A | B
    deriving (Eq, Ord, Show, Enum, Bounded)

data Accidental = DoubleFlat |Flat | Natural | Sharp | DoubleSharp
    deriving (Eq, Ord, Show, Enum, Bounded)

newtype Octave = Octave { getOctave :: Int }
    deriving (Eq, Ord, Show, Enum, Num, Real, Integral)

newtype Pitch = Pitch { getPitch :: (PitchClass, Accidental, Octave) }
    deriving (Eq, Ord, Show)

data StemDirection = Up | Down
    deriving (Eq, Ord, Show, Enum, Bounded)

data Clef = NoClef | Treble | Alto | Tenor | Bass | Perc
    deriving (Eq, Ord, Show, Enum, Bounded)

-- | Barline, including special barlines and repeats.
data Barline
    = Barline
    | DoubleBarline Bool Bool   -- thick? thick?
    | Repeat Int Bool Bool      -- times end? begin?
    | DottedBarline Barline
    | InvisibleBarline Barline
    deriving (Eq, Ord, Show)

-- TODO add elements
data Information
    = Area String
    | Book String
    | Composer String
    | Discography String
    | FileUrl String URI
    | Group String
    | History String

    | Instruction Directive
    | Key Key
    | UnitNoteLength Duration

    | Meter Meter
    -- Macros not supported
    | Notes String
    | Origin String
    | Parts -- TODO

    | Tempo Tempo
    | Rhythm String -- Polska, marsch etc.
    -- Remarks are discarded
    | Source String -- Uppland etc.

    | SymbolLine Symbol 
    | TuneTitle String
    -- User defined not supported

    | Voice VoiceProps
    | Words String -- TODO include separators
    | ReferenceNumber Integer
    | Transcription String
    deriving (Eq, Ord, Show)

type Key = (PitchClass, Mode)                   

-- | Optional string, beats, frequency (3.1.8)
type Tempo = (Maybe String, [Duration], Duration)

type Symbol = ()

data VoiceProps
    = VoiceProps
        (Maybe String)
        (Maybe String)
        (Maybe StemDirection)
        (Maybe Clef)
    deriving (Eq, Ord, Show)

data Meter
    = NoMeter
    | Common
    | Cut
    | Simple Rational
    | Compound [Integer] Integer
    deriving (Eq, Ord, Show)

data Mode
    = Major
    | Minor
    | Ionian
    | Dorian
    | Phrygian
    | Lydian
    | Mixolydian
    | Aeolian
    | Locrian
    deriving (Eq, Ord, Show)

-- | Abc directive.
type Directive = (String, String)
    
    

readAbc :: String -> AbcFile
readAbc = error "Not impl"

showAbc :: AbcFile -> String
showAbc = error "Not impl"

infixr 5 :|:
type a :|: b = Either a b