{-# LANGUAGE OverloadedStrings    #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE Strict               #-}
{-# LANGUAGE CPP                  #-}

module Data.Container.Vector (module Data.Container.Vector, module X) where

import qualified Prelude as P
import qualified Data.Text as UTF16
import           Prologue hiding (Text, length, splitHead, concat, take, drop, index)

import           Control.Monad.ST            (runST)
import           Data.Binary                 (Binary)
import           Data.Vector.Binary          ()
import qualified Data.Vector.Unboxed         as Unboxed
import qualified GHC.Exts                    as Exts
import           GHC.Exts                    (IsList)
import Data.Vector.Generic hiding (convert)
import Data.Vector.Generic as X (
    -- Info
    length, null,
    -- Construction
    singleton, cons, snoc, concat,
    -- Indexing
    unsafeIndex, unsafeHead, unsafeLast, unsafeTail, unsafeInit,
    -- Slicing
    unsafeSlice, unsafeInit, unsafeTail, unsafeTake, unsafeDrop, splitAt,
    -- Partitioning
    span,
    -- Mapping
    map, imap, concatMap
 )
import qualified Data.Vector.Generic         as V
import qualified Data.Vector.Generic.Mutable as M


--------------------
-- === Vector === --
--------------------

-- === Construction === --

-- | O(1)
alloc :: Vector v a => Int -> v a
alloc i = runST $ unsafeFreeze =<< M.new i ; {-# INLINE alloc #-}

-- | O(n)
replicate' :: Vector v a => Int -> v a -> v a
replicate' i = concat . P.replicate i ; {-# INLINE replicate' #-}


-- -- === Deconstruction === --

-- | O(1)
index, (!) :: Vector v a => v a -> Int -> Maybe a
index = (V.!?) ; {-# INLINE index #-}
(!)   = index  ; {-# INLINE (!)   #-}
infixl 9 !

-- | O(1)
(!!) :: Vector v a => v a -> Int -> a
(!!) = unsafeIndex ; {-# INLINE (!!) #-}
infixl 9 !!

-- | O(1)
head, last :: Vector v a => v a -> Maybe a
head = fmap2 fst splitHead ; {-# INLINE head #-}
last = fmap2 fst splitLast ; {-# INLINE last #-}

-- | O(1)
init, tail :: Vector v a => v a -> Maybe (v a)
init = fmap2 snd splitLast ; {-# INLINE init #-}
tail = fmap2 snd splitHead ; {-# INLINE tail #-}

-- | O(1)
splitHead, splitLast :: Vector v a => v a -> Maybe (a, v a)
splitHead t = justIf (length t > 0) $ unsafeSplitHead t ; {-# INLINE splitHead #-}
splitLast t = justIf (length t > 0) $ unsafeSplitLast t ; {-# INLINE splitLast #-}

-- | O(1)
unsafeSplitHead, unsafeSplitLast :: Vector v a => v a -> (a, v a)
unsafeSplitHead t = (V.unsafeHead t, V.unsafeTail t) ; {-# INLINE unsafeSplitHead #-}
unsafeSplitLast t = (V.unsafeLast t, V.unsafeInit t) ; {-# INLINE unsafeSplitLast #-}

-- | O(s)
takeTill :: Vector v a => (a -> Bool) -> v a -> v a
takeTill f = \v -> maybe v (flip V.unsafeTake v) $ findIndex f v ; {-# INLINE takeTill #-}

-- | O(s)
takeWhile :: Vector v a => (a -> Bool) -> v a -> v a
takeWhile = takeTill . fmap not ; {-# INLINE takeWhile #-}

-- | O(s) Just like takeWhile, but uses streaming instead of slicing.
takeWhileStream :: Vector v a => (a -> Bool) -> v a -> v a
takeWhileStream = V.takeWhile ; {-# INLINE takeWhileStream #-}

-- | O(s)
dropWhileStream :: Vector v a => (a -> Bool) -> v a -> v a
dropWhileStream = V.dropWhile ; {-# INLINE dropWhileStream #-}

-- | O(n)
breakAll :: Vector v a => (a -> Bool) -> v a -> [v a]
breakAll f = go where
    go v = case findIndex f v of
        Nothing -> [v]
        Just i  -> unsafeTake i v : go (unsafeDrop (i+1) v)
{-# INLINE breakAll #-}

-- | O(n)
replaceUsing :: Vector v a => (a -> Bool) -> (a -> v a) -> v a -> v a
replaceUsing f rf = concat . go where
    go v = case findIndex f v of
        Nothing -> [v]
        Just i  -> unsafeTake i v : rf (unsafeIndex v i) : go (unsafeDrop (i+1) v)
{-# INLINE replaceUsing #-}

-- | O(n)
replace :: (Vector v a, Eq a) => a -> v a -> v a -> v a
replace a v = replaceUsing (a ==) (const v) ; {-# INLINE replace #-}



-- === Utils === --

commonPrefixes :: (Vector v a, Eq a) => v a -> v a -> Maybe (v a, v a, v a)
commonPrefixes v v' = go 0 where
    minlen = min (length v) (length v')
    go i | i < minlen && a == b = go (i+1)
         | i > 0                = Just (unsafeTake i v, unsafeDrop i v, unsafeDrop i v')
         | otherwise            = Nothing
         where a = unsafeIndex v  i
               b = unsafeIndex v' i


-- -- === Conversions === --


-- FIXME[WD]: remove when we hit next LTS stage
#if MIN_VERSION_vector(0,12,0)
-- instance present in vector
#else
instance Unboxed.Unbox a => Semigroup (Unboxed.Vector a) where (<>) = P.mappend
#endif

-- instance Convertible Char       Text       where convert = singleton              ; {-# INLINE convert #-}
instance (Vector Unboxed.Vector a, Convertible' Char a) => IsString         (Unboxed.Vector a) where fromString = convert              ; {-# INLINE fromString #-}
instance (Vector Unboxed.Vector a, Convertible' Char a) => Convertible Char (Unboxed.Vector a) where convert    = singleton . convert' ; {-# INLINE convert    #-}

-- | We cannot use automatic Convertible1 -> Convertible lifting, because converting unboxed Vectors constraints `a` to be unboxed as well.
instance {-# OVERLAPPABLE #-} (Vector Unboxed.Vector a, Convertible' t a) => Convertible [t] (Unboxed.Vector a) where convert = V.fromList . fmap convert' ; {-# INLINE convert #-}
instance                      (Vector Unboxed.Vector a)                   => Convertible [a] (Unboxed.Vector a) where convert = V.fromList                 ; {-# INLINE convert #-}
instance {-# OVERLAPPABLE #-} (Vector Unboxed.Vector a, Convertible' a t) => Convertible (Unboxed.Vector a) [t] where convert = fmap convert' . V.toList   ; {-# INLINE convert #-}
instance                      (Vector Unboxed.Vector a)                   => Convertible (Unboxed.Vector a) [a] where convert = V.toList                   ; {-# INLINE convert #-}

instance (Vector Unboxed.Vector a, Convertible' Char a) => Convertible UTF16.Text (Unboxed.Vector a) where convert = convertVia @[Char] ; {-# INLINE convert #-}
instance (Vector Unboxed.Vector a, Convertible' a Char) => Convertible (Unboxed.Vector a) UTF16.Text where convert = convertVia @[Char] ; {-# INLINE convert #-}