{-# LANGUAGE ExistentialQuantification #-}
module Data.Binding.List (module Data.Binding.Simple, BindingList, toBindingList, fromBindingList, length, position, seek, seekBy, next, prev, remove', remove, insert', insert) where

import Prelude hiding (length)
import qualified Prelude as P
import Control.Monad

import Data.Binding.Simple

-- | Binding List
data BindingList v a = Variable v => BindingList {source :: Source v a -- ^ the list's binding source
                                                 ,list   :: v [v a]    -- ^ the bound list
                                                 ,pos    :: v Int}     -- ^ the current position
-- [v a] is itself in a Variable, to allow for insertions and deletions.

-- | Create a binding list.
toBindingList :: Variable v => [a] -> IO (BindingList v a)
toBindingList [] = error "empty list"
toBindingList list = do list'<- mapM newVar list >>= newVar
                        source <- newVar (head list)
                        pos <- newVar 0
                        return $ BindingList source list' pos

-- | Update the binding list from the 'source'.
update :: BindingList v a -> IO ()
update (BindingList source list pos) = do list' <- readVar list
                                          pos' <- readVar pos
                                          readVar source >>= writeVar (list' !! pos')

-- | Extract the data from a binding list.
fromBindingList :: Variable v => BindingList v a -> IO [a]
fromBindingList b = do update b
                       readVar (list b) >>= mapM readVar

-- | interface to the binding list's 'Source'
instance Variable v => Variable (BindingList v) where
   {- WARNING warn "Did you mean to use newBindingList?" -}
   newVar = warn where warn a = toBindingList [a]
   readVar = readVar . source
   writeVar = writeVar . source
   modifyVar = modifyVar . source
   modifyVar' = modifyVar' . source

instance Variable v => Bindable (BindingList v) where
   bind = bind . source

-- | The size of a binding list.
length :: Variable v => BindingList v a -> IO Int
length b = do list <- readVar (list b)
              return $ P.length list

-- | Get the current position.
position :: Variable v => BindingList v a -> IO Int
position b = readVar $ pos b

-- | Bind to a new position in a binding list.
-- Returns the new position; this is convenient for 'seekBy' and friends.
seek:: Variable v => BindingList v a -> Int -> IO Int
seek b new = do pos' <- readVar $ pos b
                if pos' == new then return new else update b >> seek' b new

-- | Unconditional seek. Called after elements have changed position.
seek':: BindingList v a -> Int -> IO Int
seek' (BindingList source list pos) new = do list' <- readVar list
                                             readVar (list' !! new) >>= writeVar source
                                             writeVar pos new
                                             return new

-- | Bind to a new position in a binding list.
seekBy :: Variable v => (Int -> Int) -> BindingList v a -> IO Int
seekBy f bindingList = do pos <- readVar (pos bindingList)
                          seek bindingList $ f pos

-- | Bind to the next item in a binding list.
next :: Variable v => BindingList v a -> IO Int
next = seekBy succ

-- | Bind to the previous item in a binding list.
prev :: Variable v => BindingList v a -> IO Int
prev = seekBy pred

-- | Remove an element from a list.
remove' :: [a] -> Int ->  [a]
remove' list pos = let (xs, _:ys) = splitAt pos list
                   in xs ++ ys

-- | Remove the current element from the list.
remove :: BindingList v a -> IO Int
remove b@(BindingList _ list pos) = do list' <- readVar list
                                       pos' <- readVar pos
                                       writeVar list $ remove' list' pos'
                                       seek' b (if pos' == P.length list' - 1 then pos' - 1 else pos')

-- | Insert an element into a list.
insert' :: [a] -> Int -> a -> [a]
insert' list pos x = let (xs, ys) = splitAt pos list
                     in xs ++ [x] ++ ys

-- | Insert an element into the list.
-- The new element is inserted after the current element.
-- This allows appending, but precludes prepending.
insert :: BindingList v a -> a -> IO Int
insert b@(BindingList _ list pos) x = do update b
                                         list' <- readVar list
                                         pos' <- readVar pos
                                         x' <- newVar x
                                         let pos'' = pos' + 1
                                         writeVar list $ insert' list' pos'' x'
                                         seek' b pos''