module System.Console.Haskeline.Backend.WCWidth(
                            gsWidth,
                            splitAtWidth,
                            takeWidth,
                            ) where

-- Certain characters are "wide", i.e. take up two spaces in the terminal.
-- This module wraps the necessary foreign routines, and also provides some convenience
-- functions for width-breaking code.

import System.Console.Haskeline.LineState

import Data.List
import Foreign.C.Types

foreign import ccall unsafe haskeline_mk_wcwidth :: CWchar -> CInt

wcwidth :: Char -> Int
wcwidth c = case haskeline_mk_wcwidth $ toEnum $ fromEnum c of
                -1 -> 0 -- Control characters have zero width.  (Used by the
                        -- "\SOH...\STX" hack in LineState.stringToGraphemes.)
                w -> fromIntegral w

gWidth :: Grapheme -> Int
gWidth g = wcwidth (baseChar g)

gsWidth :: [Grapheme] -> Int
gsWidth = foldl' (+) 0 . map gWidth

-- | Split off the maximal list which is no more than the given width.
-- returns the width of that list.
splitAtWidth :: Int -> [Grapheme] -> ([Grapheme],[Grapheme],Int)
splitAtWidth n xs = case splitAtWidth' n xs of
                        (this,rest,remaining) -> (this,rest,n-remaining)

-- Returns the amount of unused space in the line.
splitAtWidth' :: Int -> [Grapheme] -> ([Grapheme],[Grapheme],Int)
splitAtWidth' w [] = ([],[],w)
splitAtWidth' w (g:gs)
    | gw > w = ([],g:gs,w)
    | otherwise = (g:gs',gs'',r)
  where
    gw = gWidth g
    (gs',gs'',r) = splitAtWidth' (w-gw) gs

-- Returns the longest prefix less than or equal to the given width
-- plus the width of that list.
takeWidth :: Int -> [Grapheme] -> ([Grapheme],Int)
takeWidth n gs = case splitAtWidth n gs of
                    (gs',_,len) -> (gs',len)