{-|
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 Client.Configuration (LayoutMode(..))
import Client.Image.LineWrap (lineWrap, terminate)
import Client.Image.PackedImage (Image', unpackImage)
import Client.Image.Palette (palWindowDivider)
import Client.Image.StatusLine (statusLineImage, minorStatusLineImage)
import Client.Image.Textbox (textboxImage)
import Client.State
import Client.State.Focus ( Focus, Subfocus )
import Client.View (viewLines)
import Control.Lens (view)
import Graphics.Vty.Attributes (defAttr)
import Graphics.Vty.Image

-- | Compute the combined image for all the visible message windows.
drawLayout ::
  ClientState            {- ^ client state                                     -} ->
  (Int, Int, Int, Int, Image) {- ^ overscroll, cursor row, cursor col, 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, Subfocus)]    {- ^ extra windows                -} ->
  (Int, Int, Int, Int, Image) {- ^ overscroll and final image   -}
drawLayoutOne st extrafocus =
  (overscroll, row, col, nextOffset, output)
  where
    w      = view clientWidth st
    h:hs   = splitHeights (rows - saveRows) (length extraLines)
    scroll = view clientScroll st

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

    output = vertCat $ reverse
           $ main
           : [ drawExtra st w h' foc subfoc imgs
                 | (h', (foc, subfoc, 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, subfocus, viewLines focus subfocus w st)
                   | (focus, subfocus) <- extrafocus ]

-- | Layout algorithm for all windows in a single column.
drawLayoutTwo ::
  ClientState            {- ^ client state                                -} ->
  [(Focus, Subfocus)]    {- ^ extra windows                               -} ->
  (Int, Int, Int, Int, Image) {- ^ overscroll, cursor row, cursor col, offset, final image -}
drawLayoutTwo st extrafocus =
  (overscroll, row, col, 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 subfoc imgs
                 | (h', (foc, subfoc, imgs)) <- zip hs extraLines]

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

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

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

drawMain ::
  Int         {- ^ draw width      -} ->
  Int         {- ^ draw height     -} ->
  Int         {- ^ scroll amount   -} ->
  ClientState {- ^ client state    -} ->
  (Int,Int,Int,Int,Image)
drawMain w h scroll st = (overscroll, row, col, 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
    (row, col, nextOffset, tbImage) = textboxImage 3 w st


-- | Draw one of the extra windows from @/splits@
drawExtra ::
  ClientState {- ^ client state    -} ->
  Int         {- ^ draw width      -} ->
  Int         {- ^ draw height     -} ->
  Focus       {- ^ focus           -} ->
  Subfocus    {- ^ subfocus        -} ->
  [Image']    {- ^ image lines     -} ->
  Image       {- ^ rendered window -}
drawExtra st w h focus subfocus lineImages =
    msgImg <-> unpackImage (minorStatusLineImage focus subfocus 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
