-- Copyright (C) 2008 JP Bernardy
-- | emacs-style rectangle manipulation functions.
module Yi.Rectangle where

import Yi.Prelude
import Prelude (subtract)
import Data.List (splitAt, unzip)
import Control.Applicative
import Data.Char
import Data.List (sort, length, zipWith, transpose)

import Yi.Buffer
import Yi.Editor
import Yi.String
import Text.Regex.TDFA

alignRegion :: String -> BufferM ()
alignRegion str = modifyRegionClever (alignText str) =<< unitWiseRegion Line =<< getSelectRegionB
    where regexSplit :: String -> String -> [String]
          regexSplit regex l = case l =~ regex of
              AllTextSubmatches (_:matches) -> matches
              _ -> error "regexSplit: text does not match"
          
          alignText :: String -> String -> String
          alignText regex text = unlines' ls'
            where ls, ls' :: [String]
                  ls = lines' text
                  columns, columns' :: [[String]]
                  columns = fmap (regexSplit regex) ls
                  columnsWidth :: [Int]
                  columnsWidth =  fmap (maximum . fmap length) $ transpose columns
                  columns' = fmap (zipWith padLeft columnsWidth) columns
                  
                  ls' = fmap concat columns'



-- | Align each line of the region on the given regex.
-- Fails if it is not found in any line.
alignRegionOn :: String -> BufferM ()
alignRegionOn s = alignRegion ("^(.*)(" ++ s ++ ")(.*)")

-- | Get the selected region as a rectangle.
-- Returns the region extended to lines, plus the start and end columns of the rectangle.
getRectangle :: BufferM (Region, Int, Int)
getRectangle = do
    r <- getSelectRegionB
    extR <- unitWiseRegion Line r
    [lowCol,highCol] <- sort <$> mapM colOf [regionStart r, regionEnd r]
    return (extR, lowCol, highCol)

-- | Split a list at the boundaries given
multiSplit :: [Int] -> [a] -> [[a]]
multiSplit [] l = [l]
multiSplit (x:xs) l = left : multiSplit (fmap (subtract x) xs) right
    where (left,right) = splitAt x l

onRectangle :: (Int -> Int -> String -> String) -> BufferM ()
onRectangle f = do
    (reg, l, r) <- getRectangle 
    modifyRegionB (mapLines (f l r)) reg

openRectangle :: BufferM ()
openRectangle = onRectangle openLine
    where openLine l r line = left ++ replicate (r-l) ' ' ++ right
              where (left,right) = splitAt l line

stringRectangle :: String -> BufferM ()
stringRectangle inserted = onRectangle stringLine
    where stringLine l r line = left ++ inserted ++ right
              where [left,_,right] = multiSplit [l,r] line

killRectangle :: EditorM ()
killRectangle = do
    cutted <- withBuffer0 $ do
        (reg, l, r) <- getRectangle
        text <- readRegionB reg
        let (cutted, rest) = unzip $ fmap cut $ lines' text
            cut line = let [left,mid,right] = multiSplit [l,r] line in (mid, left ++ right)
        replaceRegionB reg (unlines' rest)
        return cutted
    setRegE (unlines' cutted)

yankRectangle :: EditorM ()
yankRectangle = do
    text <- lines' <$> getRegE
    withBuffer0 $ do
        forM_ text $ \t -> do
            savingPointB $ insertN t
            lineDown