-- 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 DeriveDataTypeable, ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
module Manatee.Extension.Mplayer.PlaylistView where

import Control.Applicative
import Control.Concurrent.STM 
import Control.Monad
import Data.Map (Map)
import Data.Text.Lazy (Text)
import Data.Typeable
import Graphics.UI.Gtk hiding (Statusbar, statusbarNew, get)
import Graphics.UI.Gtk.Gdk.SerializedEvent
import Manatee.Core.PageView
import Manatee.Core.Types
import Manatee.Extension.Mplayer.DBus
import Manatee.Extension.Mplayer.PlaylistBuffer
import Manatee.Toolkit.General.Basic
import Manatee.Toolkit.General.Functor
import Manatee.Toolkit.General.Maybe
import Manatee.Toolkit.General.STM
import Manatee.Toolkit.Gio.Gio
import Manatee.Toolkit.Gtk.Gtk
import Manatee.Toolkit.Gtk.ModelView
import Manatee.Toolkit.Gtk.ScrolledWindow
import System.GIO.File.ContentType 
import System.Posix.Process
import System.Random

import qualified Data.Map as M

data PlaylistView = 
    PlaylistView {playlistViewPlugId              :: TVar PagePlugId
                 ,playlistViewScrolledWindow      :: ScrolledWindow
                 ,playlistViewBuffer              :: PlaylistBuffer
                 ,playlistViewTreeView            :: TreeView
                 ,playlistViewListStore           :: ListStore MultimediaInfo
                 ,playlistViewSortModel           :: TypedTreeModelSort MultimediaInfo
                 ,playlistViewCurrentPlayPath     :: TVar TreePath
                 ,playlistViewPlayMode            :: TVar PlayMode}
    deriving Typeable

instance PageBuffer PlaylistBuffer where
    pageBufferGetName           = readTVarIO . playlistBufferName
    pageBufferSetName a         = writeTVarIO (playlistBufferName a)
    pageBufferClient            = playlistBufferClient
    pageBufferCreateView a pId  = PageViewWrap <$> playlistViewNew a pId
    pageBufferMode              = playlistBufferMode

instance PageView PlaylistView where
    pageViewBuffer              = PageBufferWrap . playlistViewBuffer
    pageViewPlugId              = playlistViewPlugId
    pageViewFocus               = treeViewFocus . playlistViewTreeView
    pageViewScrolledWindow      = playlistViewScrolledWindow
    pageViewHandleKeyAction     = playlistViewHandleKeyAction
    pageViewScrollToTop         = playlistViewScrollToTop
    pageViewScrollToBottom      = playlistViewScrollToBottom
    pageViewScrollVerticalPage  = playlistViewScrollVerticalPage
    pageViewScrollVerticalStep  = playlistViewScrollVerticalStep

data PlayMode = SingleMode
              | ListMode
              | RandomMode
                deriving (Show, Eq, Ord)

-- | Internal new function.
playlistViewNew :: PlaylistBuffer -> PagePlugId -> IO PlaylistView
playlistViewNew buffer plugId = do 
  -- Create plug id.
  pId <- newTVarIO plugId

  -- Create UI frame.
  scrolledWindow <- scrolledWindowNew_

  -- Tree view.
  treeView <- treeViewNew
  treeViewSetEnableTreeLines treeView True
  scrolledWindow `containerAdd` treeView

  -- List store.
  listStore <- listStoreNew []

  -- Sort model.
  sortModel <- treeModelSortNewWithModel listStore

  -- Current play path.
  path <- newTVarIO [0]

  -- Play mode.
  playMode <- newTVarIO ListMode

  -- Playlist view.
  let playlistView = PlaylistView pId scrolledWindow buffer treeView listStore sortModel path playMode 

  -- Build match rule listen DBus message.
  playlistViewBuildMatchRule playlistView

  -- Draw view.
  playlistViewDraw playlistView

  return playlistView

-- | Build match rule.
playlistViewBuildMatchRule :: PlaylistView -> IO ()  
playlistViewBuildMatchRule view = 
  mkMplayerClientMatchRule 
    (pageViewClient view) 
    (PlayFinished, 
     \ _ -> do
       mode <- readTVarIO (playlistViewPlayMode view)
       case mode of
         SingleMode -> playlistViewPlayCurrent view
         ListMode   -> playlistViewPlayNext view
         RandomMode -> playlistViewPlayRandom view)

-- | Switch play mode.
playlistViewSwitchPlayMode :: PlaylistView -> IO ()
playlistViewSwitchPlayMode view = do
  mode <- readTVarIO (playlistViewPlayMode view)
  let newMode = case mode of
                  SingleMode -> ListMode
                  ListMode   -> RandomMode
                  RandomMode -> SingleMode
  writeTVarIO (playlistViewPlayMode view) newMode
  pageViewUpdateInfoStatus view "PlayMode" (" PlayMode (" ++ show newMode ++ ")")

