{-# LANGUAGE ExistentialQuantification #-}
-- |This module provides wrapper widgets for enforcing an upper bound
-- on the size of child widgets in one or more dimensions in rows or
-- columns, respectively.  This differs from the ''fixed'' widgets in
-- the Fixed module in that Fixed widgets enforce a fixed size
-- regardless of how big or small the child widget is, and add padding
-- to guarantee that the fixed size is honored.
module Graphics.Vty.Widgets.Limits
    ( VLimit
    , HLimit
    , hLimit
    , vLimit
    , boxLimit
    , setVLimit
    , setHLimit
    , addToVLimit
    , addToHLimit
    , getVLimit
    , getHLimit
    )
where

import Control.Monad
import Graphics.Vty
import Graphics.Vty.Widgets.Core
import Graphics.Vty.Widgets.Util

data HLimit a = (Show a) => HLimit Int (Widget a)

instance Show (HLimit a) where
    show (HLimit i _) = "HLimit { width = " ++ show i ++ ", ... }"

-- |Impose a maximum horizontal size, in columns, on a 'Widget'.
hLimit :: (Show a) => Int -> Widget a -> IO (Widget (HLimit a))
hLimit maxWidth child = do
  let initSt = HLimit maxWidth child
  wRef <- newWidget initSt $ \w ->
      w { growHorizontal_ = const $ return False
        , growVertical_ = const $ growVertical child
        , render_ = \this s ctx -> do
                   HLimit width ch <- getState this
                   let region = s `withWidth` fromIntegral (min (toEnum width) (region_width s))
                   render ch region ctx

        , setCurrentPosition_ =
            \this pos -> do
              HLimit _ ch <- getState this
              setCurrentPosition ch pos

        , getCursorPosition_ = \this -> do
              HLimit _ ch <- getState this
              getCursorPosition ch
        }
  wRef `relayKeyEvents` child
  wRef `relayFocusEvents` child
  return wRef

data VLimit a = (Show a) => VLimit Int (Widget a)

instance Show (VLimit a) where
    show (VLimit i _) = "VLimit { height = " ++ show i ++ ", ... }"

-- |Impose a maximum vertical size, in columns, on a 'Widget'.
vLimit :: (Show a) => Int -> Widget a -> IO (Widget (VLimit a))
vLimit maxHeight child = do
  let initSt = VLimit maxHeight child
  wRef <- newWidget initSt $ \w ->
      w { growHorizontal_ = const $ growHorizontal child
        , growVertical_ = const $ return False

        , render_ = \this s ctx -> do
                   VLimit height ch <- getState this
                   let region = s `withHeight` fromIntegral (min (toEnum height) (region_height s))
                   render ch region ctx

        , setCurrentPosition_ =
            \this pos -> do
              VLimit _ ch <- getState this
              setCurrentPosition ch pos

        , getCursorPosition_ = \this -> do
              VLimit _ ch <- getState this
              getCursorPosition ch
        }
  wRef `relayKeyEvents` child
  wRef `relayFocusEvents` child
  return wRef

-- |Set the vertical limit of a child widget's size.
setVLimit :: Widget (VLimit a) -> Int -> IO ()
setVLimit wRef lim =
    when (lim >= 1) $
         updateWidgetState wRef $ \(VLimit _ ch) -> VLimit lim ch

-- |Set the horizontal limit of a child widget's size.
setHLimit :: Widget (HLimit a) -> Int -> IO ()
setHLimit wRef lim =
    when (lim >= 1) $
         updateWidgetState wRef $ \(HLimit _  ch) -> HLimit lim ch

-- |Add to the vertical limit of a child widget's size.
addToVLimit :: Widget (VLimit a) -> Int -> IO ()
addToVLimit wRef delta = do
  lim <- getVLimit wRef
  setVLimit wRef $ lim + delta

-- |Add to the horizontal limit of a child widget's size.
addToHLimit :: Widget (HLimit a) -> Int -> IO ()
addToHLimit wRef delta = do
  lim <- getHLimit wRef
  setHLimit wRef $ lim + delta

-- |Get the vertical limit of a child widget's size.
getVLimit :: Widget (VLimit a) -> IO Int
getVLimit wRef = do
  (VLimit lim _) <- state <~ wRef
  return lim

-- |Get the horizontal limit of a child widget's size.
getHLimit :: Widget (HLimit a) -> IO Int
getHLimit wRef = do
  (HLimit lim _) <- state <~ wRef
  return lim

-- |Impose a horizontal and vertical upper bound on the size of a
-- widget.
boxLimit :: (Show a) =>
            Int -- ^Maximum width in columns
         -> Int -- ^Maximum height in rows
         -> Widget a
         -> IO (Widget (VLimit (HLimit a)))
boxLimit maxWidth maxHeight w = do
  ch <- hLimit maxWidth w
  vLimit maxHeight ch