{-|
Module      : Client.Image.LineWrap
Description : Chat message view
Copyright   : (c) Eric Mertens, 2016
License     : ISC
Maintainer  : emertens@gmail.com

Provides utilities for line wrapping images.
-}

module Client.Image.LineWrap
  ( lineWrap
  , lineWrapPrefix
  , fullLineWrap
  , terminate
  ) where

import Client.Image.PackedImage (char, imageText, imageWidth, splitImage, string, Image')
import Graphics.Vty.Image qualified as Vty
import Graphics.Vty.Attributes (defAttr)
import Data.Text.Lazy qualified as L


-- | Trailing space with default attributes deals with bug in VTY
-- where the formatting will continue past the end of chat messages.
-- This adds an extra space if a line doesn't end on the terminal edge.
terminate ::
  Int       {- ^ terminal width  -} ->
  Vty.Image {- ^ unwrapped image -} ->
  Vty.Image {- ^ wrapped image   -}
terminate n img
  | Vty.imageWidth img == n = img
  | otherwise               = img Vty.<|> Vty.char defAttr ' '


-- | This version of line wrap wraps without regard for word boundaries.
fullLineWrap ::
  Int       {- ^ terminal width  -} ->
  Image'    {- ^ unwrapped image -} ->
  [Image']  {- ^ wrapped image   -}
fullLineWrap w img
  | iw <= w = [img]
  | otherwise = l : fullLineWrap w r
  where
    iw = imageWidth img
    (l,r) = splitImage w img

lineWrapPrefix ::
  Int       {- ^ terminal width  -} ->
  Image'    {- ^ prefix image    -} ->
  Image'    {- ^ unwrapped image -} ->
  [Image']  {- ^ wrapped image   -}
lineWrapPrefix w pfx img
  | 3*pfxW <= w = pfx <> char defAttr ' ' <> x :
                  map (pad<>) xs
  where
    pfxW = imageWidth pfx
    x:xs = lineWrap (w - pfxW - 1) img
    pad  = string defAttr (replicate (pfxW + 1) ' ')

-- Don't index when the window is tiny
lineWrapPrefix w pfx img = lineWrap w (pfx <> char defAttr ' ' <> img)


lineWrap ::
  Int      {- ^ first line length     -} ->
  Image'   {- ^ image                 -} ->
  [Image'] {- ^ splits                -}
lineWrap w img
  | imageWidth img <= w = [img] -- could be empty
  | otherwise           = lineWrap' w img


lineWrap' ::
  Int      {- ^ first line length     -} ->
  Image'   {- ^ image                 -} ->
  [Image'] {- ^ splits                -}
lineWrap' w img
  | imgW == 0 = []
  | imgW <= w = [img]
  | otherwise = l : lineWrap' w (dropSpaces r)
  where
    imgW = imageWidth img
    x:xs = splitOptions img

    (l,r) = splitImage width img

    width
      | x <= w = go x xs
      | otherwise = w

    go y [] = min y w
    go y (z:zs)
      | z-y > w = w
      | z > w = y
      | otherwise = go z zs


-- | List of image widths suitable for breaking the image on
-- that correspond to word breaks.
splitOptions :: Image' -> [Int]
splitOptions
  = dropWhile (0==)
  . scanl1 (\x y -> 1 + x + y)
  . map (Vty.wcswidth . L.unpack)
  . L.split (' '==)
  . imageText


-- | Drop the leading spaces from an image
dropSpaces :: Image' -> Image'
dropSpaces img
  | n == 0    = img
  | otherwise = snd (splitImage n img)
  where
    n = fromIntegral $ L.length $ L.takeWhile (' '==) $ imageText img