-- | Play current track.
playlistViewPlayCurrent :: PlaylistView -> IO ()
playlistViewPlayCurrent view = do
  currentPath <- readTVarIO $ playlistViewCurrentPlayPath view
  playlistViewPlayInternal view currentPath

-- | Draw playlist view.
playlistViewDraw :: PlaylistView -> IO ()
playlistViewDraw view = do
  -- Get value.
  let buffer    = playlistViewBuffer view
      treeView  = playlistViewTreeView view
      store     = playlistViewListStore view
      model     = playlistViewSortModel view
  infos <- readTVarIO $ playlistBufferInfos buffer 

  -- Append to list store.
  listStoreClear store
  forM_ infos (listStoreAppend store) 

  -- Set tree view model.
  treeViewSetModel treeView model

  -- Clean tree view.
  treeViewRemoveColumns treeView

  -- Add icon column.
  playlistViewAddIconColumn treeView store

  -- Add column file info to tree view.
  forM_ (playlistBufferOptions buffer) (playlistViewAddColumn treeView store model)

  -- Set default sort rule.
  sortStatus <- readTVarIO $ playlistBufferSortStatus buffer
  playlistViewSortInternal view sortStatus

  return ()

-- | Internal sort function.
playlistViewSortInternal :: PlaylistView -> (MultimediaOption, SortType) -> IO ()
playlistViewSortInternal view (option, sortType) = do
  let options = playlistBufferOptions $ playlistViewBuffer view 
  lookup option options ?>= \x -> 
      treeSortableSetSortColumnId (playlistViewSortModel view) x sortType

-- | Add icon column.
playlistViewAddIconColumn :: (TreeViewClass self
                            ,TreeModelClass (model row)
                            ,TypedTreeModelClass model) 
                            => self 
                            -> model row 
                            -> IO ()
playlistViewAddIconColumn treeView store = do
  -- Any music format can work, we just need music icon pixbuf.
  pixbuf <- getIconPixbuf $ contentTypeGetIcon "audio/mp3"
    
  tvc <- treeViewColumnNew
  set tvc [treeViewColumnTitle := ""]
  treeViewAppendColumn treeView tvc
  
  icon <- cellRendererPixbufNew
  treeViewColumnPackStart tvc icon True
  cellLayoutSetAttributes tvc icon store $ \_ -> [cellPixbuf := pixbuf]

-- | Add column.
playlistViewAddColumn :: (MultimediaInfoClass t
                        ,TreeViewClass self1
                        ,TreeModelClass self
                        ,TreeModelSortClass self
                        ,TypedTreeModelClass model
                        ,TreeSortableClass self) 
                        => self1
                        -> model MultimediaInfo
                        -> self
                        -> (t, SortColumnId)
                        -> IO ()
playlistViewAddColumn treeView model sortModel option@(info,sortId) = do
  playlistViewSetSortFunc model sortModel option

  let name = getColumnTitle info
      maxWidth = getColumnMaxWidth info
  tvc <- treeViewAddColumnWithTitle treeView name sortId
  maxWidth ?>= \width -> treeViewColumnSetMaxWidth tvc width

  cell <- cellRendererTextNew
  treeViewColumnPackStart tvc cell True

  playlistViewSetCellText tvc cell model sortModel info

-- | Set sort function.
playlistViewSetSortFunc :: (TreeSortableClass self,
                           TypedTreeModelClass model,
                           MultimediaInfoClass a) =>
                          model MultimediaInfo
                        -> self
                        -> (a, SortColumnId)
                        -> IO ()
playlistViewSetSortFunc model sortModel (info, sortId) = 
  treeSortableSetSortFunc sortModel sortId $ \iter1 iter2 -> do
    row1 <- treeModelGetRow model iter1
    row2 <- treeModelGetRow model iter2
    compareRow info row1 row2

-- | Set cell text.
playlistViewSetCellText :: (CellLayoutClass self,
                        CellRendererTextClass cell,
                        TreeModelClass model,
                        TreeModelSortClass model,
                        TypedTreeModelClass model1,
                        MultimediaInfoClass a) 
                     => self
                     -> cell
                     -> model1 MultimediaInfo
                     -> model
                     -> a
                     -> IO ()
playlistViewSetCellText tvc cell model sortModel info = 
    cellLayoutSetAttributeFunc tvc cell sortModel $ \iter -> do
      row <- treeModelSortGetRow model sortModel iter
      set cell [cellText   := getCellText info row
               ,cellXAlign := getCellXAlign info]

-- | Next node.
playlistViewNextNode :: PlaylistView -> IO ()
playlistViewNextNode = treeViewFocusNextToplevelNode . playlistViewTreeView
    
