{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}

module Data.FocusList
  (
    -- * FocusList
    FocusList(FocusList, focusListFocus, focusList)
    -- ** Conversions
  , fromListFL
  , fromFoldableFL
  , toSeqFL
    -- ** Query
  , lengthFL
  , isEmptyFL
  , getFocusItemFL
  , lookupFL
  , indexOfFL
  , findFL
    -- *** Query 'Focus'
  , hasFocusFL
  , getFocusFL
    -- ** Manipulate
  , prependFL
  , appendFL
  , appendSetFocusFL
  , insertFL
  , removeFL
  , deleteFL
  , moveFromToFL
  , intersperseFL
  , reverseFL
    -- *** Manipulate 'Focus'
  , setFocusFL
  , updateFocusFL
    -- ** Sort
  , sortByFL
    -- ** Construction
  , emptyFL
  , singletonFL
    -- ** Unsafe Functions
  , unsafeFromListFL
  , unsafeGetFocusFL
  , unsafeGetFocusItemFL
    -- ** Invariants
  , invariantFL
    -- ** Testing
  , genValidFL
    -- ** Optics
    -- | These optics allow you to get/set the internal state of a 'FocusList'.
    -- You should make sure not to directly set the internal state of a
    -- 'FocusList' unless you are sure that the invariants for the 'FocusList'
    -- are protected.  See 'invariantFL'.
  , lensFocusListFocus
  , lensFocusList
    -- * Focus
  , Focus(Focus, NoFocus)
    , hasFocus
    , getFocus
    , maybeToFocus
    , foldFocus
    -- ** Optics
  , _Focus
  , _NoFocus
    -- ** Unsafe Functions
  , unsafeGetFocus
  ) where

import Prelude hiding (reverse)

import Control.Lens (Prism', (^.), (.~), (-~), makeLensesFor, prism')
import Data.Foldable (toList)
import Data.Function ((&))
import Data.MonoTraversable
  (Element, GrowingAppend, MonoFoldable, MonoFunctor, MonoTraversable, olength)
import Data.Semigroup ((<>))
import qualified Data.Sequence as Sequence
import Data.Sequence
  (Seq((:<|), Empty), (<|), deleteAt, elemIndexL, insertAt, singleton)
import Data.Sequences
  (Index, SemiSequence, cons, find, intersperse, reverse, snoc, sortBy)
import GHC.Exts (fromList)
import GHC.Generics (Generic)
import Test.QuickCheck
  ( Arbitrary, Arbitrary1, CoArbitrary, Gen, arbitrary, arbitrary1, choose
  , frequency, liftArbitrary
  )
import Text.Show (Show(showsPrec), ShowS, showParen, showString)

-- $setup
-- >>> :set -XFlexibleContexts
-- >>> :set -XScopedTypeVariables
-- >>> import Data.Maybe (isJust)

