-- 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.FileManager.DiredBuffer where

import Control.Applicative
import Control.Concurrent.STM 
import Control.Monad
import Data.ByteString.UTF8
import DBus.Client hiding (Signal)
import Data.Ord (comparing)
import Data.Typeable
import Graphics.UI.Gtk.General.Enums
import Graphics.UI.Gtk.ModelView.TreeSortable
import Manatee.Core.DBus
import Manatee.Core.Types
import Manatee.Extension.FileManager.PageMode
import Manatee.Toolkit.General.List
import Manatee.Toolkit.General.Maybe
import Manatee.Toolkit.General.Misc
import Manatee.Toolkit.General.STM
import Manatee.Toolkit.General.Time
import Manatee.Toolkit.Gio.Gio
import Manatee.Toolkit.Gtk.Concurrent
import System.GIO
import System.GIO.File.FileAttribute
import System.Glib.GDateTime
import System.Locale
import System.Time

import qualified Data.Map as M

data DiredBuffer = 
    DiredBuffer {diredBufferCurrentDirectory    :: TVar FilePath
                ,diredBufferClient              :: Client
                ,diredBufferPageId              :: PageId
                ,diredBufferFileInfoOptions     :: [(FileInfoOption, SortColumnId)]
                ,diredBufferFileInfos           :: TVar [DiredFileInfo]
                ,diredBufferSortStatus          :: TVar (FileInfoOption, SortType)
                ,diredBufferIconPixbufDatabase  :: TVar FileIconPixbufDatabase
                ,diredBufferMode                :: PageMode
                ,diredBufferBroadcastChannel    :: TChan DiredBufferSignal
                }
    deriving Typeable

data DiredBufferSignal = UpdateBuffer FilePath
                       | UpdateView (Maybe String)
                         deriving (Show, Eq, Ord)

data DiredFileInfo =
    DiredFileInfo {dfiNameDescrible    :: (String,    String)
                  ,dfiTypeDescrible    :: (FileType,  String)
                  ,dfiSizeDescrible    :: (Integer,   String)
                  ,dfiMimeDescrible    :: (String,    String)
                  ,dfiMtimeDescrible   :: (GTimeVal,  String)
                  ,dfiPermDescrible    :: (String,    String)
                  }
    deriving Show

data FileInfoOption = FIName | FISize | FIMime | FIMTime | FIPermission
                    deriving (Eq, Show, Read)

class DiredFileInfoClass a where
    getColumnTitle      :: a -> String                 -- get title for treeColumn 
    getCellText         :: a -> DiredFileInfo -> String -- get text for cell
    getCellXAlign       :: a -> Float                  -- get x align for cell
    compareRow          :: a -> DiredFileInfo -> DiredFileInfo -> IO Ordering -- compare row for treeView

instance DiredFileInfoClass FileInfoOption where
      getColumnTitle FIName       = "File name"
      getColumnTitle FISize       = "Size"
      getColumnTitle FIMime       = "Type"
      getColumnTitle FIMTime      = "Modification time"
      getColumnTitle FIPermission = "Permission"

      getCellText FIName       info = snd $ dfiNameDescrible  info
      getCellText FISize       info = snd $ dfiSizeDescrible  info
      getCellText FIMime       info = snd $ dfiMimeDescrible  info
      getCellText FIMTime      info = snd $ dfiMtimeDescrible info
      getCellText FIPermission info = snd $ dfiPermDescrible  info

      getCellXAlign FIName       = 0.0
      getCellXAlign FISize       = 1.0
      getCellXAlign FIMime       = 0.0
      getCellXAlign FIMTime      = 0.0
      getCellXAlign FIPermission = 0.0

      compareRow FIName       row1 row2 = return $ diredFileInfoNameCompare row1 row2
      compareRow FISize       row1 row2 = return $ comparing fst (dfiSizeDescrible  row1) (dfiSizeDescrible  row2)
      compareRow FIMime       row1 row2 = return $ comparing fst (dfiMimeDescrible  row1) (dfiMimeDescrible  row2)
      compareRow FIMTime      row1 row2 = return $ comparing fst (dfiMtimeDescrible row1) (dfiMtimeDescrible row2)
      compareRow FIPermission row1 row2 = return $ comparing fst (dfiPermDescrible  row1) (dfiPermDescrible  row2)