-- | Previous node.
playlistViewPrevNode :: PlaylistView -> IO ()    
playlistViewPrevNode = treeViewFocusPrevToplevelNode . playlistViewTreeView

-- | Keymap.
playlistViewKeymap :: Map Text (PlaylistView -> IO ())
playlistViewKeymap = 
    M.fromList
         [("j",         playlistViewNextNode)
         ,("k",         playlistViewPrevNode)
         ,("Down",      playlistViewNextNode)
         ,("Up",        playlistViewPrevNode)          
         ,("J",         playlistViewScrollToBottom)
         ,("K",         playlistViewScrollToTop)
         ,(" ",         playlistViewScrollVerticalPage True)
         ,("b",         playlistViewScrollVerticalPage False)
         ,("PageDown",  playlistViewScrollVerticalPage True)
         ,("PageUp",    playlistViewScrollVerticalPage False)
         ,("m",         playlistViewPlay)
         ,("Return",    playlistViewPlay)
         ,("M",         playlistViewPause)
         ,("z",         playlistViewStop)
         ,("h",         playlistViewBackward)
         ,("l",         playlistViewForward)
         ,("Right",     playlistViewBackward)
         ,("Left",      playlistViewForward)
         ,(",",         playlistViewVolumeDec)
         ,(".",         playlistViewVolumeInc)
         ,("-",         playlistViewVolumeDec)
         ,("=",         playlistViewVolumeInc)
         ,("n",         playlistViewPlayNext)
         ,("p",         playlistViewPlayPrev)
         ,("N",         playlistViewPlayRandom)
         ,("P",         playlistViewSwitchPlayMode)
         ,("1",         playlistViewSortByTitle)
         ,("2",         playlistViewSortByAlbum)
         ,("3",         playlistViewSortByArtist)
         ,("4",         playlistViewSortByYear)
         ,("5",         playlistViewSortByTrack)
         ,("6",         playlistViewSortByBitRate)
         ,("7",         playlistViewSortByDuration)
         ]

-- | Sort by title.
playlistViewSortByTitle :: PlaylistView -> IO ()
playlistViewSortByTitle view = playlistViewSort view MOTitle

-- | Sort by album.
playlistViewSortByAlbum :: PlaylistView -> IO ()
playlistViewSortByAlbum view = playlistViewSort view MOAlbum

-- | Sort by artist.
playlistViewSortByArtist :: PlaylistView -> IO ()
playlistViewSortByArtist view = playlistViewSort view MOArtist

-- | Sort by year.
playlistViewSortByYear :: PlaylistView -> IO ()
playlistViewSortByYear view = playlistViewSort view MOYear

-- | Sort by track.
playlistViewSortByTrack :: PlaylistView -> IO ()
playlistViewSortByTrack view = playlistViewSort view MOTrack

-- | Sort by BitRate.
playlistViewSortByBitRate :: PlaylistView -> IO ()
playlistViewSortByBitRate view = playlistViewSort view MOBitRate

-- | Sort by duration.
playlistViewSortByDuration :: PlaylistView -> IO ()
playlistViewSortByDuration view = playlistViewSort view MODuration

-- | Sort column.
playlistViewSort :: PlaylistView -> MultimediaOption -> IO ()
playlistViewSort view option = do
  -- Get model and options.
  let model   = playlistViewSortModel view 
      buffer  = playlistViewBuffer view
      options = playlistBufferOptions buffer
  -- Get current sortType and columnId.
  (curSortType, _, curSortColumnId) <- treeSortableGetSortColumnId model
  lookup option options ?>= \id -> do
    treeSortableSetSortColumnId model id $
      if id == curSortColumnId
         -- Just change sort order when sort column id is same.
         then 
             -- Just change sort order.
             case curSortType of
               SortAscending  -> SortDescending
               SortDescending -> SortAscending
         -- Otherwise sort ascending.
         else SortAscending

    -- Get new sort type.
    (newSortType, _, _) <- treeSortableGetSortColumnId model

    -- Update sort status of buffer.
    writeTVarIO (playlistBufferSortStatus $ playlistViewBuffer view) (option, newSortType)

    -- Focus to cell.
    treeViewFocus (playlistViewTreeView view)

-- | Play current selected track.
playlistViewPlay :: PlaylistView -> IO ()
playlistViewPlay view = do
  let treeView = playlistViewTreeView view
  treeViewGetSelectedPath treeView
    >?>= \ path -> do
      -- Convert sorted path to unsorted model path.
      currentPath <- treeModelSortConvertPathToChildPath (playlistViewSortModel view) path
      playlistViewPlayInternal view currentPath