-- | A 'Focus' for the 'FocusList'.
--
-- The 'Focus' is either 'NoFocus' (if the 'Focuslist' is empty), or 'Focus'
-- 'Int' to represent focusing on a specific element of the 'FocusList'.
data Focus = Focus {-# UNPACK #-} !Int | NoFocus deriving (Eq, Generic, Read, Show)

-- | 'NoFocus' is always less than 'Focus'.
--
-- prop> NoFocus < Focus a
--
-- The ordering of 'Focus' depends on the ordering of the integer contained
-- inside.
--
-- prop> (a < b) ==> (Focus a < Focus b)
instance Ord Focus where
  compare :: Focus -> Focus -> Ordering
  compare NoFocus NoFocus = EQ
  compare NoFocus (Focus _) = LT
  compare (Focus _) NoFocus = GT
  compare (Focus a) (Focus b) = compare a b

instance CoArbitrary Focus

instance Arbitrary Focus where
  arbitrary = frequency [(1, pure NoFocus), (3, fmap Focus arbitrary)]

-- | A fold function for 'Focus'.
--
-- This is similar to 'maybe' for 'Maybe'.
--
-- >>> foldFocus "empty" (\i -> "focus at " <> show i) (Focus 3)
-- "focus at 3"
--
-- >>> foldFocus Nothing Just NoFocus
-- Nothing
--
-- prop> foldFocus NoFocus Focus focus == focus
foldFocus :: b -> (Int -> b) -> Focus -> b
foldFocus b _ NoFocus = b
foldFocus _ f (Focus i) = f i

-- | A 'Prism'' for focusing on the 'Focus' constructor in a 'Focus' data type.
_Focus :: Prism' Focus Int
_Focus = prism' Focus (foldFocus Nothing Just)

-- | A 'Prism'' for focusing on the 'NoFocus' constructor in a 'Focus' data type.
_NoFocus :: Prism' Focus ()
_NoFocus = prism' (const NoFocus) (foldFocus (Just ()) (const Nothing))

-- | Returns 'True' if a 'Focus' exists, and 'False' if not.
--
-- >>> hasFocus (Focus 0)
-- True
--
-- >>> hasFocus NoFocus
-- False
--
-- /complexity/: @O(1)@
hasFocus :: Focus -> Bool
hasFocus NoFocus = False
hasFocus (Focus _) = True

-- | Get the focus index from a 'Focus'.
--
-- >>> getFocus (Focus 3)
-- Just 3
--
-- >>> getFocus NoFocus
-- Nothing
--
-- /complexity/: @O(1)@
getFocus :: Focus -> Maybe Int
getFocus NoFocus = Nothing
getFocus (Focus i) = Just i

-- | Convert a 'Maybe' 'Int' to a 'Focus'.
--
-- >>> maybeToFocus (Just 100)
-- Focus 100
--
-- >>> maybeToFocus Nothing
-- NoFocus
--
-- 'maybeToFocus' and 'getFocus' witness an isomorphism.
--
-- prop> focus == maybeToFocus (getFocus focus)
--
-- prop> maybeInt == getFocus (maybeToFocus maybeInt)
--
-- /complexity/: @O(1)@
maybeToFocus :: Maybe Int -> Focus
maybeToFocus Nothing = NoFocus
maybeToFocus (Just i) = Focus i

-- | Unsafely get the focus index from a 'Focus'.
--
-- Returns an error if 'NoFocus'.
--
-- >>> unsafeGetFocus (Focus 50)
-- 50
--
-- >>> unsafeGetFocus NoFocus
-- *** Exception: ...
-- ...
--
-- /complexity/: @O(1)@
unsafeGetFocus :: Focus -> Int
unsafeGetFocus NoFocus = error "unsafeGetFocus: NoFocus"
unsafeGetFocus (Focus i) = i

-- | A list with a given element having the 'Focus'.
--
-- 'FocusList' has some invariants that must be protected.  You should not use
-- the 'FocusList' constructor or the 'focusListFocus' or 'focusList'
-- accessors.
--
-- Implemented under the hood as a 'Seq'.
data FocusList a = FocusList
  { focusListFocus :: !Focus
  , focusList :: !(Seq a)
  } deriving (Eq, Functor, Generic)

$(makeLensesFor
    [ ("focusListFocus", "lensFocusListFocus")
    , ("focusList", "lensFocusList")
    ]
    ''FocusList
 )

instance Foldable FocusList where
  foldr f b (FocusList _ fls) = foldr f b fls

  length = lengthFL

instance Traversable FocusList where
  traverse :: Applicative f => (a -> f b) -> FocusList a -> f (FocusList b)
  traverse f (FocusList focus fls) = FocusList focus <$> traverse f fls

type instance Element (FocusList a) = a

instance MonoFunctor (FocusList a)

instance MonoFoldable (FocusList a) where
  olength = lengthFL

instance MonoTraversable (FocusList a)

instance GrowingAppend (FocusList a)

instance SemiSequence (FocusList a) where
  type Index (FocusList a) = Int

  intersperse = intersperseFL

  reverse = reverseFL

  find = findFL

  sortBy = sortByFL

  cons = prependFL

  snoc = appendFL

-- | Given a 'Gen' for @a@, generate a valid 'FocusList'.
genValidFL :: forall a. Gen a -> Gen (FocusList a)
genValidFL genA = do
  newFL <- genFL
  if invariantFL newFL
    then pure newFL
    else error "genValidFL generated an invalid FocusList!  This should never happen!"
  where
    genFL :: Gen (FocusList a)
    genFL = do
      arbList <- liftArbitrary genA
      case arbList of
        [] -> pure emptyFL
        (_:_) -> do
          let listLen = length arbList
          len <- choose (0, listLen - 1)
          pure $ unsafeFromListFL (Focus len) arbList

instance Arbitrary1 FocusList where
  liftArbitrary = genValidFL

instance Arbitrary a => Arbitrary (FocusList a) where
  arbitrary = arbitrary1

instance CoArbitrary a => CoArbitrary (FocusList a)

instance Show a => Show (FocusList a) where
  showsPrec :: Int -> FocusList a -> ShowS
  showsPrec d FocusList{..} =
    showParen (d > 10) $
      showString "FocusList " .
      showsPrec 11 focusListFocus .
      showString " " .
      showsPrec 11 (toList focusList)

-- | Get the underlying 'Seq' in a 'FocusList'.
--
-- /complexity/: @O(1)@
toSeqFL :: FocusList a -> Seq a
toSeqFL FocusList{focusList = fls} = fls

-- | Return the length of a 'FocusList'.
--
-- >>> let Just fl = fromListFL (Focus 2) ["hello", "bye", "parrot"]
-- >>> lengthFL fl
-- 3
--
-- /complexity/: @O(1)@
lengthFL :: FocusList a -> Int
lengthFL = length . focusList

-- | This is an invariant that the 'FocusList' must always protect.
--
-- The functions in this module should generally protect this invariant.  If
-- they do not, it is generally a bug.
--
-- The invariants are as follows:
--
-- - The 'Focus' in a 'FocusList' can never be negative.
--
-- - If there is a 'Focus', then it actually exists in
--   the 'FocusList'.
--
-- - There needs to be a 'Focus' if the length of the
--   'FocusList' is greater than 0.
--
-- /complexity/: @O(log n)@, where @n@ is the length of the 'FocusList'.
invariantFL :: FocusList a -> Bool
invariantFL fl =
  invariantFocusNotNeg &&
  invariantFocusInMap &&
  invariantFocusIfLenGT0
  where
    -- This makes sure that the 'Focus' in a 'FocusList' can never be negative.
    invariantFocusNotNeg :: Bool
    invariantFocusNotNeg =
      case fl ^. lensFocusListFocus of
        NoFocus -> True
        Focus i -> i >= 0

    -- | This makes sure that if there is a 'Focus', then it actually exists in
    -- the 'FocusList'.
    invariantFocusInMap :: Bool
    invariantFocusInMap =
      case fl ^. lensFocusListFocus of
        NoFocus -> length (fl ^. lensFocusList) == 0
        Focus i ->
          case Sequence.lookup i (fl ^. lensFocusList) of
            Nothing -> False
            Just _ -> True

    -- | This makes sure that there needs to be a 'Focus' if the length of the
    -- 'FocusList' is greater than 0.
    invariantFocusIfLenGT0 :: Bool
    invariantFocusIfLenGT0 =
      let len = lengthFL fl
          focus = fl ^. lensFocusListFocus
      in
      case focus of
        Focus _ -> len /= 0
        NoFocus -> len == 0

-- | Unsafely create a 'FocusList'.  This does not check that the focus
-- actually exists in the list.  This is an internal function and should
-- generally not be used.  It is only safe to use if you ALREADY know
-- the 'Focus' is within the list.
--
-- Instead, you should generally use 'fromListFL'.
--
-- The following is an example of using 'unsafeFromListFL' correctly.
--
-- >>> unsafeFromListFL (Focus 1) [0..2]
-- FocusList (Focus 1) [0,1,2]
--
-- >>> unsafeFromListFL NoFocus []
-- FocusList NoFocus []
--
-- 'unsafeFromListFL' can also be used uncorrectly.  The following is an
-- example of 'unsafeFromListFL' allowing you to create a 'FocusList' that does
-- not pass 'invariantFL'.
--
-- >>> unsafeFromListFL (Focus 100) [0..2]
-- FocusList (Focus 100) [0,1,2]
--
-- If 'fromListFL' returns a 'Just' 'FocusList', then 'unsafeFromListFL' should
-- return the same 'FocusList'.
--
-- /complexity/: @O(n)@ where @n@ is the length of the input list.
unsafeFromListFL :: Focus -> [a] -> FocusList a
unsafeFromListFL focus list =
  FocusList
    { focusListFocus = focus
    , focusList = fromList list
    }

-- | Safely create a 'FocusList' from a list.
--
-- >>> fromListFL (Focus 1) ["cat","dog","goat"]
-- Just (FocusList (Focus 1) ["cat","dog","goat"])
--
-- >>> fromListFL NoFocus []
-- Just (FocusList NoFocus [])
--
-- If the 'Focus' is out of range for the list, then 'Nothing' will be returned.
--
-- >>> fromListFL (Focus (-1)) ["cat","dog","goat"]
-- Nothing
--
-- >>> fromListFL (Focus 3) ["cat","dog","goat"]
-- Nothing
--
-- >>> fromListFL NoFocus ["cat","dog","goat"]
-- Nothing
--
-- /complexity/: @O(n)@ where @n@ is the length of the input list.
fromListFL :: Focus -> [a] -> Maybe (FocusList a)
fromListFL NoFocus [] = Just emptyFL
fromListFL _ [] = Nothing
fromListFL NoFocus (_:_) = Nothing
fromListFL (Focus i) list =
  let len = length list
  in
  if i < 0 || i >= len
    then Nothing
    else
      Just $
        FocusList
          { focusListFocus = Focus i
          , focusList = fromList list
          }

-- | Create a 'FocusList' from any 'Foldable' container.
--
-- This just calls 'toList' on the 'Foldable', and then passes the result to
-- 'fromListFL'.
--
-- prop> fromFoldableFL foc (foldable :: Data.Sequence.Seq Int) == fromListFL foc (toList foldable)
--
-- /complexity/: @O(n)@ where @n@ is the length of the 'Foldable'
fromFoldableFL :: Foldable f => Focus -> f a -> Maybe (FocusList a)
fromFoldableFL foc as = fromListFL foc (toList as)

-- | Create a 'FocusList' with a single element.
--
-- >>> singletonFL "hello"
-- FocusList (Focus 0) ["hello"]
--
-- /complexity/: @O(1)@
singletonFL :: a -> FocusList a
singletonFL a =
  FocusList
    { focusListFocus = Focus 0
    , focusList = singleton a
    }

-- | Create an empty 'FocusList' without a 'Focus'.
--
-- >>> emptyFL
-- FocusList NoFocus []
--
-- /complexity/: @O(1)@
emptyFL :: FocusList a
emptyFL =
  FocusList
    { focusListFocus = NoFocus
    , focusList = mempty
    }

-- | Return 'True' if the 'FocusList' is empty.
--
-- >>> isEmptyFL emptyFL
-- True
--
-- >>> isEmptyFL $ singletonFL "hello"
-- False
--
-- Any 'FocusList' with a 'Focus' should never be empty.
--
-- prop> hasFocusFL fl ==> not (isEmptyFL fl)
--
-- The opposite is also true.
--
-- /complexity/: @O(1)@
isEmptyFL :: FocusList a -> Bool
isEmptyFL fl = (lengthFL fl) == 0

-- | Append a value to the end of a 'FocusList'.
--
-- This can be thought of as a \"snoc\" operation.
--
-- >>> appendFL emptyFL "hello"
-- FocusList (Focus 0) ["hello"]
--
-- >>> appendFL (singletonFL "hello") "bye"
-- FocusList (Focus 0) ["hello","bye"]
--
-- Appending a value to an empty 'FocusList' is the same as using 'singletonFL'.
--
-- prop> appendFL emptyFL a == singletonFL a
--
-- /complexity/: @O(log n)@ where @n@ is the length of the 'FocusList'.
appendFL :: FocusList a -> a -> FocusList a
appendFL fl a =
  if isEmptyFL fl
    then singletonFL a
    else insertFL (length $ focusList fl) a fl

-- | A combination of 'appendFL' and 'setFocusFL'.
--
-- >>> let Just fl = fromListFL (Focus 1) ["hello", "bye", "tree"]
-- >>> appendSetFocusFL fl "pie"
-- FocusList (Focus 3) ["hello","bye","tree","pie"]
--
-- The 'Focus' will always be updated after calling 'appendSetFocusFL'.
--
-- prop> getFocusFL (appendSetFocusFL fl a) > getFocusFL fl
--
-- /complexity/: @O(log n)@ where @n@ is the length of the 'FocusList'.
appendSetFocusFL :: FocusList a -> a -> FocusList a
appendSetFocusFL fl a =
  let oldLen = length $ focusList fl
  in
  case setFocusFL oldLen (appendFL fl a) of
    Nothing -> error "Internal error with setting the focus.  This should never happen."
    Just newFL -> newFL

-- | Prepend a value to a 'FocusList'.
--
-- This can be thought of as a \"cons\" operation.
--
-- >>> prependFL "hello" emptyFL
-- FocusList (Focus 0) ["hello"]
--
-- The focus will be updated when prepending:
--
-- >>> prependFL "bye" (singletonFL "hello")
-- FocusList (Focus 1) ["bye","hello"]
--
-- Prepending to a 'FocusList' will always update the 'Focus':
--
-- prop> getFocusFL fl < getFocusFL (prependFL a fl)
--
-- /complexity/: @O(1)@
prependFL :: a -> FocusList a -> FocusList a
prependFL a fl@FocusList{ focusListFocus = focus, focusList = fls}  =
  case focus of
    NoFocus -> singletonFL a
    Focus i ->
      fl
        { focusListFocus = Focus (i+1)
        , focusList = a <| fls
        }

-- | Unsafely get the 'Focus' from a 'FocusList'.  If the 'Focus' is
-- 'NoFocus', this function returns 'error'.
--
-- This function is only safe if you already have knowledge that
-- the 'FocusList' has a 'Focus'.
--
-- Generally, 'getFocusFL' should be used instead of this function.
--
-- >>> let Just fl = fromListFL (Focus 1) [0..9]
-- >>> unsafeGetFocusFL fl
-- 1
--
-- >>> unsafeGetFocusFL emptyFL
-- *** Exception: ...
-- ...
--
-- /complexity/: @O(1)@
unsafeGetFocusFL :: FocusList a -> Int
unsafeGetFocusFL fl =
  let focus = fl ^. lensFocusListFocus
  in
  case focus of
    NoFocus -> error "unsafeGetFocusFL: the focus list doesn't have a focus"
    Focus i -> i

-- | Return 'True' if the 'Focus' in a 'FocusList' exists.
--
-- Return 'False' if the 'Focus' in a 'FocusList' is 'NoFocus'.
--
-- >>> hasFocusFL $ singletonFL "hello"
-- True
--
-- >>> hasFocusFL emptyFL
-- False
--
-- /complexity/: @O(1)@
hasFocusFL :: FocusList a -> Bool
hasFocusFL = hasFocus . getFocusFL

-- | Get the 'Focus' from a 'FocusList'.
--
-- >>> getFocusFL $ singletonFL "hello"
-- Focus 0
--
-- >>> let Just fl = fromListFL (Focus 3) [0..9]
-- >>> getFocusFL fl
-- Focus 3
--
-- >>> getFocusFL emptyFL
-- NoFocus
--
-- /complexity/: @O(1)@
getFocusFL :: FocusList a -> Focus
getFocusFL FocusList{focusListFocus} = focusListFocus

-- | Unsafely get the value of the 'Focus' from a 'FocusList'.  If the 'Focus' is
-- 'NoFocus', this function returns 'error'.
--
-- This function is only safe if you already have knowledge that the 'FocusList'
-- has a 'Focus'.
--
-- Generally, 'getFocusItemFL' should be used instead of this function.
--
-- >>> let Just fl = fromListFL (Focus 0) ['a'..'c']
-- >>> unsafeGetFocusItemFL fl
-- 'a'
--
-- >>> unsafeGetFocusFL emptyFL
-- *** Exception: ...
-- ...
--
-- /complexity/: @O(log(min(i, n - i)))@ where @i@ is the 'Focus', and @n@
-- is the length of the 'FocusList'.
unsafeGetFocusItemFL :: FocusList a -> a
unsafeGetFocusItemFL fl =
  let focus = fl ^. lensFocusListFocus
  in
  case focus of
    NoFocus -> error "unsafeGetFocusItemFL: the focus list doesn't have a focus"
    Focus i ->
      let fls = fl ^. lensFocusList
      in
      case Sequence.lookup i fls of
        Nothing ->
          error $
            "unsafeGetFocusItemFL: internal error, i (" <>
            show i <>
            ") doesnt exist in sequence"
        Just a -> a

-- | Get the item the 'FocusList' is focusing on.  Return 'Nothing' if the
-- 'FocusList' is empty.
--
-- >>> let Just fl = fromListFL (Focus 0) ['a'..'c']
-- >>> getFocusItemFL fl
-- Just 'a'
--
-- >>> getFocusItemFL emptyFL
-- Nothing
--
-- This will always return 'Just' if there is a 'Focus'.
--
-- prop> hasFocusFL fl ==> isJust (getFocusItemFL fl)
--
-- /complexity/: @O(log(min(i, n - i)))@ where @i@ is the 'Focus', and @n@
-- is the length of the 'FocusList'.
getFocusItemFL :: FocusList a -> Maybe a
getFocusItemFL fl =
  let focus = fl ^. lensFocusListFocus
  in
  case focus of
    NoFocus -> Nothing
    Focus i ->
      let fls = fl ^. lensFocusList
      in
      case Sequence.lookup i fls of
        Nothing ->
          error $
            "getFocusItemFL: internal error, i (" <>
            show i <>
            ") doesnt exist in sequence"
        Just a -> Just a

-- | Lookup the element at the specified index, counting from 0.
--
-- >>> let Just fl = fromListFL (Focus 0) ['a'..'c']
-- >>> lookupFL 0 fl
-- Just 'a'
--
-- Returns 'Nothing' if the index is out of bounds.
--
-- >>> let Just fl = fromListFL (Focus 0) ['a'..'c']
-- >>> lookupFL 100 fl
-- Nothing
-- >>> lookupFL (-1) fl
-- Nothing
--
-- Always returns 'Nothing' if the 'FocusList' is empty.
--
-- prop> lookupFL i emptyFL == Nothing
--
-- /complexity/: @O(log(min(i, n - i)))@ where @i@ is the index you want to look up, and @n@
-- is the length of the 'FocusList'.
lookupFL
  :: Int  -- ^ Index to lookup.
  -> FocusList a
  -> Maybe a
lookupFL i fl = Sequence.lookup i (fl ^. lensFocusList)

-- | Insert a new value into the 'FocusList'.  The 'Focus' of the list is
-- changed appropriately.
--
-- Inserting an element into an empty 'FocusList' will set the 'Focus' on
-- that element.
--
-- >>> insertFL 0 "hello" emptyFL
-- FocusList (Focus 0) ["hello"]
--
-- The 'Focus' will not be changed if you insert a new element after the
-- current 'Focus'.
--
-- >>> insertFL 1 "hello" (singletonFL "bye")
-- FocusList (Focus 0) ["bye","hello"]
--
-- The 'Focus' will be bumped up by one if you insert a new element before
-- the current 'Focus'.
--
-- >>> insertFL 0 "hello" (singletonFL "bye")
-- FocusList (Focus 1) ["hello","bye"]
--
-- Behaves like @Data.Sequence.'Data.Sequence.insertAt'@. If the index is out of bounds, it will be
-- inserted at the nearest available index
--
-- >>> insertFL 100 "hello" emptyFL
-- FocusList (Focus 0) ["hello"]
--
-- >>> insertFL 100 "bye" (singletonFL "hello")
-- FocusList (Focus 0) ["hello","bye"]
--
-- >>> insertFL (-1) "bye" (singletonFL "hello")
-- FocusList (Focus 1) ["bye","hello"]
--
-- /complexity/: @O(log(min(i, n - i)))@ where @i@ is the index you want to insert at, and @n@
-- is the length of the 'FocusList'.
insertFL
  :: Int  -- ^ The index at which to insert the new element.
  -> a    -- ^ The new element.
  -> FocusList a
  -> FocusList a
insertFL _ a FocusList {focusListFocus = NoFocus} = singletonFL a
insertFL i a fl@FocusList{focusListFocus = Focus focus, focusList = fls} =
  if i > focus
    then
      fl
        { focusList = insertAt i a fls
        }
    else
      fl
        { focusList = insertAt i a fls
        , focusListFocus = Focus $ focus + 1
        }

-- | Remove an element from a 'FocusList'.
--
-- If the element to remove is not the 'Focus', then update the 'Focus'
-- accordingly.
--
-- For example, if the 'Focus' is on index 1, and we have removed index 2, then
-- the focus is not affected, so it is not changed.
--
-- >>> let focusList = unsafeFromListFL (Focus 1) ["cat","goat","dog","hello"]
-- >>> removeFL 2 focusList
-- Just (FocusList (Focus 1) ["cat","goat","hello"])
--
-- If the 'Focus' is on index 2 and we have removed index 1, then the 'Focus'
-- will be moved back one element to set to index 1.
--
-- >>> let focusList = unsafeFromListFL (Focus 2) ["cat","goat","dog","hello"]
-- >>> removeFL 1 focusList
-- Just (FocusList (Focus 1) ["cat","dog","hello"])
--
-- If we remove the 'Focus', then the next item is set to have the 'Focus'.
--
-- >>> let focusList = unsafeFromListFL (Focus 0) ["cat","goat","dog","hello"]
-- >>> removeFL 0 focusList
-- Just (FocusList (Focus 0) ["goat","dog","hello"])
--
-- If the element to remove is the only element in the list, then the 'Focus'
-- will be set to 'NoFocus'.
--
-- >>> let focusList = unsafeFromListFL (Focus 0) ["hello"]
-- >>> removeFL 0 focusList
-- Just (FocusList NoFocus [])
--
-- If the 'Int' for the index to remove is either less than 0 or greater then
-- the length of the list, then 'Nothing' is returned.
--
-- >>> let focusList = unsafeFromListFL (Focus 0) ["hello"]
-- >>> removeFL (-1) focusList
-- Nothing
--
-- >>> let focusList = unsafeFromListFL (Focus 1) ["hello","bye","cat"]
-- >>> removeFL 3 focusList
-- Nothing
--
-- If the 'FocusList' passed in is 'Empty', then 'Nothing' is returned.
--
-- >>> removeFL 0 emptyFL
-- Nothing
--
-- /complexity/: @O(log(min(i, n - i)))@ where @i@ is index of the element to remove, and @n@
-- is the length of the 'FocusList'.
removeFL
  :: Int          -- ^ Index of the element to remove from the 'FocusList'.
  -> FocusList a  -- ^ The 'FocusList' to remove an element from.
  -> Maybe (FocusList a)
removeFL i fl@FocusList{focusList = fls}
  | i < 0 || i >= (lengthFL fl) || isEmptyFL fl =
    -- Return Nothing if the removal position is out of bounds.
    Nothing
  | lengthFL fl == 1 =
    -- Return an empty focus list if there is currently only one element
    Just emptyFL
  | otherwise =
    let newFL = fl {focusList = deleteAt i fls}
        focus = unsafeGetFocusFL fl
    in
    if focus >= i && focus /= 0
      then Just $ newFL & lensFocusListFocus . _Focus -~ 1
      else Just newFL

-- | Find the index of the first element in the 'FocusList'.
--
-- >>> let Just fl = fromListFL (Focus 1) ["hello", "bye", "tree"]
-- >>> indexOfFL "hello" fl
-- Just 0
--
-- If more than one element exists, then return the index of the first one.
--
-- >>> let Just fl = fromListFL (Focus 1) ["dog", "cat", "cat"]
-- >>> indexOfFL "cat" fl
-- Just 1
--
-- If the element doesn't exist, then return 'Nothing'
--
-- >>> let Just fl = fromListFL (Focus 1) ["foo", "bar", "baz"]
-- >>> indexOfFL "hogehoge" fl
-- Nothing
indexOfFL :: Eq a => a -> FocusList a -> Maybe Int
indexOfFL a FocusList{focusList = fls} =
  elemIndexL a fls

-- | Delete an element from a 'FocusList'.
--
-- >>> let Just fl = fromListFL (Focus 0) ["hello", "bye", "tree"]
-- >>> deleteFL "bye" fl
-- FocusList (Focus 0) ["hello","tree"]
--
-- The focus will be updated if an item before it is deleted.
--
-- >>> let Just fl = fromListFL (Focus 1) ["hello", "bye", "tree"]
-- >>> deleteFL "hello" fl
-- FocusList (Focus 0) ["bye","tree"]
--
-- If there are multiple matching elements in the 'FocusList', remove them all.
--
-- >>> let Just fl = fromListFL (Focus 0) ["hello", "bye", "bye"]
-- >>> deleteFL "bye" fl
-- FocusList (Focus 0) ["hello"]
--
-- If there are no matching elements, return the original 'FocusList'.
--
-- >>> let Just fl = fromListFL (Focus 2) ["hello", "good", "bye"]
-- >>> deleteFL "frog" fl
-- FocusList (Focus 2) ["hello","good","bye"]
deleteFL
  :: forall a.
     (Eq a)
  => a
  -> FocusList a
  -> FocusList a
deleteFL item = go
  where
    go :: FocusList a -> FocusList a
    go fl =
      let maybeIndex = indexOfFL item fl
      in
      case maybeIndex of
        Nothing -> fl
        Just i ->
          let maybeNewFL = removeFL i fl
          in
          case maybeNewFL of
            Nothing -> fl
            Just newFL -> go newFL

-- | Set the 'Focus' for a 'FocusList'.
--
-- This is just like 'updateFocusFL', but doesn't return the newly focused item.
--
-- prop> setFocusFL i fl == fmap snd (updateFocusFL i fl)
--
-- /complexity/: @O(1)@
setFocusFL :: Int -> FocusList a -> Maybe (FocusList a)
setFocusFL i fl
  -- Can't set a 'Focus' for an empty 'FocusList'.
  | isEmptyFL fl = Nothing
  | otherwise =
    let len = lengthFL fl
    in
    if i < 0 || i >= len
      then Nothing
      else Just $ fl & lensFocusListFocus . _Focus .~ i

-- | Update the 'Focus' for a 'FocusList' and get the new focused element.
--
-- >>> updateFocusFL 1 =<< fromListFL (Focus 2) ["hello","bye","dog","cat"]
-- Just ("bye",FocusList (Focus 1) ["hello","bye","dog","cat"])
--
-- If the 'FocusList' is empty, then return 'Nothing'.
--
-- >>> updateFocusFL 1 emptyFL
-- Nothing
--
-- If the new focus is less than 0, or greater than or equal to the length of
-- the 'FocusList', then return 'Nothing'.
--
-- >>> updateFocusFL (-1) =<< fromListFL (Focus 2) ["hello","bye","dog","cat"]
-- Nothing
--
-- >>> updateFocusFL 4 =<< fromListFL (Focus 2) ["hello","bye","dog","cat"]
-- Nothing
--
-- /complexity/: @O(log(min(i, n - i)))@ where @i@ is the new index to put the 'Focus' on,
-- and @n@ -- is the length of the 'FocusList'.
updateFocusFL
  :: Int  -- ^ The new index to put the 'Focus' on.
  -> FocusList a
  -> Maybe (a, FocusList a)
  -- ^ A tuple of the new element that gets the 'Focus', and the new
  -- 'FocusList'.
updateFocusFL i fl
  | isEmptyFL fl = Nothing
  | otherwise =
    let len = lengthFL fl
    in
    if i < 0 || i >= len
      then Nothing
      else
        let newFL = fl & lensFocusListFocus . _Focus .~ i
        in Just (unsafeGetFocusItemFL newFL, newFL)

-- | Find a value in a 'FocusList'.  Similar to @Data.List.'Data.List.find'@.
--
-- >>> let Just fl = fromListFL (Focus 1) ["hello", "bye", "tree"]
-- >>> findFL (\a -> a == "hello") fl
-- Just "hello"
--
-- This will only find the first value.
--
-- >>> let Just fl = fromListFL (Focus 0) ["hello", "bye", "bye"]
-- >>> findFL (\a -> a == "bye") fl
-- Just "bye"
--
-- If no values match the comparison, this will return 'Nothing'.
--
-- >>> let Just fl = fromListFL (Focus 1) ["hello", "bye", "parrot"]
-- >>> findFL (\a -> a == "ball") fl
-- Nothing
--
-- /complexity/: @O(n)@ where @n@ is the length of the 'FocusList'.
findFL :: (a -> Bool) -> FocusList a -> Maybe (a)
findFL p fl =
  let fls = fl ^. lensFocusList
  in find p fls

-- | Move an existing item in a 'FocusList' to a new index.
--
-- The 'Focus' gets updated appropriately when moving items.
--
-- >>> let Just fl = fromListFL (Focus 1) ["hello", "bye", "parrot"]
-- >>> moveFromToFL 0 1 fl
-- Just (FocusList (Focus 0) ["bye","hello","parrot"])
--
-- The 'Focus' may not get updated if it is not involved.
--
-- >>> let Just fl = fromListFL (Focus 0) ["hello", "bye", "parrot"]
-- >>> moveFromToFL 1 2 fl
-- Just (FocusList (Focus 0) ["hello","parrot","bye"])
--
-- If the element with the 'Focus' is moved, then the 'Focus' will be updated
-- appropriately.
--
-- >>> let Just fl = fromListFL (Focus 2) ["hello", "bye", "parrot"]
-- >>> moveFromToFL 2 0 fl
-- Just (FocusList (Focus 0) ["parrot","hello","bye"])
--
-- If the index of the item to move is out bounds, then 'Nothing' will be returned.
--
-- >>> let Just fl = fromListFL (Focus 2) ["hello", "bye", "parrot"]
-- >>> moveFromToFL 3 0 fl
-- Nothing
--
-- If the new index is out of bounds, then 'Nothing' wil be returned.
--
-- >>> let Just fl = fromListFL (Focus 2) ["hello", "bye", "parrot"]
-- >>> moveFromToFL 1 (-1) fl
-- Nothing
--
-- /complexity/: @O(log n)@ where @n@ is the length of the 'FocusList'.
moveFromToFL
  :: Show a
  => Int  -- ^ Index of the item to move.
  -> Int  -- ^ New index for the item.
  -> FocusList a
  -> Maybe (FocusList a)
moveFromToFL oldPos newPos fl
  | oldPos < 0 || oldPos >= length fl = Nothing
  | newPos < 0 || newPos >= length fl = Nothing
  | otherwise =
    let oldFocus = fl ^. lensFocusListFocus
    in
    case lookupFL oldPos fl of
      Nothing -> error "moveFromToFL should have been able to lookup the item"
      Just item ->
        case removeFL oldPos fl of
          Nothing -> error "moveFromToFL should have been able to remove old position"
          Just flAfterRemove ->
            let flAfterInsert = insertFL newPos item flAfterRemove in
                if Focus oldPos == oldFocus
                  then
                    case setFocusFL newPos flAfterInsert of
                      Nothing -> error "moveFromToFL should have been able to reset the focus"
                      Just flWithUpdatedFocus -> Just flWithUpdatedFocus
                  else Just flAfterInsert

-- | Intersperse a new element between existing elements in the 'FocusList'.
--
-- >>> let Just fl = fromListFL (Focus 0) ["hello", "bye", "cat"]
-- >>> intersperseFL "foo" fl
-- FocusList (Focus 0) ["hello","foo","bye","foo","cat"]
--
-- The 'Focus' is updated accordingly.
--
-- >>> let Just fl = fromListFL (Focus 2) ["hello", "bye", "cat", "goat"]
-- >>> intersperseFL "foo" fl
-- FocusList (Focus 4) ["hello","foo","bye","foo","cat","foo","goat"]
--
-- The item with the 'Focus' should never change after calling 'intersperseFL'.
--
-- prop> getFocusItemFL (fl :: FocusList Int) == getFocusItemFL (intersperseFL a fl)
--
-- 'intersperseFL' should not have any effect on a 'FocusList' with less than
-- two items.
--
-- prop> emptyFL == intersperseFL x emptyFL
-- prop> singletonFL a == intersperseFL x (singletonFL a)
--
-- /complexity/: @O(n)@ where @n@ is the length of the 'FocusList'.
intersperseFL :: a -> FocusList a -> FocusList a
intersperseFL _ FocusList{focusListFocus = NoFocus} = emptyFL
intersperseFL a FocusList{focusList = fls, focusListFocus = Focus foc} =
  let newFLS = intersperse a fls
  in
  FocusList
    { focusList = newFLS
    , focusListFocus = Focus (foc * 2)
    }

-- | Reverse a 'FocusList'.  The 'Focus' is updated accordingly.
--
-- >>> let Just fl = fromListFL (Focus 0) ["hello", "bye", "cat"]
-- >>> reverseFL fl
-- FocusList (Focus 2) ["cat","bye","hello"]
--
-- >>> let Just fl = fromListFL (Focus 2) ["hello", "bye", "cat", "goat"]
-- >>> reverseFL fl
-- FocusList (Focus 1) ["goat","cat","bye","hello"]
--
-- The item with the 'Focus' should never change after calling 'intersperseFL'.
--
-- prop> getFocusItemFL (fl :: FocusList Int) == getFocusItemFL (reverseFL fl)
--
-- Reversing twice should not change anything.
--
-- prop> (fl :: FocusList Int) == reverseFL (reverseFL fl)
--
-- Reversing empty lists and single lists should not do anything.
--
-- prop> emptyFL == reverseFL emptyFL
-- prop> singletonFL a == reverseFL (singletonFL a)
--
-- /complexity/: @O(n)@ where @n@ is the length of the 'FocusList'.
reverseFL :: FocusList a -> FocusList a
reverseFL FocusList{focusListFocus = NoFocus} = emptyFL
reverseFL FocusList{focusList = fls, focusListFocus = Focus foc} =
  let newFLS = reverse fls
      newFLSLen = length newFLS
  in
  FocusList
    { focusList = newFLS
    , focusListFocus = Focus (newFLSLen - foc - 1)
    }

-- | Sort a 'FocusList'.
--
-- The 'Focus' will stay with the element that has the 'Focus'.
--
-- >>> let Just fl = fromListFL (Focus 2) ["b", "c", "a"]
-- >>> sortByFL compare fl
-- FocusList (Focus 0) ["a","b","c"]
--
-- Nothing will happen if you try to sort an empty 'FocusList', or a
-- 'FocusList' with only one element.
--
-- prop> emptyFL == sortByFL compare emptyFL
-- prop> singletonFL a == sortByFL compare (singletonFL a)
--
-- The element with the 'Focus' should be the same before and after sorting.
--
-- prop> getFocusItemFL (fl :: FocusList Int) == getFocusItemFL (sortByFL compare fl)
--
-- Sorting a 'FocusList' and getting the underlying 'Seq' should be the same as
-- getting the underlying 'Seq' and then sorting it.
--
-- prop> toSeqFL (sortByFL compare (fl :: FocusList Int)) == sortBy compare (toSeqFL fl)
--
-- __WARNING__: The computational complexity for this is very bad. It should be
-- able to be done in @O(n * log n)@, but the current implementation is
-- @O(n^2)@ (or worse), where @n@ is the length of the 'FocusList'.  This
-- function could be implemented the same way
-- @Data.Sequence.'Data.Sequence.sortBy'@ is implemented.  However, a small
-- change needs to be added to that function to keep track of the 'Focus' in
-- the 'FocusList' and make sure it gets updated properly.  If you're
-- interested in fixing this, please send a PR.
sortByFL
  :: forall a
   . (a -> a -> Ordering) -- ^ The function to use to compare elements.
  -> FocusList a
  -> FocusList a
sortByFL _ FocusList{focusListFocus = NoFocus} = emptyFL
sortByFL cmpFunc FocusList{focusList = fls, focusListFocus = Focus foc} =
  let (res, maybeNewFoc) = go fls (Just foc)
  in
  case maybeNewFoc of
    Nothing -> error "sortByFL: A sequence should never lose its focus."
    Just newFoc ->
      FocusList
        { focusList = res
        , focusListFocus = Focus newFoc
        }
  where
    go
      :: Seq a -- ^ The sequence that needs to be sorted.
      -> Maybe Int
         -- ^ Whether or not we are tracking a 'Focus' that needs to be updated.
      -> (Seq a, Maybe Int)
    -- Trying to sort an empty sequence with a 'Focus'.  This should never happen.
    go Empty (Just _) =
      error "sortByFL: go: this should never happen, sort empty with focus."
    -- Trying to sort an empty sequence.
    go Empty Nothing = (Empty, Nothing)
    -- Trying to sort a non-empty sequence with no focus.
    go (a :<| as) Nothing =
      let res = go as Nothing
      in
      case res of
        (_, Just _) -> error "sortByFL: go: this should never happen, no focus case"
        (Empty, Nothing) -> (a :<| Empty, Nothing)
        (b :<| bs, Nothing) ->
          case cmpFunc a b of
            LT -> (a :<| b :<| bs, Nothing)
            EQ -> (a :<| b :<| bs, Nothing)
            GT -> (b :<| fst (go (a :<| bs) Nothing), Nothing)
    -- Trying to sort a non-empty sequence with the top element having the focus.
    go (a :<| as) (Just 0) =
      let res = go as Nothing
      in
      case res of
        (_, Just _) -> error "sortByFL: go: this should never happen, top elem has focus case"
        (Empty, Nothing) -> (a :<| Empty, Just 0)
        (b :<| bs, Nothing) ->
          case cmpFunc a b of
            LT -> (a :<| b :<| bs, Just 0)
            EQ -> (a :<| b :<| bs, Just 0)
            GT ->
              let (newSeq, maybeNewFoc) = go (a :<| bs) (Just 0)
              in
              case maybeNewFoc of
                Nothing -> error "sortByFL: go: this should never happen, lost the focus"
                Just newFoc -> (b :<| newSeq, Just (newFoc + 1))
    -- Trying to sort a non-empty sequence where some element other than the
    -- top element has the focus.
    go (a :<| as) (Just n) =
      let res = go as (Just (n - 1))
      in
      case res of
        (_, Nothing) -> error "sortByFL: go: this should never happen, no focus"
        (Empty, Just _) -> error "sortByFL: go: this should never happen, focus but no elems"
        (b :<| bs, Just newFoc) ->
          case cmpFunc a b of
            LT -> (a :<| b :<| bs, Just (newFoc + 1))
            EQ -> (a :<| b :<| bs, Just (newFoc + 1))
            GT ->
              case newFoc of
                0 -> (b :<| fst (go (a :<| bs) Nothing), Just 0)
                gt0 ->
                  let (newSeq, maybeNewFoc') = go (a :<| bs) (Just gt0)
                  in
                  case maybeNewFoc' of
                    Nothing -> error "sortByFL: go: this should never happen, lost the focus again"
                    Just newFoc' -> (b :<| newSeq, Just (newFoc' + 1))
