{-# LANGUAGE CPP               #-}
{-# LANGUAGE DeriveAnyClass    #-}
{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Data.LinearRing (
    
        LinearRing
    ,   ListToLinearRingError(..)
    ,   SequenceToLinearRingError(..)
    
    ,   toSeq
    ,   combineToSeq
    ,   fromSeq
    ,   fromLinearRing
    ,   fromList
    ,   fromListWithEqCheck
    ,   makeLinearRing
    ,   ringHead
    ,   ringLength
    ) where
#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 800
import           Prelude             hiding (foldr)
#else
import           Prelude
#endif
import           Control.Applicative (Applicative (..))
import           Control.DeepSeq
import           Control.Lens        (( # ), (^?))
import           Control.Monad       (mzero)
import           Data.Aeson          (FromJSON (..), ToJSON (..), Value)
import           Data.Aeson.Types    (Parser, typeMismatch)
import qualified Data.Foldable       as Foldable
import           Data.Functor        ((<$>))
import           Data.List           (intercalate)
import           Data.List.NonEmpty  as NL (NonEmpty, toList)
import qualified Data.Sequence       as Sequence
import qualified Data.Validation     as Validation
import           GHC.Generics        (Generic)
import qualified Data.SeqHelper      as SeqHelper
data LinearRing a = LinearRing a a a (Sequence.Seq a) deriving (Eq, Show, Generic, NFData)
data ListToLinearRingError a =
        ListTooShort Int
    |   HeadNotEqualToLast a a
    deriving (Eq)
data SequenceToLinearRingError a =
    SequenceTooShort Int
  | FirstNotEqualToLast a a
  deriving (Eq)
ringHead :: LinearRing a -> a
ringHead (LinearRing x _ _ _)   = x
ringLength :: LinearRing a -> Int
ringLength (LinearRing _ _ _ xs) = 4 + Sequence.length xs
fromLinearRing :: LinearRing a -> [a]
fromLinearRing (LinearRing x y z ws) = x : y : z : Foldable.foldr (:) [x] ws
fromList :: (Eq a, Show a, Validation.Validate v, Functor (v (NonEmpty (ListToLinearRingError a)))) => [a] -> v (NonEmpty (ListToLinearRingError a)) (LinearRing a)
fromList (x:y:z:ws@(_:_)) = Validation._Success # LinearRing x y z (fromListDropLast ws)
fromList xs               = Validation._Failure # pure (ListTooShort (length xs))
{-# INLINE fromList #-}
fromListWithEqCheck :: (Eq a, Show a, Validation.Validate v, Applicative (v (NonEmpty (ListToLinearRingError a)))) => [a] -> v (NonEmpty (ListToLinearRingError a)) (LinearRing a)
fromListWithEqCheck xs = checkHeadAndLastEq xs *> fromList xs
combineToSeq :: (a -> a -> b) -> LinearRing a -> Sequence.Seq b
combineToSeq combine (LinearRing a b c rest) = combine a b Sequence.:<| (combine b c Sequence.:<| combineRest)
    where
        combineRest =
          if Sequence.null rest
            then
              Sequence.empty
            else
              (Sequence.zipWith combine <*> SeqHelper.sequenceTail) (c Sequence.<| rest)
{-# INLINE combineToSeq #-}
toSeq :: LinearRing a -> Sequence.Seq a
toSeq (LinearRing a b c rest) = a Sequence.:<| (b Sequence.:<| (c Sequence.:<| rest))
{-# INLINE toSeq #-}
fromSeq :: (Eq a, Show a, Validation.Validate v, Functor (v (NonEmpty (ListToLinearRingError a)))) => Sequence.Seq a -> v (NonEmpty (SequenceToLinearRingError a)) (LinearRing a)
fromSeq as =
    case as of
        (first Sequence.:<| (second Sequence.:<| (third Sequence.:<| rest@(_ Sequence.:|> lastS)))) ->
            if first == lastS then
                Validation._Success # LinearRing first second third rest
            else
                Validation._Failure # pure (FirstNotEqualToLast first lastS)
        (first Sequence.:<| (second Sequence.:<| (third Sequence.:<| _))) ->
            if first == third then
                Validation._Success # LinearRing first second third Sequence.empty
            else
                Validation._Failure # pure (FirstNotEqualToLast first third)
        v -> Validation._Failure # pure (SequenceTooShort (Sequence.length v))
        _ -> Validation._Failure # pure (SequenceTooShort 0)
{-# INLINE fromSeq #-}
makeLinearRing :: (Eq a, Show a) =>
       a                        
    -> a                        
    -> a                        
    -> Sequence.Seq a  
    -> LinearRing a
makeLinearRing = LinearRing
instance (Show a) => Show (ListToLinearRingError a) where
    show (ListTooShort n) = "List too short: (length = " ++ show n ++ ")"
    show (HeadNotEqualToLast h l) = "head (" ++ show h ++ ") /= last(" ++ show l ++ ")"
instance (Show a) => Show (SequenceToLinearRingError a) where
    show (SequenceTooShort n) = "Sequence too short: (length = " ++ show n ++ ")"
    show (FirstNotEqualToLast h l) = "head (" ++ show h ++ ") /= last(" ++ show l ++ ")"
instance Functor LinearRing where
    fmap f (LinearRing x y z ws) = LinearRing (f x) (f y) (f z) (fmap f ws)
instance Foldable LinearRing where
    
    foldr f u (LinearRing x y z ws) = f x (f y (f z (Foldable.foldr f (f x u) ws)))
instance Traversable LinearRing where
    
    sequenceA (LinearRing fx fy fz fws) = (LinearRing <$> fx <*> fy <*> fz <*> sequenceA fws) <* fx
instance (ToJSON a) => ToJSON (LinearRing a) where
    toJSON = toJSON . fromLinearRing
instance (Eq a, FromJSON a, Show a) => FromJSON (LinearRing a) where
    parseJSON v = do
        xs <- parseJSON v
        let vxs = fromListAcc xs
        maybe (parseError v (vxs ^? Validation._Failure)) return (vxs ^? Validation._Success)
fromListAcc :: (Eq a, Show a) => [a] -> Validation.Validation (NonEmpty (ListToLinearRingError a)) (LinearRing a)
fromListAcc = fromList
showErrors :: (Show a) => NonEmpty (ListToLinearRingError a) -> String
showErrors = intercalate ", " . NL.toList . fmap show
parseError :: (Show a) => Value -> Maybe (NonEmpty (ListToLinearRingError a)) -> Parser b
parseError v = maybe mzero (\e -> typeMismatch (showErrors e) v)
checkHeadAndLastEq :: (Eq a, Validation.Validate v, Functor (v (NonEmpty (ListToLinearRingError a))))
    => [a]
    -> v (NonEmpty (ListToLinearRingError a)) ()
checkHeadAndLastEq = maybe (Validation._Failure # pure (ListTooShort 0)) (\(h, l) -> if h == l then Validation._Success # () else Validation._Failure # pure (HeadNotEqualToLast h l)) . mhl
    where
        mhl ::[a] -> Maybe (a, a)
        mhl xs = (,) <$> safeHead xs <*> safeLast xs
safeHead :: [a] -> Maybe a
safeHead []    = Nothing
safeHead (x:_) = Just x
safeLast :: [a] -> Maybe a
safeLast []     = Nothing
safeLast [x]    = Just x
safeLast (_:xs) = safeLast xs
fromListDropLast :: (Eq a) => [a] -> Sequence.Seq a
fromListDropLast []  = Sequence.empty
fromListDropLast [_] = Sequence.empty
fromListDropLast x   = SeqHelper.sequenceHead $ Sequence.fromList x