{-# LANGUAGE RankNTypes, TupleSections, FlexibleContexts, MultiParamTypeClasses, FlexibleInstances #-}



module Data.Text.Region (

	pt, start, lineStart, regionLength, till, linesSize, regionLines, emptyRegion, line,

	regionSize, expandLines, atRegion, overlaps, applyMap, cutMap, insertMap,

	cutRegion, insertRegion,

	EditAction(..), replace, cut, paste, overwrite, apply, update, undo,



	module Data.Text.Region.Types

	) where



import Prelude hiding (id, (.))

import Prelude.Unicode



import Control.Category

import Control.Lens



import Data.Text.Region.Types



-- | Make 'Point' from line and column

pt  Int  Int  Point

pt = Point



-- | 'Point' at the beginning

start  Point

start = pt 0 0



-- | 'Point' at the beginning of line

lineStart  Int  Point

lineStart l = pt l 0



-- | Regions length

regionLength  Lens' Region Size

regionLength = lens fromr tor where

	fromr (Region f t) = t .-. f

	tor (Region f _) sz = Region f (f .+. sz)



-- | Region from one 'Point' to another

till  Point  Point  Region

l `till` r = Region (min l r) (max l r)



-- | Distance of @n@ lines

linesSize  Int  Size

linesSize = pt 0



-- 'Region' height in lines, any 'Region' at least of line height 1

regionLines  Lens' Region Int

regionLines = lens fromr tor where

	fromr (Region f t) = succ $ (t ^. pointLine) - (f ^. pointLine)

	tor (Region f t) l = Region f (set pointLine (f ^. pointLine + l) t)



-- | Is 'Region' empty

emptyRegion  Region  Bool

emptyRegion r = r ^. regionFrom  r ^. regionTo



-- | n'th line region, starts at the beginning of line and ends on the next line

line  Int  Region

line l = lineStart l `till` lineStart (succ l)



-- | Make 'Region' by start position and 'Size'

regionSize  Point  Size  Region

regionSize pt' sz = pt' `till` (pt' .+. sz)



-- | Expand 'Region' to contain full lines

expandLines  Region  Region

expandLines (Region f t) = lineStart (f ^. pointLine) `till` lineStart (succ $ t ^. pointLine)



-- | Get contents at 'Region'

atRegion  Editable s  Region  Lens' (Contents s) (Contents s)

atRegion r = lens fromc toc where

	fromc cts = cts ^. splitted (r ^. regionTo) . _1 . splitted (r ^. regionFrom) . _2

	toc cts cts' = (cts ^. splitted (r ^. regionFrom) . _1) `concatCts` cts' `concatCts` (cts ^. splitted (r ^. regionTo) . _2)



-- | Does regions overlaps

overlaps  Region  Region  Bool

overlaps l r

	| r ^. regionFrom  l ^. regionTo = False

	| r ^. regionTo  l ^. regionFrom = False

	| otherwise = True



applyMap  Map  Region  Region

applyMap = view  mapIso



-- | Cut 'Region' mapping

cutMap  Region  Map

cutMap rgn = Map $ iso (cutRegion rgn) (insertRegion rgn)



-- | Opposite to 'cutMap'

insertMap  Region  Map

insertMap = invert  cutMap



-- | Update second 'Region' position as if it was data cutted at first 'Region'

cutRegion  Region  Region  Region

cutRegion (Region is ie) (Region s e) = Region

	(if is < s then (s .-. ie) .+. is else s)

	(if is < e then (e .-. ie) .+. is else e)



-- | Update second region position as if it was data inserted at first region (region sets insertion point and data size)

-- Region tries not to extend if data inserted at region bound except when region is empty

-- This allows define replace as cut and insert in special case when we replace region itself

insertRegion  Region  Region  Region

insertRegion (Region is ie) (Region s e)

	| (s  e)  (is  s) = Region is ie

	| otherwise = Region

		(if is  s then (s .-. is) .+. ie else s)

		(if is < e then (e .-. is) .+. ie else e)



class Editable s  EditAction e s where

	-- | Make replace action over 'Region' and 'Contents'

	replaceAction  Region  Contents s  e s

	-- | Make 'Map' from action

	actionMap  e s  Map

	-- | Perform action, modifying 'Contents'

	perform  e s  Contents s  Contents s

	-- | Get action undo

	inversed  e s  Contents s  e s



-- | Replace region with data

replace  EditAction e s  Region  s  e s

replace r = replaceAction r  view contents



-- | Cuts region

cut  EditAction e s  Region  e s

cut r = replaceAction r emptyContents



-- | Pastes 'Contents' at some 'Point'

paste  EditAction e s  Point  s  e s

paste p = replaceAction (p `till` p)  view contents



-- | Overwrites 'Contents' at some 'Point'

overwrite  EditAction e s  Point  s  e s

overwrite p c = replaceAction (p `regionSize` measure cts) cts where

	cts = view contents c



-- | 'perform' for 'Edit'

apply  Editable s  Edit s  s  s

apply = over contents  perform



-- | Get undo

undo  Editable s  Edit s  s  Edit s

undo e = inversed e  view contents



-- | Update regions

update  (Editable s, Regioned r)  Edit s  r  r

update e = over regions (applyMap  actionMap $ e)



instance Editable s  EditAction Replace s where

	replaceAction = Replace

	actionMap (Replace r w) = insertMap (r & regionLength .~ measure w) `mappend` cutMap r

	perform (Replace r w) cts = cts & atRegion r .~ w

	inversed (Replace r w) cts = Replace (r & regionLength .~ measure w) (cts ^. atRegion r)



instance Editable s  EditAction Edit s where

	replaceAction rgn txt = Edit [replaceAction rgn txt]

	actionMap = foldr go mempty  view replaces where

		go r m = actionMap (over replaceRegion (applyMap m) r) `mappend` m

	perform = snd  foldr go (mempty, id)  view replaces where

		go r (m, fn) = (actionMap r' `mappend` m, perform r'  fn) where

			r' = over replaceRegion (applyMap m) r

	inversed e@(Edit rs) cts = Edit [Replace (applyMap m r) (cts ^. atRegion r) | Replace r _  rs] where

		m = actionMap e