{-# Language MultiParamTypeClasses, BangPatterns, TemplateHaskell #-}

{-|
Module      : Client.State.Window
Description : Types and operations for managing message buffers.
Copyright   : (c) Eric Mertens, 2016
License     : ISC
Maintainer  : emertens@gmail.com

This module defines types and operations used to store messages for display
in the client's buffers.
-}

module Client.State.Window
  (
  -- * Windows
    Window(..)
  , winMessages
  , winUnread
  , winTotal
  , winMention
  , winMarker

  -- * Window lines
  , WindowLine(..)
  , wlSummary
  , wlText
  , wlImage
  , wlFullImage
  , wlImportance
  , wlTimestamp

  -- * Window line importance
  , WindowLineImportance(..)

  -- * Window operations
  , emptyWindow
  , addToWindow
  , windowSeen
  , windowActivate
  , windowDeactivate
  ) where

import           Client.Image.PackedImage
import           Client.Message
import           Control.Lens
import           Data.Text (Text)
import           Data.Time (UTCTime)
import           Graphics.Vty.Image (Image)

-- | A single message to be displayed in a window
data WindowLine = WindowLine
  { _wlSummary    :: !IrcSummary  -- ^ Summary value
  , _wlText       :: {-# UNPACK #-} !Text -- ^ Searchable text form
  , _wlImage'     :: !Image'      -- ^ Normal rendered image
  , _wlFullImage' :: !Image'      -- ^ Detailed rendered image
  , _wlImportance :: !WindowLineImportance -- ^ Importance of message
  , _wlTimestamp  :: {-# UNPACK #-} !UTCTime
  }

data WindowLines
  = {-# UNPACK #-} !WindowLine :- WindowLines
  | Nil

-- | A 'Window' tracks all of the messages and metadata for a particular
-- message buffer.
data Window = Window
  { _winMessages :: !WindowLines   -- ^ Messages to display, newest first
  , _winMarker   :: !(Maybe Int)   -- ^ Location of line drawn to indicate newer messages
  , _winUnread   :: !Int           -- ^ Messages added since buffer was visible
  , _winTotal    :: !Int           -- ^ Messages in buffer
  , _winMention  :: !WindowLineImportance -- ^ Indicates an important event is unread
  }

data ActivityLevel = NoActivity | NormalActivity | HighActivity
  deriving (Eq, Ord, Read, Show)

-- | Flag for the important of a message being added to a window
data WindowLineImportance
  = WLBoring -- ^ Don't update unread count
  | WLNormal -- ^ Increment unread count
  | WLImportant -- ^ Increment unread count and set important flag
  deriving (Eq, Ord, Show, Read)

makeLenses ''Window
makeLenses ''WindowLine


-- | Lens for the '_wlImage' field viewed in unpacked form.
wlImage :: Lens' WindowLine Image
wlImage = wlImage' . _Image'
{-# INLINE wlImage #-}

-- | Lens for the '_wlFullImage' field viewed in unpacked form.
wlFullImage :: Lens' WindowLine Image
wlFullImage = wlFullImage' . _Image'
{-# INLINE wlFullImage #-}


-- | A window with no messages
emptyWindow :: Window
emptyWindow = Window
  { _winMessages = Nil
  , _winMarker   = Nothing
  , _winUnread   = 0
  , _winTotal    = 0
  , _winMention  = WLBoring
  }

-- | Adds a given line to a window as the newest message. Window's
-- unread count will be updated according to the given importance.
addToWindow :: WindowLine -> Window -> Window
addToWindow !msg !win = Window
    { _winMessages = msg :- view winMessages win
    , _winTotal    = view winTotal win + 1
    , _winMarker   = do i <- view winMarker win; return $! i+1
    , _winUnread   = if view wlImportance msg == WLBoring
                     then view winUnread win
                     else view winUnread win + 1
    , _winMention  = max (view winMention win) (view wlImportance msg)
    }

-- | Update the window clearing the unread count and important flag.
windowSeen :: Window -> Window
windowSeen = set winUnread 0
           . set winMention WLBoring


-- | Update the window when it first becomes active. If only /boring/
-- messages have been added since last time the marker will be hidden.
windowActivate :: Window -> Window
windowActivate win
  | view winUnread win == 0 = set winMarker Nothing win
  | otherwise               = win


-- | Update the window when it becomes inactive. This resets the activity
-- marker to the bottom of the window.
windowDeactivate :: Window -> Window
windowDeactivate = set winMarker (Just 0)


instance Each WindowLines WindowLines WindowLine WindowLine where
  each _ Nil = pure Nil
  each f (x :- xs) = (:-) <$> f x <*> each f xs