-- | Play next track randomly.
playlistViewPlayRandom :: PlaylistView -> IO ()
playlistViewPlayRandom view = do
  size <- treeViewGetToplevelNodeCount (playlistViewTreeView view)
  randomIndex <- randomRIO (0, size - 1)
  playlistViewPlayInternal view [randomIndex]

-- | Play next track.
playlistViewPlayNext :: PlaylistView -> IO ()
playlistViewPlayNext view = do
  currentPath <- readTVarIO $ playlistViewCurrentPlayPath view
  nextSortPath <- treeViewNextSortPath (playlistViewTreeView view) (playlistViewSortModel view) currentPath
  playlistViewPlayInternal view nextSortPath

-- | Play previous track.
playlistViewPlayPrev :: PlaylistView -> IO ()
playlistViewPlayPrev view = do
  currentPath <- readTVarIO $ playlistViewCurrentPlayPath view
  prevSortPath <- treeViewPrevSortPath (playlistViewTreeView view) (playlistViewSortModel view) currentPath
  playlistViewPlayInternal view prevSortPath

-- | The internal function of `playlistViewPlay'.
playlistViewPlayInternal :: PlaylistView -> TreePath -> IO ()
playlistViewPlayInternal view path = do
  -- Get file path and update play path status.
  filePath <- liftM miFilePath $ listStoreGetValue (playlistViewListStore view) (head path)
  let displayPath = filepathGetDisplayName filePath
  writeTVarIO (playlistViewCurrentPlayPath view) path

  -- Update play mode status.
  mode <- readTVarIO (playlistViewPlayMode view)
  pageViewUpdateInfoStatus view "PlayMode" (" PlayMode (" ++ show mode ++ ")")

  -- Update playing status.
  pageViewUpdateInfoStatus view "Playing" (" Playing : " ++ displayPath)

  -- Send daemon signal to play.
  processId <- getProcessID
  mkMplayerDaemonSignal (pageViewClient view) Play (PlayArgs filePath processId)

-- | Stop play.
playlistViewStop :: PlaylistView -> IO ()
playlistViewStop view = 
  mkMplayerDaemonSignal (pageViewClient view) Stop StopArgs

-- | Pause or continue play.
playlistViewPause :: PlaylistView -> IO ()
playlistViewPause view = 
  mkMplayerDaemonSignal (pageViewClient view) Pause PauseArgs

-- | Forward current track.
playlistViewForward :: PlaylistView -> IO ()
playlistViewForward view =
  mkMplayerDaemonSignal (pageViewClient view) Forward (ForwardArgs 10)

-- | Backward current track.
playlistViewBackward :: PlaylistView -> IO ()
playlistViewBackward view =
  mkMplayerDaemonSignal (pageViewClient view) Backward (BackwardArgs 10)

-- | Increase volume.
playlistViewVolumeInc :: PlaylistView -> IO ()
playlistViewVolumeInc view =
  mkMplayerDaemonSignal (pageViewClient view) VolumeInc (VolumeIncArgs 10)

-- | Decrease volume.
playlistViewVolumeDec :: PlaylistView -> IO ()
playlistViewVolumeDec view =
  mkMplayerDaemonSignal (pageViewClient view) VolumeDec (VolumeDecArgs 10)

-- | Begin.
playlistViewScrollToTop :: PlaylistView -> IO ()
playlistViewScrollToTop = 
    treeViewFocusFirstToplevelNode . playlistViewTreeView

-- | End.
playlistViewScrollToBottom :: PlaylistView -> IO ()
playlistViewScrollToBottom = 
    treeViewFocusLastToplevelNode . playlistViewTreeView 

-- | Scroll page vertically.
playlistViewScrollVerticalPage :: Bool -> PlaylistView -> IO ()
playlistViewScrollVerticalPage isDown a = do
  let sw = playlistViewScrolledWindow a
      tv = playlistViewTreeView a
  pageInc <- (<=<) adjustmentGetPageIncrement scrolledWindowGetVAdjustment sw
  treeViewScrollVertical tv sw (if isDown then pageInc else (- pageInc))

-- | Scroll step vertically.
playlistViewScrollVerticalStep :: Bool -> PlaylistView -> IO ()
playlistViewScrollVerticalStep isDown a = do
  let sw = playlistViewScrolledWindow a
      tv = playlistViewTreeView a
  stepInc <- (<<<=) i2d treeViewGetSelectedCellHeight tv
  treeViewScrollVertical tv sw (if isDown then stepInc else (- stepInc))

-- | Handle key action.
playlistViewHandleKeyAction :: PlaylistView -> Text -> SerializedEvent -> IO ()  
playlistViewHandleKeyAction view keystoke sEvent = 
  case M.lookup keystoke playlistViewKeymap of
    Just action -> action view
    Nothing -> widgetPropagateEvent (playlistViewTreeView view) sEvent