{-|
Module      : Client.Image.Layout
Description : Layout code for the multi-window splits
Copyright   : (c) Eric Mertens, 2016
License     : ISC
Maintainer  : emertens@gmail.com

-}
module Client.Image.Layout (scrollAmount, drawLayout) where

import Control.Lens
import Client.State
import Client.State.Focus
import Client.Configuration (LayoutMode(..))
import Client.Image.PackedImage (Image', unpackImage)
import Client.Image.StatusLine (statusLineImage, minorStatusLineImage)
import Client.Image.Textbox
import Client.Image.LineWrap (lineWrap, terminate)
import Client.Image.Palette
import Client.View
import Graphics.Vty.Image
import Graphics.Vty.Attributes (defAttr)

-- | Compute the combined image for all the visible message windows.
drawLayout ::
  ClientState            {- ^ client state                                     -} ->
  (Int, Int, Int, Image) {- ^ overscroll, cursor pos, next offset, final image -}
drawLayout st =
  case view clientLayout st of
    TwoColumn | not (null extrafocus) -> drawLayoutTwo st extrafocus
    _                                 -> drawLayoutOne st extrafocus
  where
    extrafocus = clientExtraFocuses st

-- | Layout algorithm for all windows in a single column.
drawLayoutOne ::
  ClientState            {- ^ client state                 -} ->
  [Focus]                {- ^ extra window names           -} ->
  (Int, Int, Int, Image) {- ^ overscroll and final image   -}
drawLayoutOne st extrafocus =
  (overscroll, pos, nextOffset, output)
  where
    w      = view clientWidth st
    h:hs   = splitHeights (rows - saveRows) (length extraLines)
    scroll = view clientScroll st

    (overscroll, pos, nextOffset, main) =
        drawMain w (saveRows + h) scroll st

    output = vertCat $ reverse
           $ main
           : [ drawExtra st w h' foc imgs
                 | (h', (foc, imgs)) <- zip hs extraLines]

    rows = view clientHeight st

    -- don't count textbox or the main status line against the main window's height
    saveRows = 1 + imageHeight (statusLineImage w st)

    extraLines = [ (focus', viewLines focus' FocusMessages w st)
                   | focus' <- extrafocus ]

-- | Layout algorithm for all windows in a single column.
drawLayoutTwo ::
  ClientState            {- ^ client state                                -} ->
  [Focus]                {- ^ extra window names                          -} ->
  (Int, Int, Int, Image) {- ^ overscroll, cursor pos, offset, final image -}
drawLayoutTwo st extrafocus =
  (overscroll, pos, nextOffset, output)
  where
    [wl,wr] = divisions (view clientWidth st - 1) 2
    hs      = divisions (rows - length extraLines) (length extraLines)
    scroll = view clientScroll st

    output = main <|> divider <|> extraImgs

    extraImgs = vertCat $ reverse
             [ drawExtra st wr h' foc imgs
                 | (h', (foc, imgs)) <- zip hs extraLines]

    (overscroll, pos, nextOffset, main) =
        drawMain wl rows scroll st

    pal     = clientPalette st
    divider = charFill (view palWindowDivider pal) ' ' 1 rows
    rows    = view clientHeight st

    extraLines = [ (focus', viewLines focus' FocusMessages wr st)
                   | focus' <- extrafocus ]

drawMain ::
  Int         {- ^ draw width      -} ->
  Int         {- ^ draw height     -} ->
  Int         {- ^ scroll amount   -} ->
  ClientState {- ^ client state    -} ->
  (Int,Int,Int,Image)
drawMain w h scroll st = (overscroll, pos, nextOffset, msgs <-> bottomImg)
  where
    focus = view clientFocus st
    subfocus = view clientSubfocus st

    msgLines = viewLines focus subfocus w st

    (overscroll, msgs) = messagePane w h' scroll msgLines

    h' = max 0 (h - imageHeight bottomImg)

    bottomImg = statusLineImage w st <-> tbImage
    (pos, nextOffset, tbImage) = textboxImage w st


-- | Draw one of the extra windows from @/splits@
drawExtra ::
  ClientState {- ^ client state    -} ->
  Int         {- ^ draw width      -} ->
  Int         {- ^ draw height     -} ->
  Focus       {- ^ focus           -} ->
  [Image']    {- ^ image lines     -} ->
  Image       {- ^ rendered window -}
drawExtra st w h focus lineImages =
    msgImg <-> unpackImage (minorStatusLineImage focus w True st)
  where
    (_, msgImg) = messagePane w h 0 lineImages


-- | Generate an image corresponding to the image lines of the given
-- focus and subfocus. Returns the number of lines overscrolled to
-- assist in clamping scroll to the lines available in the window.
messagePane ::
  Int          {- ^ client width                  -} ->
  Int          {- ^ available rows                -} ->
  Int          {- ^ current scroll                -} ->
  [Image']     {- ^ focused window                -} ->
  (Int, Image) {- ^ overscroll, rendered messages -}
messagePane w h scroll images = (overscroll, img)
  where
    vimg   = assemble emptyImage images
    vimg1  = cropBottom h vimg
    img    = charFill defAttr ' ' w (h - imageHeight vimg1)
             <-> vimg1

    overscroll = vh - imageHeight vimg
    vh         = h + scroll

    assemble acc _ | imageHeight acc >= vh = cropTop vh acc
    assemble acc [] = acc
    assemble acc (x:xs) = assemble (this <-> acc) xs
      where
        this = vertCat
             $ map (terminate w . unpackImage)
             $ lineWrap w x


splitHeights ::
  Int   {- ^ screen rows to fill               -} ->
  Int   {- ^ number of extra windows           -} ->
  [Int] {- ^ list of heights for each division -}
splitHeights h ex = divisions (h - ex) (1 + ex)


-- | Constructs a list of numbers with the length of the divisor
-- and that sums to the dividend. Each element will be within
-- one of the quotient.
divisions ::
  Int {- ^ dividend -} ->
  Int {- ^ divisor  -} ->
  [Int]
divisions x y
  | y <= 0    = []
  | otherwise = replicate r (q+1) ++ replicate (y-r) q
  where
    (q,r) = quotRem (max 0 x) y



-- | Compute the number of lines in a page at the current window size
scrollAmount ::
  ClientState {- ^ client state  -} ->
  Int         {- ^ scroll amount -}
scrollAmount st =
  case view clientLayout st of
    TwoColumn -> h
    OneColumn -> head (splitHeights h ex) -- extra will be equal to main or 1 smaller
  where
    layout = view clientLayout st

    h = view clientHeight st - bottomSize
    ex = length (clientExtraFocuses st)

    bottomSize = 1 -- textbox
               + imageHeight (statusLineImage mainWidth st)

    mainWidth =
      case layout of
        TwoColumn -> head (divisions (view clientWidth st - 1) 2)
        OneColumn -> view clientWidth st