-- | New.
diredBufferNew :: FilePath -> Client -> PageId -> IO DiredBuffer
diredBufferNew dir client pageId = do
  -- Create buffer.
  buffer <- DiredBuffer <$> newTVarIO dir 
                        <*> pure client 
                        <*> pure pageId 
                        <*> pure (pairPred [FIName, FISize, FIMime, FIMTime, FIPermission])
                        <*> newTVarIO []
                        <*> newTVarIO (FIName, SortAscending) 
                        <*> newTVarIO M.empty 
                        <*> pure diredMode
                        <*> (newTChanIO :: IO (TChan DiredBufferSignal)) 

  -- Load directory content.
  diredBufferLoad buffer dir

  -- Listen broadcast channel.
  diredBufferListenChannel buffer

  return buffer

-- | Listen broadcast channel.
diredBufferListenChannel :: DiredBuffer -> IO ()
diredBufferListenChannel DiredBuffer {diredBufferClient                 = client
                                     ,diredBufferPageId                 = pageId
                                     ,diredBufferMode                   = mode
                                     ,diredBufferBroadcastChannel       = channel
                                     } = 
  listenBufferChannel channel $ \ signal ->
    case signal of
      UpdateBuffer path -> 
         -- Send SynchronizationPathName signal to daemon process.
         mkDaemonSignal client SynchronizationPathName (SynchronizationPathNameArgs (pageModeName mode) pageId path)
      _ -> return ()

-- | Load.
diredBufferLoad :: DiredBuffer -> FilePath -> IO ()
diredBufferLoad buffer dir = do
  -- Get file infos.
  infos <- directoryGetFileInfos (fromString dir)
  fileInfos <- diredBufferGenerateFileInfos infos

  -- Update value.
  writeTVarIO (diredBufferCurrentDirectory buffer) dir
  writeTVarIO (diredBufferFileInfos buffer) fileInfos
  forM_ infos $ \info -> 
      modifyTVarIOM (diredBufferIconPixbufDatabase buffer)
                    (updateFileIconPixbufDatabase info)

-- | Get file infos.
diredBufferGenerateFileInfos :: [FileInfo] -> IO [DiredFileInfo]
diredBufferGenerateFileInfos infos = 
  forM infos $ \info -> do
    -- Get Type.
    typeDes <- do
      let fType = fileInfoGetFileType info
          title = show fType
      return (fType, title)

    -- Get Name.
    nameDes <- do
      let fName = maybeError (fileInfoGetName info) 
                  "diredBufferGenerateFileInfos - `fileInfoGetName`." 
          title = maybeError (fileInfoGetDisplayName info)
                  "diredBufferGenerateFileInfos - `fileInfoGetDisplayName`."
      return (toString fName, title)

    -- Get Size.
    sizeDes <- do
      let fSize = toInteger $ fileInfoGetSize info
          title = formatFileSizeForDisplay fSize
      return (fSize, title)

    -- Get MIME.
    mimeDes <- do
      let fMime = maybeError (fileInfoGetContentType info) 
                  "diredBufferGenerateFileInfos - `fileInfoGetContentType`."
          title = contentTypeGetDescription fMime
          -- title = fMime
      return (fMime, title)

    -- Get modification time.
    mtimeDes <- do
      let fTime = fileInfoGetModificationTime info
      title <- do
        calTime <- toCalendarTime $ gTimeValToClockTime fTime
        return (formatCalendarTime defaultTimeLocale "%Y/%m/%d %T" calTime)
      return (fTime, title)

    -- Get permission.
    permissionDes <- do
      let isDir  = fileInfoGetFileType info == FileTypeDirectory
      canRead    <- fileInfoGetAttributeBool info fileAttributeAccessCanRead
      canWrite   <- fileInfoGetAttributeBool info fileAttributeAccessCanWrite
      canExecute <- fileInfoGetAttributeBool info fileAttributeAccessCanExecute
      let permission =    
              (if isDir         then "d" else "-")
           ++ (if canRead       then "r" else "-") 
           ++ (if canWrite      then "w" else "-") 
           ++ (if canExecute    then "x" else "-")
      return (permission, permission)

    return $ DiredFileInfo nameDes typeDes sizeDes mimeDes mtimeDes permissionDes
  
-- | Compare file name.
diredFileInfoNameCompare :: DiredFileInfo -> DiredFileInfo -> Ordering
diredFileInfoNameCompare row1 row2 = 
  compareFileWithType (fName1, fType1) (fName2, fType2)
  where fType1 = fst $ dfiTypeDescrible row1
        fType2 = fst $ dfiTypeDescrible row2
        fName1 = fst $ dfiNameDescrible row1
        fName2 = fst $ dfiNameDescrible row2