-- Author:     Andy Stewart <lazycat.manatee@gmail.com>
-- Maintainer: Andy Stewart <lazycat.manatee@gmail.com>
-- 
-- Copyright (C) 2010 Andy Stewart, all rights reserved.
-- 
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- any later version.
-- 
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
-- 
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

{-# LANGUAGE ScopedTypeVariables, DeriveDataTypeable #-}
module Manatee.Extension.Mplayer.PlaylistBuffer where

import Audio.TagLib.TagLib
import Control.Applicative
import Control.Concurrent.STM 
import Control.Monad (liftM, unless)
import DBus.Client hiding (Signal)
import Data.ByteString.UTF8
import Data.Maybe (maybeToList)
import Data.Ord (comparing)
import Data.Time
import Data.Typeable
import Graphics.UI.Gtk.General.Enums
import Graphics.UI.Gtk.ModelView.TreeSortable
import Manatee.Core.Types
import Manatee.Extension.Mplayer.PageMode
import Manatee.Extension.Mplayer.DBus
import Manatee.Toolkit.General.Basic
import Manatee.Toolkit.General.DBus
import Manatee.Toolkit.General.List
import Manatee.Toolkit.General.Maybe
import Manatee.Toolkit.General.Process
import Manatee.Toolkit.Gio.Gio
import System.FilePath
import System.GIO.File.File 
import System.GIO.File.FileInfo 
import System.Posix.Process
import Text.Regex.TDFA

import qualified Data.Map as M

data PlaylistBuffer =
    PlaylistBuffer {playlistBufferName                  :: TVar String
                   ,playlistBufferClient                :: Client
                   ,playlistBufferPageId                :: PageId
                   ,playlistBufferMode                  :: PageMode
                   ,playlistBufferInfos                 :: TVar [MultimediaInfo]
                   ,playlistBufferOptions               :: [(MultimediaOption, SortColumnId)]
                   ,playlistBufferSortStatus            :: TVar (MultimediaOption, SortType)
                   }
    deriving Typeable

data MultimediaInfo =
    MultimediaInfo {miFilePath          :: ByteString
                   ,miArtist            :: String
                   ,miTitle             :: String
                   ,miAlbum             :: String
                   ,miTrack             :: Int
                   ,miGenre             :: String
                   ,miYear              :: Int
                   ,miDuration          :: Int
                   ,miBitRate           :: Int
                   ,miSampleRate        :: Int
                   ,miChannels          :: Int
                   ,miComment           :: String
                   }
    deriving Show

class MultimediaInfoClass a where
    getColumnTitle      :: a -> String                  -- get title for treeColumn 
    getColumnMaxWidth   :: a -> Maybe Int
    getCellText         :: a -> MultimediaInfo -> String -- get text for cell
    getCellXAlign       :: a -> Float                   -- get x align for cell
    compareRow          :: a -> MultimediaInfo -> MultimediaInfo -> IO Ordering

instance MultimediaInfoClass MultimediaOption where
    getColumnTitle MOArtist     = "Artist"
    getColumnTitle MOTitle      = "Title"
    getColumnTitle MOAlbum      = "Album"
    getColumnTitle MOTrack      = "Track"
    getColumnTitle MOGenre      = "Genre"
    getColumnTitle MOYear       = "Year"
    getColumnTitle MODuration   = "Duration"
    getColumnTitle MOBitRate    = "Bit Rate"
    getColumnTitle MOSampleRate = "Sample Rate"
    getColumnTitle MOChannels   = "Channels"
    getColumnTitle MOComment    = "comment"

    getColumnMaxWidth MOArtist     = Just 200
    getColumnMaxWidth MOTitle      = Just 400
    getColumnMaxWidth MOAlbum      = Just 400
    getColumnMaxWidth MOTrack      = Nothing
    getColumnMaxWidth MOGenre      = Nothing
    getColumnMaxWidth MOYear       = Nothing
    getColumnMaxWidth MODuration   = Nothing
    getColumnMaxWidth MOBitRate    = Nothing
    getColumnMaxWidth MOSampleRate = Nothing
    getColumnMaxWidth MOChannels   = Nothing
    getColumnMaxWidth MOComment    = Nothing

    getCellText MOArtist     info = miArtist info
    getCellText MOTitle      info = miTitle info
    getCellText MOAlbum      info = miAlbum info
    getCellText MOTrack      info = if miTrack info == 0 then "" else show $ miTrack info
    getCellText MOGenre      info = miGenre info
    getCellText MOYear       info = if miYear info == 0 then "" else show $ miYear info
    getCellText MODuration   info = showDuration (miDuration info)
    getCellText MOBitRate    info = show (miBitRate info) ++ " kb/s"
    getCellText MOSampleRate info = show (miSampleRate info) ++ " HZ"
    getCellText MOChannels   info = show $ miChannels info
    getCellText MOComment    info = miComment info

    getCellXAlign MOArtist     = 0.0
    getCellXAlign MOTitle      = 0.0
    getCellXAlign MOAlbum      = 0.0
    getCellXAlign MOTrack      = 1.0
    getCellXAlign MOGenre      = 0.0
    getCellXAlign MOYear       = 1.0
    getCellXAlign MODuration   = 0.0
    getCellXAlign MOBitRate    = 0.0
    getCellXAlign MOSampleRate = 0.0
    getCellXAlign MOChannels   = 0.0
    getCellXAlign MOComment    = 0.0

    compareRow MOArtist     row1 row2 = return $ comparing miArtist     row1 row2
    compareRow MOTitle      row1 row2 = return $ comparing miTitle      row1 row2
    compareRow MOAlbum      row1 row2 = return $ playlistAlbumCompare   row1 row2
    compareRow MOTrack      row1 row2 = return $ comparing miTrack      row1 row2
    compareRow MOGenre      row1 row2 = return $ comparing miGenre      row1 row2
    compareRow MOYear       row1 row2 = return $ comparing miYear       row1 row2
    compareRow MODuration   row1 row2 = return $ comparing miDuration   row1 row2
    compareRow MOBitRate    row1 row2 = return $ comparing miBitRate    row1 row2
    compareRow MOSampleRate row1 row2 = return $ comparing miSampleRate row1 row2
    compareRow MOChannels   row1 row2 = return $ comparing miChannels   row1 row2
    compareRow MOComment    row1 row2 = return $ comparing miComment    row1 row2


data MultimediaOption = MOArtist 
                      | MOTitle
                      | MOAlbum
                      | MOTrack
                      | MOGenre
                      | MOYear
                      | MODuration
                      | MOBitRate
                      | MOSampleRate
                      | MOChannels
                      | MOComment
                        deriving (Eq, Show, Read)

-- | New playlist buffer.
-- If path is directory, will search multimedia files with given directory.
-- If path is multimedia file, then add to play list.
playlistBufferNew :: FilePath -> Client -> PageId -> IO PlaylistBuffer
playlistBufferNew path client pageId = do
  -- Get multimedia info list.
  infos <- playlistBufferGenerateInfos path

  -- Play first music ... sweet ... :)
  let sendPlaySignal = unless (null infos) $ do
         let filepath = miFilePath $ head infos 
         processId <- getProcessID
         mkMplayerDaemonSignal client Play (PlayArgs filepath processId)

  -- Build DBus match rule.
  mkMplayerClientMatchRule client
     (DaemonProcessStartup,
      \ DaemonProcessStartupArgs -> sendPlaySignal)

  -- Check whether daemon process has startup.
  ifM (isBusNameExist mplayerDaemonBusName) 
      -- Send play signal if daemon process has startup.
      sendPlaySignal
      -- Otherwise startup daemon process, send join signal after daemon process startup complete.
      (do 
        processId <- getProcessID
        putStrLn "No mplayer daemon process, starting one."
        runProcess_ "manatee-mplayer-daemon" [show processId])

  PlaylistBuffer <$> newTVarIO path
                 <*> pure client
                 <*> pure pageId
                 <*> pure mplayerMode
                 <*> newTVarIO infos
                 <*> pure (pairPred [MOTitle, MOAlbum, MOArtist, MOYear, MOTrack, MOBitRate, MODuration])
                 <*> newTVarIO (MOAlbum, SortAscending)

