module Data.CircularSeq( CSeq
                       , cseq
                       , singleton
                       , fromNonEmpty
                       , fromList
                       , focus
                       , index, adjust
                       , item
                       , rotateL
                       , rotateR
                       , rotateNL, rotateNR
                       , rightElements
                       , leftElements
                       , asSeq
                       , reverseDirection
                       , allRotations
                       , findRotateTo
                       , rotateTo
                       , zipLWith, zipL
                       , zip3LWith
                       , insertOrd, insertOrdBy
                       , isShiftOf
                       ) where
import           Algorithms.StringSearch.KMP (isSubStringOf)
import           Control.DeepSeq
import           Control.Lens (lens, Lens', bimap)
import qualified Data.Foldable as F
import qualified Data.List as L
import qualified Data.List.NonEmpty as NonEmpty
import           Data.Maybe (listToMaybe, isJust)
import           Data.Semigroup.Foldable
import           Data.Sequence ((|>),(<|),ViewL(..),ViewR(..),Seq)
import qualified Data.Sequence as S
import qualified Data.Traversable as T
import           Data.Tuple (swap)
import           GHC.Generics (Generic)
import           Test.QuickCheck(Arbitrary(..))
import           Test.QuickCheck.Instances ()
data CSeq a = CSeq !(Seq a) !a !(Seq a)
  deriving (Generic)
                     
instance NFData a => NFData (CSeq a)
instance Eq a => Eq (CSeq a) where
  a == b = asSeq a == asSeq b
instance Show a => Show (CSeq a) where
  showsPrec d s = showParen (d > app_prec) $
                    showString (("CSeq " <>) . show . F.toList . rightElements $ s)
    where app_prec = 10
instance T.Traversable CSeq where
  traverse f (CSeq l x r) = (\x' r' l' -> CSeq l' x' r')
                         <$> f x <*> traverse f r <*> traverse f l
instance Foldable1 CSeq
instance F.Foldable CSeq where
  foldMap = T.foldMapDefault
  length (CSeq l _ r) = 1 + S.length l + S.length r
instance Functor CSeq where
  fmap = T.fmapDefault
instance Arbitrary a => Arbitrary (CSeq a) where
  arbitrary = CSeq <$> arbitrary <*> arbitrary <*> arbitrary
singleton   :: a -> CSeq a
singleton x = CSeq S.empty x S.empty
focus              :: CSeq a -> a
focus (CSeq _ x _) = x
index                   :: CSeq a -> Int -> a
index s@(CSeq l x r) i' = let i  = i' `mod` length s
                              rn = length r
                          in if i == 0 then x
                               else if i - 1 < rn then S.index r (i - 1)
                                                  else S.index l (i - rn - 1)
adjust                     :: (a -> a) -> Int -> CSeq a -> CSeq a
adjust f i' s@(CSeq l x r) = let i  = i' `mod` length s
                                 rn = length r
                             in if i == 0 then CSeq l (f x) r
                                else if i - 1 < rn
                                     then CSeq l                           x (S.adjust f (i - 1) r)
                                     else CSeq (S.adjust f (i - rn - 1) l) x r
item   :: Int -> Lens' (CSeq a) a
item i = lens (flip index i) (\s x -> adjust (const x) i s)
resplit   :: Seq a -> (Seq a, Seq a)
resplit s = swap $ S.splitAt (length s `div` 2) s
cseq                   :: Seq a -> a -> Seq a -> CSeq a
cseq l x r
    | ln > 1 + 2*rn    = withFocus x (r <> l)
    | ln < rn `div`  2 = withFocus x (r <> l)
    | otherwise        = CSeq l x r
  where
    rn = length r
    ln = length l
cseq'     :: Seq a -> Seq a -> CSeq a
cseq' l r = case S.viewl r of
              (x :< r') -> cseq l x r'
              EmptyL    -> let (x :< l') = S.viewl l in cseq l' x r
withFocus     :: a -> Seq a -> CSeq a
withFocus x s = let (l,r) = resplit s in CSeq l x r
rotateR                :: CSeq a -> CSeq a
rotateR s@(CSeq l x r) = case S.viewl r of
                           EmptyL    -> case S.viewl l of
                             EmptyL    -> s
                             (y :< l') -> cseq (S.singleton x) y l'
                           (y :< r') -> cseq (l |> x) y r'
rotateL                :: CSeq a -> CSeq a
rotateL s@(CSeq l x r) = case S.viewr l of
                           EmptyR    -> case S.viewr r of
                             EmptyR     -> s
                             (r' :> y)  -> cseq r' y (S.singleton x)
                           (l' :> y) -> cseq l' y (x <| r)
asSeq :: CSeq a -> Seq a
asSeq = rightElements
rightElements              :: CSeq a -> Seq a
rightElements (CSeq l x r) = x <| r <> l
leftElements              :: CSeq a -> Seq a
leftElements (CSeq l x r) = x <| S.reverse l <> S.reverse r
fromNonEmpty                    :: NonEmpty.NonEmpty a -> CSeq a
fromNonEmpty (x NonEmpty.:| xs) = withFocus x $ S.fromList xs
fromList        :: [a] -> CSeq a
fromList (x:xs) = withFocus x $ S.fromList xs
fromList []     = error "fromList: Empty list"
rotateNR   :: Int -> CSeq a -> CSeq a
rotateNR i = uncurry cseq' . S.splitAt i . rightElements
rotateNL     :: Int -> CSeq a -> CSeq a
rotateNL i s = let (x :< xs) = S.viewl $ rightElements s
                   (l',r)    = S.splitAt (length s - i) $ xs |> x
               in case S.viewr l' of
                    l :> y   -> cseq l y r
                    S.EmptyR -> let (y :< r') = S.viewl r in cseq l' y r'
reverseDirection              :: CSeq a -> CSeq a
reverseDirection (CSeq l x r) = CSeq (S.reverse r) x (S.reverse l)
findRotateTo   :: (a -> Bool) -> CSeq a -> Maybe (CSeq a)
findRotateTo p = listToMaybe . filter (p . focus) . allRotations'
rotateTo   :: Eq a => a -> CSeq a -> Maybe (CSeq a)
rotateTo x = findRotateTo (== x)
allRotations :: CSeq a -> CSeq (CSeq a)
allRotations = fromList . allRotations'
allRotations'   :: CSeq a -> [CSeq a]
allRotations' s = take (length s) . iterate rotateR $ s
zipLWith         :: (a -> b -> c) -> CSeq a -> CSeq b -> CSeq c
zipLWith f as bs = fromList $ zipWith f (F.toList as) (F.toList bs)
zipL :: CSeq a -> CSeq b -> CSeq (a, b)
zipL = zipLWith (,)
zip3LWith            :: (a -> b -> c -> d) -> CSeq a -> CSeq b -> CSeq c -> CSeq d
zip3LWith f as bs cs = fromList $ zipWith3 f (F.toList as) (F.toList bs) (F.toList cs)
insertOrd :: Ord a => a -> CSeq a -> CSeq a
insertOrd = insertOrdBy compare
insertOrdBy       :: (a -> a -> Ordering) -> a -> CSeq a -> CSeq a
insertOrdBy cmp x = fromList . insertOrdBy' cmp x . F.toList . rightElements
insertOrdBy'         :: (a -> a -> Ordering) -> a -> [a] -> [a]
insertOrdBy' cmp x xs = case (rest, x `cmp` head rest) of
    ([],  _)   -> L.insertBy cmp x pref
    (z:zs, GT) -> (z : L.insertBy cmp x zs) ++ pref
    (_:_,  EQ) -> (x : xs) 
    (_:_,  LT) -> rest ++ L.insertBy cmp x pref
  where
    
    (pref,rest) = splitIncr cmp xs
splitIncr              :: (a -> a -> Ordering) -> [a] -> ([a],[a])
splitIncr _   []       = ([],[])
splitIncr cmp xs@(x:_) = swap . bimap (map snd) (map snd)
                      . L.break (\(a,b) -> (a `cmp` b) == GT) $ zip (x:xs) xs
isShiftOf         :: Eq a => CSeq a -> CSeq a -> Bool
xs `isShiftOf` ys = let twice zs    = let zs' = leftElements zs in zs' <> zs'
                        once        = leftElements
                        check as bs = isJust $ once as `isSubStringOf` twice bs
                    in length xs == length ys && check xs ys