{-# language LambdaCase #-}
{-# language DeriveFunctor, DeriveFoldable, DeriveTraversable, DeriveGeneric #-}
module Language.Python.Syntax.CommaSep
  ( Comma(..)
  , CommaSep(..), _CommaSep, csTrailingWhitespace
  , appendCommaSep, maybeToCommaSep, listToCommaSep
  , CommaSep1(..)
  , commaSep1Head, appendCommaSep1, listToCommaSep1, listToCommaSep1'
  , CommaSep1'(..)
  , _CommaSep1'
  )
where
import Control.Lens.Getter ((^.))
import Control.Lens.Iso (Iso, iso)
import Control.Lens.Lens (lens)
import Control.Lens.Setter ((.~))
import Control.Lens.Traversal (Traversal')
import Data.Coerce (coerce)
import Data.Function ((&))
import Data.Functor (($>))
import Data.Functor.Apply ((<.>))
import Data.List.NonEmpty (NonEmpty(..))
import Data.Maybe (fromMaybe)
import Data.Semigroup (Semigroup(..))
import Data.Semigroup.Foldable (Foldable1(..))
import Data.Semigroup.Traversable (Traversable1(..), foldMap1Default)
import GHC.Generics (Generic)
import Language.Python.Syntax.Punctuation
import Language.Python.Syntax.Whitespace (Whitespace (Space), HasTrailingWhitespace (..))
data CommaSep a
  = CommaSepNone
  | CommaSepOne a
  | CommaSepMany a Comma (CommaSep a)
  deriving (Eq, Show, Functor, Foldable, Traversable, Generic)
csTrailingWhitespace
  :: HasTrailingWhitespace a
  => Traversal' (CommaSep a) [Whitespace]
csTrailingWhitespace _ CommaSepNone = pure CommaSepNone
csTrailingWhitespace f (CommaSepOne a) = CommaSepOne <$> trailingWhitespace f a
csTrailingWhitespace f (CommaSepMany a (MkComma b) CommaSepNone) =
  (\b' -> CommaSepMany a (MkComma b') CommaSepNone) <$> f b
csTrailingWhitespace f (CommaSepMany a b c) =
  CommaSepMany a b <$> csTrailingWhitespace f c
maybeToCommaSep :: Maybe a -> CommaSep a
maybeToCommaSep = maybe CommaSepNone CommaSepOne
listToCommaSep :: [a] -> CommaSep a
listToCommaSep [] = CommaSepNone
listToCommaSep [a] = CommaSepOne a
listToCommaSep (a:as) = CommaSepMany a (MkComma [Space]) $ listToCommaSep as
appendCommaSep :: [Whitespace] -> CommaSep a -> CommaSep a -> CommaSep a
appendCommaSep _  CommaSepNone b = b
appendCommaSep _  (CommaSepOne a) CommaSepNone = CommaSepOne a
appendCommaSep ws (CommaSepOne a) (CommaSepOne b) = CommaSepMany a (MkComma ws) (CommaSepOne b)
appendCommaSep ws (CommaSepOne a) (CommaSepMany b c cs) = CommaSepMany a (MkComma ws) (CommaSepMany b c cs)
appendCommaSep ws (CommaSepMany a c cs) b = CommaSepMany a c (appendCommaSep ws cs b)
instance Semigroup (CommaSep a) where
  (<>) = appendCommaSep [Space]
instance Monoid (CommaSep a) where
  mempty  = CommaSepNone
  mappend = (<>)
data CommaSep1 a
  = CommaSepOne1 a
  | CommaSepMany1 a Comma (CommaSep1 a)
  deriving (Eq, Show, Functor, Foldable, Traversable, Generic)
instance Foldable1 CommaSep1 where; foldMap1 = foldMap1Default
instance Traversable1 CommaSep1 where
  traverse1 f = go
    where
      go (CommaSepOne1 a) = CommaSepOne1 <$> f a
      go (CommaSepMany1 a b c) = (\a' c' -> CommaSepMany1 a' b c') <$> f a <.> go c
commaSep1Head :: CommaSep1 a -> a
commaSep1Head (CommaSepOne1 a) = a
commaSep1Head (CommaSepMany1 a _ _) = a
appendCommaSep1 :: [Whitespace] -> CommaSep1 a -> CommaSep1 a -> CommaSep1 a
appendCommaSep1 ws a b =
  CommaSepMany1
    (case a of; CommaSepOne1 x -> x;  CommaSepMany1 x _ _  -> x)
    (case a of; CommaSepOne1 _ -> MkComma ws; CommaSepMany1 _ ws' _ -> ws')
    (case a of; CommaSepOne1 _ -> b;  CommaSepMany1 _ _ x  -> x <> b)
instance Semigroup (CommaSep1 a) where
  (<>) = appendCommaSep1 [Space]
instance HasTrailingWhitespace s => HasTrailingWhitespace (CommaSep1 s) where
  trailingWhitespace =
    lens
      (\case
         CommaSepOne1 a -> a ^. trailingWhitespace
         CommaSepMany1 _ _ a -> a ^. trailingWhitespace)
      (\cs ws ->
         case cs of
           CommaSepOne1 a ->
             CommaSepOne1 (a & trailingWhitespace .~ ws)
           CommaSepMany1 a b c -> CommaSepMany1 (coerce a) b (c & trailingWhitespace .~ ws))
listToCommaSep1 :: NonEmpty a -> CommaSep1 a
listToCommaSep1 (a :| as) = go (a:as)
  where
    go [] = error "impossible"
    go [x] = CommaSepOne1 x
    go (x:xs) = CommaSepMany1 x (MkComma [Space]) $ go xs
data CommaSep1' a
  = CommaSepOne1' a (Maybe Comma)
  | CommaSepMany1' a Comma (CommaSep1' a)
  deriving (Eq, Show, Functor, Foldable, Traversable, Generic)
instance Foldable1 CommaSep1' where; foldMap1 = foldMap1Default
instance Traversable1 CommaSep1' where
  traverse1 f = go
    where
      go (CommaSepOne1' a b) = (\a' -> CommaSepOne1' a' b) <$> f a
      go (CommaSepMany1' a b c) = (\a' c' -> CommaSepMany1' a' b c') <$> f a <.> go c
_CommaSep
  :: Iso
       (Maybe (a, [(Comma, a)], Maybe Comma))
       (Maybe (b, [(Comma, b)], Maybe Comma))
       (CommaSep a)
       (CommaSep b)
_CommaSep = iso toCs fromCs
  where
    toCs :: Maybe (a, [(Comma, a)], Maybe Comma) -> CommaSep a
    toCs Nothing = CommaSepNone
    toCs (Just (a, b, c)) =
      case b of
        [] -> maybe (CommaSepOne a) (\c' -> CommaSepMany a c' CommaSepNone) c
        (d, e):ds -> CommaSepMany a d $ toCs (Just (e, ds, c))
    fromCs :: CommaSep a -> Maybe (a, [(Comma, a)], Maybe Comma)
    fromCs CommaSepNone = Nothing
    fromCs (CommaSepOne a) = Just (a, [], Nothing)
    fromCs (CommaSepMany a b c) =
      case fromCs c of
        Nothing -> Just (a, [], Just b)
        Just (x, y, z) -> Just (a, (b, x) : y, z)
_CommaSep1'
  :: Iso
       (a, [(Comma, a)], Maybe Comma)
       (b, [(Comma, b)], Maybe Comma)
       (CommaSep1' a)
       (CommaSep1' b)
_CommaSep1' = iso toCs fromCs
  where
    toCs (a, [], b) = CommaSepOne1' a b
    toCs (a, (b, c) : bs, d) = CommaSepMany1' a b $ toCs (c, bs, d)
    fromCs (CommaSepOne1' a b) = (a, [], b)
    fromCs (CommaSepMany1' a b c) =
      let
        (d, e, f) = fromCs c
      in
        (a, (b, d) : e, f)
listToCommaSep1' :: [a] -> Maybe (CommaSep1' a)
listToCommaSep1' [] = Nothing
listToCommaSep1' [a] = Just (CommaSepOne1' a Nothing)
listToCommaSep1' (a:as) =
  CommaSepMany1' a (MkComma [Space]) <$> listToCommaSep1' as
instance HasTrailingWhitespace s => HasTrailingWhitespace (CommaSep1' s) where
  trailingWhitespace =
    lens
      (\case
         CommaSepOne1' a b -> maybe (a ^. trailingWhitespace) (^. trailingWhitespace) b
         CommaSepMany1' _ _ a -> a ^. trailingWhitespace)
      (\cs ws ->
         case cs of
           CommaSepOne1' a b ->
             CommaSepOne1'
               (fromMaybe (a & trailingWhitespace .~ ws) $ b $> coerce a)
               (b $> MkComma ws)
           CommaSepMany1' a b c ->
             CommaSepMany1' (coerce a) b (c & trailingWhitespace .~ ws))