-- |  Generate multimedia infos.
playlistBufferGenerateInfos :: FilePath -> IO [MultimediaInfo]
playlistBufferGenerateInfos path
    -- Search multimedia file info is path is directory.
    | directoryDoesExist (fromString path) 
        = directoryGetFilesRecursive (fromString path)
          >>= concatMapM filterMultimediaFile 
    | fileDoesExist (fromString path)
        = filterMultimediaFile (fromString path)
          where filterMultimediaFile file = do
                    isMFile <- isMultimediaFile file
                    if isMFile
                       then liftM maybeToList $ getMultimediaInfo file
                       else return []                       
  
-- | Whether path match multimedia regular expression.
isMultimediaFile :: ByteString -> IO Bool
isMultimediaFile path = do
  let file = fileFromPath path
  fileInfo <- fileQueryInfo file "*" [] Nothing
  let contentType = fileInfoGetContentType fileInfo
  return $ 
    case contentType of
      Just ct -> ct =~ ("^(audio|video)/.+" :: String)
      Nothing -> False

-- | Get multimedia information.
getMultimediaInfo :: ByteString -> IO (Maybe MultimediaInfo)
getMultimediaInfo path = 
  tagFileOpen path 
    >?>=> \tf ->
  tagFileGetTag tf 
    >?>=> \t -> 
  tagFileGetAudioProperties tf 
    >?>=> \p -> liftM Just $ 
        MultimediaInfo <$> pure path
                       <*> tagGetArtist t
                       <*> getMultimediaTitle (toString path) t
                       <*> tagGetAlbum t
                       <*> tagGetTrack t
                       <*> tagGetGenre t
                       <*> tagGetYear t                                                                      
                       <*> audioPropertiesGetDuration p
                       <*> audioPropertiesGetBitRate p
                       <*> audioPropertiesGetSampleRate p
                       <*> audioPropertiesGetChannels p
                       <*> tagGetComment t
-- | Show duration.
showDuration :: Int -> String
showDuration = 
    show . timeToTimeOfDay . secondsToDiffTime . fromIntegral

-- | Get multimedia title.
getMultimediaTitle :: FilePath -> Tag -> IO String
getMultimediaTitle path tag = do
  title <- tagGetTitle tag
  return $ if null title
              -- Use file name if title is empty.
              then takeBaseName path
              else title

-- | Compare album.
playlistAlbumCompare :: MultimediaInfo -> MultimediaInfo -> Ordering
playlistAlbumCompare row1 row2 
    | mAlbum1 /= mAlbum2
        = compare mAlbum1 mAlbum2
    | mArtist1 /= mArtist2
        = compare mArtist1 mArtist2
    | mTrack1 /= mTrack2
        = compare mTrack1 mTrack2
    | otherwise
        = compare mTitle1 mTitle2
    where 
      mAlbum1  = miAlbum row1
      mAlbum2  = miAlbum row2
      mArtist1 = miArtist row1
      mArtist2 = miArtist row2
      mTrack1  = miTrack row1
      mTrack2  = miTrack row2
      mTitle1  = miTitle row1
      mTitle2  = miTitle row2