{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleInstances #-}
module Data.Raz.Sequence.Internal where

import Control.Applicative
import qualified Control.Monad as Monad
import Control.Monad.Random hiding (fromList)
import Data.Foldable (toList)
import qualified Data.List as List
import Data.Tuple (swap)
import System.IO.Unsafe
import Prelude hiding (lookup, zipWith)

import qualified Data.Raz.Core.Internal as Raz
import qualified Data.Raz.Core.Sequence as Raz
import Data.Raz.Util

-- * Types

data Seq' g a = Seq' !g !(Raz.Tree a)
  deriving (Functor, Foldable, Traversable)

instance Eq a => Eq (Seq' g a) where
  Seq' _ t == Seq' _ t' = t == t'

instance Ord a => Ord (Seq' g a) where
  compare (Seq' _ t) (Seq' _ t') = compare t t'

instance Show a => Show (Seq' g a) where
  showsPrec d (Seq' _ t) = showsPrec d t

-- |
-- @
-- pure :: a -> 'Impure' ('Seq' a)
-- (<*>) :: 'Seq' (a -> b) -> 'Seq' a -> 'Seq' b
-- @
instance Applicative (Seq' StdGen) where
  pure a = createSeq (Raz.Leaf a)
  (<*>) = seqLift2 Raz.ap

-- ** Synonyms

-- $convention
-- The actual type signatures of functions below are as general as possible.
-- Alternative signatures may appear in comments:
--   - A purely informative "enriched" type with 'Impure' and 'Splittable'
--   hinting implementation details.
--   - A specialization at type 'Seq' that parallels the @Data.Sequence@ API.

-- | The sequence type with a default generator.
type Seq = Seq' StdGen

-- | A type synonym for documentation. Marks uses of 'unsafePerformIO'.
type Impure a = a

-- | A type synonym for documentation. Marks functions that only use the
-- 'split' method of random generators.
type Splittable g = RandomGen g

-- * Construction

-- $implicit
-- Since RAZ makes use of randomness, a pure implementation will leak in the
-- interface (e.g., via 'MonadRandom' constraints or explicit generator
-- passing).
--
-- In order to provide the same interface as @Data.Sequence@ from @containers@,
-- we cheat by requesting a random generator via 'unsafePerformIO' and storing
-- it alongside the sequence when constructing it.
--
-- Functions that transform existing sequences can then be implemented purely.
--
-- Alternative construction functions ('empty'', 'singleton'', 'fromList'')
-- are provided for compatibility with other random generators.

-- |
-- @
-- empty :: 'Impure' (Seq a)
-- @
--
-- /O(1)/. The empty sequence.
empty :: Seq a
empty = empty' (unsafePerformIO newStdGen)

-- |
-- @
-- singleton :: a -> 'Impure' (Seq a)
-- @
--
-- /O(1)/. A singleton sequence.
singleton :: a -> Seq a
singleton a = singleton' (unsafePerformIO newStdGen) a

-- |
-- @
-- fromList :: [a] -> 'Impure' (Seq a)
-- @
--
-- /O(n)/. Create a sequence from a finite list of elements.
--
-- The inverse 'fromList' is given by the 'Foldable' instance of 'Seq'.
fromList :: [a] -> Seq a
fromList as = fromList' (unsafePerformIO newStdGen) as

-- |
-- @
-- fromFunction :: Int -> (Int -> a) -> 'Impure' (Seq a)
-- fromFunction n f = fromList (fmap f [0 .. n - 1])
-- @
--
-- /O(n)/.
fromFunction :: Int -> (Int -> a) -> Seq a
fromFunction n f = fromList (fmap f [0 .. n - 1])

-- |
-- @
-- (\<|) :: a -> 'Seq' a -> 'Seq' a
-- @
(<|) :: RandomGen g => a -> Seq' g a -> Seq' g a
a <| as = seqBind as (Raz.cons a)

-- |
-- @
-- (|>) :: 'Seq' a -> a -> 'Seq' a
-- @
(|>) :: RandomGen g => Seq' g a -> a -> Seq' g a
as |> a = seqBind as (`Raz.snoc` a)

-- |
-- @
-- (>\<) :: 'Seq' a -> 'Seq' a -> 'Seq' a
-- @
(><) :: RandomGen g => Seq' g a -> Seq' g a -> Seq' g a
s1 >< Seq' _ t2 = seqBind s1 (\t1 -> Raz.append t1 t2)

-- | /O(1)/. The empty sequence.
empty' :: g -> Seq' g a
empty' g = Seq' g Raz.Empty

-- | /O(1)/. A singleton sequence.
singleton' :: g -> a -> Seq' g a
singleton' g = Seq' g . Raz.Leaf

fromList' :: RandomGen g => g -> [a] -> Seq' g a
fromList' g as = seqRun g (Raz.fromList as)

-- ** Repetition

-- |
-- @
-- replicate :: Int -> a -> 'Impure' (Seq a)
-- @
--
-- /O(n)/.
replicate :: Int -> a -> Seq a
replicate n a = fromList $ List.replicate n a

-- |
-- @
-- replicateA :: Applicative f => Int -> f a -> f ('Impure' (Seq a))
-- @
--
-- /O(n)/.
replicateA :: Applicative f => Int -> f a -> f (Seq a)
replicateA n a = fromList <$> Monad.replicateM n a

-- |
-- @
-- replicateM :: Monad m => Int -> m a -> m ('Impure' (Seq a))
-- @
--
-- /O(n)/.
replicateM :: Monad m => Int -> m a -> m (Seq a)
replicateM n = unwrapMonad . replicateA n . WrapMonad

-- |
-- @
-- cycleTaking :: Int -> 'Seq' a -> 'Seq' a
-- @
--
-- /O(k)/.
cycleTaking :: RandomGen g => Int -> Seq' g a -> Seq' g a
cycleTaking k (Seq' g t) = fromList' g . List.take k . cycle . toList $ t

-- ** Iterative construction

-- |
-- @
-- iterateN :: Int -> (a -> a) -> a -> 'Impure' (Seq a)
-- @
--
-- /O(n)/.
iterateN :: Int -> (a -> a) -> a -> Seq a
iterateN n f = fromList . List.take n . List.iterate f

-- |
-- @
-- unfoldr :: (b -> Maybe (a, b)) -> b -> 'Impure' (Seq a)
-- @
--
-- /O(n)/, where @n@ is the length of the output sequence.
unfoldr :: (b -> Maybe (a, b)) -> b -> Seq a
unfoldr f = fromList . List.unfoldr f

-- |
-- @
-- unfoldl :: (b -> Maybe (b, a)) -> b -> 'Impure' (Seq a)
-- @
--
-- /O(n)/, where @n@ is the length of the output sequence.
unfoldl :: (b -> Maybe (b, a)) -> b -> Seq a
unfoldl f = fromList . reverse . List.unfoldr (fmap swap . f)

-- * Deconstruction

-- ** Queries

-- |
-- @
-- null :: 'Seq' a -> Bool
-- @
--
-- /O(1)/. Is this the empty sequence?
null :: Seq' g a -> Bool
null (Seq' _ Raz.Empty) = True
null _ = False

-- |
-- @
-- length :: 'Seq' a -> Int
-- @
--
-- /O(1)/. The number of elements in the sequence.
length :: Seq' g a -> Int
length (Seq' _ t) = Raz.size t

-- ** Views

data ViewL' g a
  = EmptyL
  | a :< Seq' g a

viewl :: Seq' g a -> ViewL' g a
viewl (Seq' _ Raz.Empty) = EmptyL
viewl (Seq' g t) = Raz.viewC raz :< Seq' g t'
  where
    raz = Raz.focusL t
    t' = Raz.unfocus . Raz.removeC Raz.R $ raz

-- * Sublists

-- |
-- @
-- tails :: 'Splittable' g => Seq' g a -> Seq' g (Seq' g a)
-- tails :: 'Seq' a -> 'Seq' ('Seq' a)
-- @
tails :: RandomGen g => Seq' g a -> Seq' g (Seq' g a)
tails s = seqBind s (Raz.tailsWith splitting)
  where
    splitting a = getSplit <&> \g -> Seq' g a

-- ** Sequential searches

-- |
-- @
-- takeWhileL :: (a -> Bool) -> Seq a -> Seq a
-- @
takeWhileL :: (a -> Bool) -> Seq' g a -> Seq' g a
takeWhileL = seqLift . Raz.takeWhileL

-- |
-- @
-- takeWhileR :: (a -> Bool) -> 'Seq' a -> 'Seq' a
-- @
takeWhileR :: (a -> Bool) -> Seq' g a -> Seq' g a
takeWhileR = seqLift . Raz.takeWhileR

-- |
-- @
-- dropWhileL :: (a -> Bool) -> 'Seq' a -> 'Seq' a
-- @
dropWhileL :: (a -> Bool) -> Seq' g a -> Seq' g a
dropWhileL = seqLift . Raz.dropWhileL

-- |
-- @
-- dropWhileR :: (a -> Bool) -> 'Seq' a -> 'Seq' a
-- @
dropWhileR :: (a -> Bool) -> Seq' g a -> Seq' g a
dropWhileR = seqLift . Raz.dropWhileR

-- |
-- @
-- spanl :: 'Splittable' g => (a -> Bool) -> Seq' g a -> (Seq' g a, Seq' g a)
-- spanl :: (a -> Bool) -> Seq a -> (Seq a, Seq a)
-- @
spanl :: RandomGen g => (a -> Bool) -> Seq' g a -> (Seq' g a, Seq' g a)
spanl = seqLiftSplit . Raz.spanL

-- |
-- @
-- spanr :: 'Splittable' g => (a -> Bool) -> Seq' g a -> (Seq' g a, Seq' g a)
-- spanr :: (a -> Bool) -> 'Seq' a -> ('Seq' a, 'Seq' a)
-- @
spanr :: RandomGen g => (a -> Bool) -> Seq' g a -> (Seq' g a, Seq' g a)
spanr = seqLiftSplit . Raz.spanR

-- |
-- @
-- breakl :: 'Splittable' g => (a -> Bool) -> Seq' g a -> (Seq' g a, Seq' g a)
-- breakl :: (a -> Bool) -> 'Seq' a -> ('Seq' a, 'Seq' a)
-- @
breakl :: RandomGen g => (a -> Bool) -> Seq' g a -> (Seq' g a, Seq' g a)
breakl = seqLiftSplit . Raz.breakL

-- |
-- @
-- breakr :: 'Splittable' g => (a -> Bool) -> Seq' g a -> (Seq' g a, Seq' g a)
-- breakr :: (a -> Bool) -> 'Seq' a -> ('Seq' a, 'Seq' a)
-- @
breakr :: RandomGen g => (a -> Bool) -> Seq' g a -> (Seq' g a, Seq' g a)
breakr = seqLiftSplit . Raz.breakR

-- |
-- @
-- partition :: 'Splittable' g => (a -> Bool) -> Seq' g a -> (Seq' g a, Seq' g a)
-- partition :: (a -> Bool) -> 'Seq' a -> ('Seq' a, 'Seq' a)
-- @
partition :: RandomGen g => (a -> Bool) -> Seq' g a -> (Seq' g a, Seq' g a)
partition = seqLiftSplit . Raz.partition

-- |
-- @
-- filter :: (a -> Bool) -> 'Seq' a -> 'Seq' a
-- @
filter :: (a -> Bool) -> Seq' g a -> Seq' g a
filter = seqLift . Raz.filter

-- * Indexing

-- |
-- @
-- lookup :: Int -> Seq a -> Maybe a
-- @
lookup :: Int -> Seq' g a -> Maybe a
lookup = seqApply . Raz.lookup

-- |
-- @
-- (?!) :: Seq a -> Int -> Maybe a
-- @
(?!) :: Seq' g a -> Int -> Maybe a
(?!) = flip lookup

-- |
-- @
-- index :: Seq' g a -> Int -> a
-- @
index :: Seq' g a -> Int -> a
index s n = seqApply (\t -> Raz.index t n) s

-- |
-- @
-- adjust :: (a -> a) -> Int -> 'Seq' a -> 'Seq' a
-- @
adjust :: (a -> a) -> Int -> Seq' g a -> Seq' g a
adjust f = seqLift . Raz.adjust f

-- |
-- @
-- adjust' :: (a -> a) -> Int -> 'Seq' a -> 'Seq' a
-- @
adjust' :: (a -> a) -> Int -> Seq' g a -> Seq' g a
adjust' f = seqLift . Raz.adjust' f

-- |
-- @
-- update :: Int -> a -> 'Seq' a -> 'Seq' a
-- @
update :: Int -> a -> Seq' g a -> Seq' g a
update n = seqLift . Raz.update n

-- |
-- @
-- take :: Int -> 'Seq' a -> 'Seq' a
-- @
take :: Int -> Seq' g a -> Seq' g a
take = seqLift . Raz.take

-- |
-- @
-- drop :: Int -> 'Seq' a -> 'Seq' a
-- @
drop :: Int -> Seq' g a -> Seq' g a
drop = seqLift . Raz.drop

-- |
-- @
-- insertAt :: Int -> a -> 'Seq' a -> 'Seq' a
-- @
insertAt :: RandomGen g => Int -> a -> Seq' g a -> Seq' g a
insertAt n = seqDnib . Raz.insertAt n

-- |
-- @
-- deleteAt :: Int -> 'Seq' a -> 'Seq' a
-- @
deleteAt :: Int -> Seq' g a -> Seq' g a
deleteAt = seqLift . Raz.deleteAt

-- |
-- @
-- splitAt :: 'Splittable' g => Int -> Seq' g a -> (Seq' g a, Seq' g a)
-- splitAt :: Int -> Seq a -> (Seq a, Seq a)
-- @
splitAt :: RandomGen g => Int -> Seq' g a -> (Seq' g a, Seq' g a)
splitAt = seqLiftSplit . Raz.splitAt

-- * Transformations

-- |
-- @
-- mapWithIndex :: (Int -> a -> b) -> Seq a -> Seq b
-- @
mapWithIndex :: (Int -> a -> b) -> Seq' g a -> Seq' g b
mapWithIndex = seqLift . Raz.mapWithIndex

-- |
-- @
-- traverseWithIndex :: Applicative f => (Int -> a -> f b) -> Seq a -> Seq b
-- @
traverseWithIndex
  :: Applicative f => (Int -> a -> f b) -> Seq' g a -> f (Seq' g b)
traverseWithIndex = seqLens . Raz.traverseWithIndex

-- * Zips

-- |
-- @
-- zip :: Seq a -> Seq b -> Seq (a, b)
-- @
zip :: Seq' g a -> Seq' g b -> Seq' g (a, b)
zip = zipWith (,)

-- |
-- @
-- zipWith :: (a -> b -> c) -> Seq a -> Seq b -> Seq c
-- @
zipWith :: (a -> b -> c) -> Seq' g a -> Seq' g b -> Seq' g c
zipWith = seqLift2 . Raz.zipWith

-- * Random generator manipulations

splitSeq :: Splittable g => Seq' g a -> (Seq' g a, Seq' g a)
splitSeq (Seq' g t) = (Seq' g1 t, Seq' g2 t)
  where
    (g1, g2) = split g

-- | Put a fresh generator.
refreshSeq :: Seq' g a -> Impure (Seq a)
refreshSeq (Seq' _ t) = createSeq t

-- | Wrap a 'Raz.Tree' into a 'Seq'.
createSeq :: Raz.Tree a -> Impure (Seq a)
createSeq t = Seq' (unsafePerformIO newStdGen) t

seqBind :: Seq' g a -> (Raz.Tree a -> Rand g (Raz.Tree b)) -> Seq' g b
seqBind (Seq' g t) f = Seq' g' t'
  where
    (t', g') = runRand (f t) g

seqDnib :: (Raz.Tree a -> Rand g (Raz.Tree b)) -> Seq' g a -> Seq' g b
seqDnib = flip seqBind

seqRun :: g -> Rand g (Raz.Tree a) -> Seq' g a
seqRun g t = Seq' g' t'
  where
    (t', g') = runRand t g

seqLift :: (Raz.Tree a -> Raz.Tree b) -> Seq' g a -> Seq' g b
seqLift f (Seq' g t) = Seq' g (f t)

seqLift2
  :: (Raz.Tree a -> Raz.Tree b -> Raz.Tree c)
  -> Seq' g a -> Seq' g b -> Seq' g c
seqLift2 f (Seq' g ta) (Seq' _ tb) = Seq' g (f ta tb)

seqLiftSplit
  :: Splittable g
  => (Raz.Tree a -> (Raz.Tree b, Raz.Tree c))
  -> Seq' g a -> (Seq' g b, Seq' g c)
seqLiftSplit f (Seq' g t) = (Seq' g1 t1, Seq' g2 t2)
  where
    (g1, g2) = split g
    (t1, t2) = f t

seqApply :: (Raz.Tree a -> b) -> Seq' g a -> b
seqApply f (Seq' _ t) = f t

seqLens
  :: Functor f
  => (Raz.Tree a -> f (Raz.Tree b))
  -> Seq' g a -> f (Seq' g b)
seqLens f (Seq' g a) = Seq' g <$> f a