{-# LANGUAGE DeriveDataTypeable #-}

-- | Nonempty lists.

module Agda.Utils.NonemptyList where

import Control.Monad

import Data.Data (Data)
import Data.Foldable (Foldable)
  -- The toList method of Foldable may do something stupid,
  -- like traversing the list just to build a list again.
import Data.Traversable (Traversable)
import Data.Maybe
import Data.Semigroup
import qualified Data.List as List

infixr 5 :!
data NonemptyList a = (:!) { headNe :: a, tailNe :: [a] }
  deriving (Eq, Ord, Functor, Foldable, Traversable, Data)

instance Semigroup (NonemptyList a) where
  (x :! xs) <> (y :! ys) = x :! xs ++ y : ys

instance Applicative NonemptyList where
  pure x = x :! []
  (<*>) = ap

instance Monad NonemptyList where
  return = pure
  (x :! xs) >>= f = foldr1 (<>) (f x : map f xs)

instance Show a => Show (NonemptyList a) where
  showsPrec _ = showList . toList

-- | Implementing conversion to list manually, since @Foldable.toList@
--   might recurse over the tail and, thus, destroy sharing.
toList :: NonemptyList a -> [a]
toList (x :! xs) = x : xs

-- | Converting a list (safe).
fromList :: [a] -> Maybe (NonemptyList a)
fromList []       = Nothing
fromList (x : xs) = Just $ x :! xs

-- | Prepending an element.
consNe :: a -> NonemptyList a -> NonemptyList a
consNe x (y :! zs) = x :! (y : zs)

-- | Returns the union of the argument lists seen as sets. The order of the
--   elements in the result is not specified. Precondition: arguments contain
--   no duplicates.
unionNe :: Eq a => NonemptyList a -> NonemptyList a -> NonemptyList a
unionNe (x :! xs) (y :! ys) = z :! zs
  where z : zs = List.union (x : xs) (y : ys)

-- | Zip two nonempty lists.
zipWithNe :: (a -> b -> c) -> NonemptyList a -> NonemptyList b -> NonemptyList c
zipWithNe f (x :! xs) (y :! ys) = f x y :! zipWith f xs ys

-- | Zip two nonempty lists.
zipNe :: NonemptyList a -> NonemptyList b -> NonemptyList (a, b)
zipNe = zipWithNe (,)

-- | Case on a list, getting a nonempty list in the cons case.
caseListNe :: [a] -> b -> (NonemptyList a -> b) -> b
caseListNe []       e ne = e
caseListNe (x : xs) e ne = ne (x :! xs)

-- | Case on a list, with list last.
listCaseNe :: b -> (NonemptyList a -> b) -> [a] -> b
listCaseNe e ne xs = caseListNe xs e ne

-- | Check if an element is present in a list.
elemNe :: Eq a => a -> NonemptyList a -> Bool
elemNe y (x :! xs) = elem y (x : xs)
