{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE FlexibleInstances, TypeSynonymInstances,
   MultiParamTypeClasses, DeriveDataTypeable, OverloadedStrings #-}
-----------------------------------------------------------------------------
--
-- Module      :  IDE.Pane.Errors
-- Copyright   :  2007-2011 Juergen Nicklisch-Franken, Hamish Mackenzie
-- License     :  GPL
--
-- Maintainer  :  maintainer@leksah.org
-- Stability   :  provisional
-- Portability :
--
-- | A pane which displays a list of errors
--
-----------------------------------------------------------------------------

module IDE.Pane.Errors (
    IDEErrors
,   ErrorsState
,   fillErrorList
,   addErrorToList
,   selectError
,   getErrors
,   selectMatchingErrors
) where

import Graphics.UI.Gtk
import Data.Typeable (Typeable)
import IDE.Core.State
import IDE.ImportTool
       (addResolveMenuItems, resolveErrors)
import Data.List (elemIndex)
import IDE.LogRef (showSourceSpan)
import Control.Monad.IO.Class (MonadIO(..))
import IDE.Utils.GUIUtils (getDarkState, treeViewContextMenu, __)
import Data.Text (Text)
import Control.Monad (foldM_, unless, void, when, forM_)
import qualified Data.Text as T
       (intercalate, lines, takeWhile, length, drop)
import Data.IORef (writeIORef, readIORef, newIORef, IORef)
import Data.Maybe (isNothing)
import qualified Data.Foldable as F (toList)
import qualified Data.Sequence as Seq (null, elemIndexL)


-- | The representation of the Errors pane
data IDEErrors      =   IDEErrors {
    scrolledView    ::   ScrolledWindow
,   treeView        ::   TreeView
,   errorStore      ::   ListStore ErrColumn
,   autoClose       ::   IORef Bool -- ^ If the pane was only displayed to show current error
} deriving Typeable


-- | The data for a single row in the Errors pane
data ErrColumn = ErrColumn {
    logRef     :: LogRef,
    background :: Text}


-- | The additional state used when recovering the pane
data ErrorsState    =   ErrorsState
   deriving (Eq,Ord,Read,Show,Typeable)


instance Pane IDEErrors IDEM
    where
    primPaneName _  =   __ "Errors"
    getTopWidget    =   castToWidget . scrolledView
    paneId _b       =   "*Errors"


instance RecoverablePane IDEErrors ErrorsState IDEM where
    saveState _p     =   return (Just ErrorsState)
    recoverState pp ErrorsState = do nb <- getNotebook pp
                                     buildPane pp nb builder
    builder = builder'

-- | Builds an 'IDEErrors' pane together with a list of
--   event 'Connections'
builder' :: PanePath ->
    Notebook ->
    Window ->
    IDEM (Maybe IDEErrors, Connections)
builder' _pp _nb _windows = reifyIDE $ \ ideR -> do
    errorStore   <- listStoreNew []
    treeView     <- treeViewNew
    treeViewSetModel treeView errorStore

    rendererA    <- cellRendererTextNew
    colA         <- treeViewColumnNew
    treeViewColumnSetTitle colA (__ "Description")
    treeViewColumnSetSizing colA TreeViewColumnAutosize
    treeViewColumnSetResizable colA True
    treeViewColumnSetReorderable colA True
    treeViewAppendColumn treeView colA
    cellLayoutPackStart colA rendererA False
    cellLayoutSetAttributes colA rendererA errorStore
        $ \row -> [cellText := removeIndentation (refDescription (logRef row)),
                   cellTextBackground := background row ]
    rendererB    <- cellRendererTextNew
    colB         <- treeViewColumnNew
    treeViewColumnSetTitle colB (__ "Location")
    treeViewColumnSetSizing colB TreeViewColumnAutosize
    treeViewColumnSetResizable colB True
    treeViewColumnSetReorderable colB True
    treeViewAppendColumn treeView colB
    cellLayoutPackStart colB rendererB False
    cellLayoutSetAttributes colB rendererB errorStore
        $ \row -> [ cellText := showSourceSpan (logRef row),
                   cellTextBackground := background row ]
    treeViewSetHeadersVisible treeView False
    selB <- treeViewGetSelection treeView
    treeSelectionSetMode selB SelectionMultiple
    scrolledView <- scrolledWindowNew Nothing Nothing
    scrolledWindowSetShadowType scrolledView ShadowIn
    containerAdd scrolledView treeView
    scrolledWindowSetPolicy scrolledView PolicyAutomatic PolicyAutomatic
    autoClose <- newIORef False
    let pane = IDEErrors {..}
    cid1 <- after treeView focusInEvent $ do
        liftIO $ reflectIDE (makeActive pane) ideR
        return True
    (cid2, cid3) <- treeViewContextMenu treeView $ errorsContextMenu ideR errorStore treeView
    cid4 <- treeView `on` rowActivated $ errorsSelect ideR errorStore
    reflectIDE (fillErrorList' pane) ideR
    return (Just pane, map ConnectC [cid1, cid2, cid3, cid4])


-- | Removes the unnecessary indentation
removeIndentation :: Text -> Text
removeIndentation t = T.intercalate "\n" $ map (T.drop minIndent) l
  where
    l = T.lines t
    minIndent = minimum $ map (T.length . T.takeWhile (== ' ')) l


-- | Get the Errors pane
getErrors :: Maybe PanePath -> IDEM IDEErrors
getErrors Nothing    = forceGetPane (Right "*Errors")
getErrors (Just pp)  = forceGetPane (Left pp)


-- | Repopulates the Errors pane
fillErrorList :: Bool -- ^ Whether to display the Errors pane
              -> IDEAction
fillErrorList False = getPane >>= maybe (return ()) fillErrorList'
fillErrorList True = getErrors Nothing  >>= \ p -> fillErrorList' p >> displayPane p False


-- | Fills the pane with the error list from the IDE state
fillErrorList' :: IDEErrors -> IDEAction
fillErrorList' pane = do
    refs <- readIDE errorRefs
    ac   <- liftIO $ readIORef (autoClose pane)
    when (Seq.null refs && ac) . void $ closePane pane
    isDark <- getDarkState
    liftIO $ do
        let store = errorStore pane
        listStoreClear store
        forM_ (zip (F.toList refs) [0..]) $ \ (lr, index) ->
            listStoreInsert store index $ ErrColumn lr (
                (if even index then fst else snd) $
                (if isDark then fst else snd) $
                    case logRefType lr of
                        WarningRef -> (("#282000", "#201900"), ("#FFF1DE", "#FFF5E8"))
                        LintRef    -> (("#003000", "#002800"), ("#DBFFDB", "#EDFFED"))
                        _          -> (("#380000", "#280000"), ("#FFDEDE", "#FFEBEB")))


-- | Add any LogRef to the Errors pane at a given index
addErrorToList :: Bool -- ^ Whether to display the pane
               -> Int  -- ^ The index to insert at
               -> LogRef 
               -> IDEAction
addErrorToList False index lr = getPane >>= maybe (return ()) (addErrorToList' index lr)
addErrorToList True  index lr = getErrors Nothing  >>= \ p -> addErrorToList' index lr p >> displayPane p False


-- | Add a 'LogRef' at a specific index to the Errors pane
addErrorToList' :: Int -> LogRef -> IDEErrors -> IDEAction
addErrorToList' index lr pane = do
--    refs <- readIDE errorRefs
    ac   <- liftIO $ readIORef (autoClose pane)
--    when (null refs && ac) . void $ closePane pane
    isDark <- getDarkState
    liftIO $ do
        let store = errorStore pane
--        listStoreClear store
--        forM_ (zip (toList refs) [0..]) $ \ (lr, index) ->
        listStoreInsert store index $ ErrColumn lr (
                (if even index then fst else snd) $
                (if isDark then fst else snd) $
                    case logRefType lr of
                        WarningRef -> (("#282000", "#201900"), ("#FFF1DE", "#FFF5E8"))
                        LintRef    -> (("#003000", "#002800"), ("#DBFFDB", "#EDFFED"))
                        _          -> (("#380000", "#280000"), ("#FFDEDE", "#FFEBEB")))


-- | Get the currently selected error
getSelectedError ::  TreeView
    -> ListStore ErrColumn
    -> IO (Maybe LogRef)
getSelectedError treeView store = do
    treeSelection   <-  treeViewGetSelection treeView
    paths           <-  treeSelectionGetSelectedRows treeSelection
    case paths of
        [a]:r ->  do
            val     <-  listStoreGetValue store a
            return (Just (logRef val))
        _  ->  return Nothing

-- | Select an error in the Errors pane
selectError :: Maybe LogRef -- ^ When @Nothing@, the first error in the list is selected
            -> IDEAction
selectError mbLogRef = do
    (mbPane :: Maybe IDEErrors) <- getPane
    errorRefs' <- readIDE errorRefs
    errors     <- getErrors Nothing
    when (isNothing mbPane) $ do
        liftIO $ writeIORef (autoClose errors) True
        displayPane errors False
    liftIO $ do
        selection <- treeViewGetSelection (treeView errors)
        case mbLogRef of
            Nothing -> do
                size <- listStoreGetSize (errorStore errors)
                unless (size == 0) $
                    treeViewScrollToCell (treeView errors) (Just [0]) Nothing Nothing
                treeSelectionUnselectAll selection
            Just lr -> case lr `Seq.elemIndexL` errorRefs' of
                        Nothing  -> return ()
                        Just ind -> do
                            treeViewScrollToCell (treeView errors) (Just [ind]) Nothing Nothing
                            treeSelectionSelectPath selection [ind]


-- | Constructs the context menu for the Errors pane
errorsContextMenu :: IDERef
                  -> ListStore ErrColumn
                  -> TreeView
                  -> Menu
                  -> IO ()
errorsContextMenu ideR store treeView theMenu = do
    mbSel           <-  getSelectedError treeView store
    item0           <-  menuItemNewWithLabel (__ "Resolve Errors")
    item0 `on` menuItemActivate $ reflectIDE resolveErrors ideR
    menuShellAppend theMenu item0
    case mbSel of
        Just sel -> addResolveMenuItems ideR theMenu sel
        Nothing -> return ()


-- | Highlight an error refered to by the 'TreePath' in the given 'TreeViewColumn'
errorsSelect :: IDERef
                -> ListStore ErrColumn
                -> TreePath
                -> TreeViewColumn
                -> IO ()
errorsSelect ideR store [index] _ = do
    ref <- listStoreGetValue store index
    reflectIDE (setCurrentError (Just (logRef ref))) ideR
errorsSelect _ _ _ _ = return ()


-- | Select the matching errors for a 'SrcSpan' in the Errors 
--   pane, or none at all
selectMatchingErrors :: Maybe SrcSpan -- ^ When @Nothing@, unselects any errors in the pane 
                     -> IDEM ()
selectMatchingErrors mbSpan = do
    mbErrors <- getPane
    case mbErrors of
        Nothing -> return ()
        Just pane  ->
            liftIO $ do
                treeSel <- treeViewGetSelection (treeView pane)
                case mbSpan of
                    Nothing -> treeSelectionUnselectAll treeSel
                    Just (SrcSpan file lStart cStart lEnd cEnd) -> do
                        size <- listStoreGetSize (errorStore pane)
                        foldM_ (\ haveScrolled n -> do
                            mbIter <- treeModelGetIter (errorStore pane) [n]
                            case mbIter of
                                Nothing -> return False
                                Just iter -> do
                                    ErrColumn {logRef = ref@LogRef{..}} <- listStoreGetValue (errorStore pane) n
                                    isSelected <- treeSelectionIterIsSelected treeSel iter
                                    let shouldBeSel = file == logRefFullFilePath ref
                                                     && (lStart, cStart) <= (srcSpanEndLine     logRefSrcSpan,
                                                                               srcSpanEndColumn   logRefSrcSpan)
                                                     && (lEnd, cEnd)     >= (srcSpanStartLine   logRefSrcSpan,
                                                                               srcSpanStartColumn logRefSrcSpan)
                                    when (isSelected && not shouldBeSel) $ treeSelectionUnselectIter treeSel iter
                                    when (not isSelected && shouldBeSel) $ do
                                        unless haveScrolled $ treeViewScrollToCell (treeView pane) (Just [n]) Nothing Nothing
                                        treeSelectionSelectIter treeSel iter
                                    return $ haveScrolled || shouldBeSel)
                            False (take size [0..])


