module Sound.Audacity.Project.Track.Label (
   T (Cons, name_, height_, minimized_, track_),
   deflt,
   toXML,
   intervalToXML,
   tracksFromXML,
   parse,
   parseInterval,
   labelName,
   labeltrackName,
   ) where

import qualified Sound.Audacity.LabelTrack as LabelTrack
import qualified Sound.Audacity.XML.Attribute as Attr
import qualified Sound.Audacity.XML.Parser as Parser
import qualified Sound.Audacity.XML as XML

import qualified Text.HTML.Tagchup.Tag as Tag
import qualified Text.HTML.Tagchup.Tag.Match as TagMatch
import qualified Text.XML.Basic.Name.MixedCase as Name

import Text.Printf (printf)

import qualified Control.Monad.Trans.State as MS
import qualified Control.Monad.Trans.Maybe as MM
import qualified Control.Monad.Exception.Synchronous as ME
import Control.Applicative (many, (<*))

import qualified Data.NonEmpty.Mixed as NonEmptyM
import qualified Data.NonEmpty as NonEmpty
import Data.Maybe (mapMaybe)


data T =
   Cons {
      name_ :: String,
      height_ :: Int,
      minimized_ :: Bool,
      track_ :: LabelTrack.T Double String
   }
   deriving (Show)

deflt :: T
deflt =
   Cons {
      name_ = "",
      height_ = 100,
      minimized_ = False,
      track_ = LabelTrack.empty
   }


toXML :: T -> [[Tag.T Name.T String]]
toXML x =
   XML.tag "labeltrack" x
      (Attr.string "name" name_ :
       Attr.int "numlabels" (length . LabelTrack.decons . track_) :
       Attr.int "height" height_ :
       Attr.bool "minimized" minimized_ :
       [])
      $
      map intervalToXML (LabelTrack.decons $ track_ x)

{-
nanosecond precision as in ALSA
-}
intervalToXML :: LabelTrack.Interval Double String -> [Tag.T Name.T String]
intervalToXML ((from,to), title) =
   (Tag.open labelName $
      XML.attr "t"  (printf "%.9f" from) :
      XML.attr "t1" (printf "%.9f" to) :
      XML.attr "title" title :
      []) :
   Tag.close labelName :
   []


maybeExc ::
   MM.MaybeT (ME.Exceptional Parser.Message) a ->
   Maybe (ME.Exceptional Parser.Message a)
maybeExc (MM.MaybeT act) =
   case act of
      ME.Exception msg -> Just $ ME.Exception msg
      ME.Success ma -> fmap ME.Success ma

tracksFromXML :: [Tag.T Name.T String] -> ME.Exceptional Parser.Message [T]
tracksFromXML =
   sequence . mapMaybe (maybeExc . MS.evalStateT parse . NonEmpty.flatten) .
   snd . NonEmptyM.segmentBefore (TagMatch.open (labeltrackName==) (const True))

{- |
Currently we ignore the 'numlabels' attribute.
Alternatively we could check whether that value matches
the number of read intervals.
-}
parse :: Parser.T T
parse = do
   attrs <- Parser.tagOpen labeltrackName
   name <- Parser.lookupAttr "name" attrs
   height <- Parser.lookupAttrRead "height" attrs
   minimized <- Parser.lookupAttrBool "minimized" attrs
   Parser.skipSpaces
   intervals <- many (parseInterval <* Parser.skipSpaces)
   Parser.tagClose labeltrackName
   return $
      Cons {
         name_ = name,
         height_ = height,
         minimized_ = minimized,
         track_ = LabelTrack.Cons intervals
      }

parseInterval :: Parser.T (LabelTrack.Interval Double String)
parseInterval = do
   attrs <- Parser.tagOpen labelName
   from <- Parser.lookupAttrRead "t" attrs
   to <- Parser.lookupAttrRead "t1" attrs
   title <- Parser.lookupAttr "title" attrs
   Parser.tagClose labelName
   return ((from, to), title)


labelName :: Tag.Name Name.T
labelName = Tag.Name $ Name.Cons "label"

labeltrackName :: Tag.Name Name.T
labeltrackName = Tag.Name $ Name.Cons "labeltrack"