{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleInstances #-}

module Data.Separated.Separated(
  Separated
, separated
, Separated1
, separated1
-- * Viewing
, separated1Head
, separated1Tail
-- Constructing
, empty
, (+-)
, single
-- * Appending
, shift
, (.++.)
, (++.)
, (.++)
-- * Alternating
, separatedBy
, separatedBy1
) where

import Control.Applicative(Alternative(many))
import Data.Bifoldable(Bifoldable(bifoldr))
import Data.Bitraversable(Bitraversable(bitraverse))
import Data.Functor.Apply as Apply((<.>))
import Data.List(intercalate, zipWith, repeat)
import Data.Monoid as Monoid(mappend)
import Data.Semigroup as Semigroup((<>))
import Data.Separated.SeparatedCons(SeparatedCons((+:), SeparatedConsF, SeparatedConsG))
import Data.String(String)
import Papa hiding ((<.>))

-- $setup
-- >>> :set -XNoImplicitPrelude
-- >>> import Data.Char(toUpper)
-- >>> import Data.Either(isLeft)
-- >>> import Text.Parsec(parse, char, digit)
-- >>> import Test.QuickCheck(Arbitrary(..))
-- >>> instance (Arbitrary s, Arbitrary a) => Arbitrary (Separated s a) where arbitrary = fmap (^. separated) arbitrary
-- >>> instance (Arbitrary a, Arbitrary s) => Arbitrary (Separated1 s a) where arbitrary = do a <- arbitrary; x <- arbitrary; return ((a, x) ^. separated1)


newtype Separated a b =
  Separated [(a, b)]
  deriving (Eq, Ord)

makeWrapped ''Separated

-- | The isomorphism that swaps elements with their separators.
--
-- >>> swapped # empty
-- []
--
-- >>> swapped # ('x' +: 6 +: empty)
-- [6,'x']
--
-- >>> empty ^. swapped
-- []
--
-- >>> ('x' +: 6 +: empty) ^. swapped
-- [6,'x']
instance Swapped Separated where
  swapped =
    let swap = (\(Separated x) -> Separated (fmap (\(a, b) -> (b, a)) x))
    in iso swap swap

instance Bifunctor Separated where
  bimap f g (Separated x) =
    Separated (fmap (bimap f g) x)

instance Bifoldable Separated where
  bifoldr f g z (Separated x) =
    foldr (\(a, b) -> f a . g b) z x

instance Bitraversable Separated where
  bitraverse f g (Separated x) =
    Separated <$> traverse (\(a, b) -> (,) <$> f a <*> g b) x

-- | Map across a @Separated@ on the element values.
--
-- prop> fmap id (x :: Separated Int String) == x
--
-- prop> \a b -> fmap (+1) (a +: b +: empty) == a +: (1+b) +: empty
instance Functor (Separated a) where
  fmap =
    bimap id

-- | Applies functions with element values, using a zipping operation, appending
-- separators.
--
-- >>> (empty :: Separated [Int] (String -> [String])) <.> empty
-- []
--
-- >>> [1,2] +: (\s -> [s, reverse s, drop 1 s]) +: empty <.> [3,4,5] +: "abc" +: empty
-- [[1,2,3,4,5],["abc","cba","bc"]]
instance Semigroup a => Apply (Separated a) where
  (<.>) =
    separatedAp (<>)

-- | Applies functions with element values, using a zipping operation, appending
-- separators. The identity operation is an infinite list of the empty separator
-- and the given element value.
--
-- >>> (empty :: Separated [Int] (String -> [String])) <*> empty
-- []
--
-- >>> [1,2] +: (\s -> [s, reverse s, drop 1 s]) +: empty <*> [3,4,5] +: "abc" +: empty
-- [[1,2,3,4,5],["abc","cba","bc"]]
instance (Semigroup a, Monoid a) => Applicative (Separated a) where    
  (<*>) =
    separatedAp (<>)
  pure =
    Separated . repeat . (,) mempty

instance (Show a, Show b) => Show (Separated a b) where
  show (Separated x) =
    showSeparated id x

instance Semigroup (Separated a b) where
  Separated x <> Separated y =
    Separated (x <> y)    

instance Monoid (Separated a b) where
  mappend =
    (<>)
  mempty =
    Separated mempty

instance SeparatedCons Separated1 Separated where
  type SeparatedConsF Separated = Separated1
  type SeparatedConsG Separated1 = Separated
  s +: Separated1 a (Separated x) =
    Separated ((s, a) : x)

----

data Separated1 b a =
  Separated1 b (Separated a b)
  deriving (Eq, Ord)

instance Bifunctor Separated1 where
  bimap f g (Separated1 a x) =
    Separated1 (f a) (bimap g f x)

-- | Map across a @Separated1@ on the separator values.
--
-- >>> fmap (+1) (set separated1Tail (1 +: 'b' +: 2 +: 'c' +: empty) (single 'a'))
-- ['a',2,'b',3,'c']
--
-- prop> fmap id (x :: Separated1 Int String) == x
--
-- prop> fmap (+1) (single x) == single x
instance Functor (Separated1 b) where
  fmap =
    bimap id

-- | Applies functions with separator values, using a zipping operation,
-- appending elements.
--
-- >>> [1,2] +: reverse +: [3,4] +: empty <.> [5,6,7] +: "abc" +: [8] +: empty
-- [[1,2,5,6,7],"cba",[3,4,8]]
instance Semigroup b => Apply (Separated1 b) where
  (<.>) =
    separated1Ap (<>)

instance (Show b, Show a) => Show (Separated1 b a) where
  show (Separated1 a (Separated x)) =
    showSeparated (show a:) x
    
-- | Applies functions with separator values, using a zipping operation,
-- appending elements. The identity operation is an infinite list of the empty
-- element and the given separator value.
--
-- >>> [1,2] +: reverse +: [3,4] +: empty <*> [5,6,7] +: "abc" +: [8] +: empty
-- [[1,2,5,6,7],"cba",[3,4,8]]
instance (Semigroup b, Monoid b) => Applicative (Separated1 b) where    
  (<*>) =
    separated1Ap (<>)
  pure =
    Separated1 mempty . (swapped #) . pure

instance SeparatedCons Separated Separated1 where
  type SeparatedConsF Separated1 = Separated
  type SeparatedConsG Separated = Separated1
  (+:) =
    Separated1

----

-- | The isomorphism to a list of pairs of element and separator values.
--
-- >>> separated # empty
-- []
--
-- >>> separated # ('x' +: 6 +: empty)
-- [('x',6)]
--
-- >>> [] ^. separated
-- []
--
-- >>> [(6, [])] ^. separated
-- [6,[]]
separated ::
  Iso [(a, b)] [(c, d)] (Separated a b) (Separated c d)
separated =
  from _Wrapped

empty ::
  Separated s a
empty =
  Separated []

-- | The isomorphism to element values interspersed with a separator.
--
-- >>> separated1 # (single 6)
-- (6,[])
--
-- >>> separated1 # (5 +: 'x' +: single 6)
-- (5,['x',6])
--
-- >>> (6, empty) ^. separated1
-- [6]
--
-- >>> (5, 'x' +- 6) ^. separated1
-- [5,'x',6]
separated1 ::
  Iso (a, Separated s a) (b, Separated t b) (Separated1 a s) (Separated1 b t)
separated1 =
  iso (uncurry Separated1) (\(Separated1 a x) -> (a, x))

-- | A lens on the first element value.
--
-- >>> single 7 ^. separated1Head
-- 7
--
-- prop> single x ^. separated1Head == (x :: Int)
separated1Head ::
  Lens (Separated1 a t) (Separated1 a t) a a
separated1Head =
  from separated1 . _1

-- | A lens on the tail.
--
-- prop> (d +: e +: single x) ^. separated1Tail == e +: x +: empty
separated1Tail ::
  Lens (Separated1 a s) (Separated1 a t) (Separated s a) (Separated t a)
separated1Tail =
  from separated1 . _2

-- | One element and one separator.
--
-- >>> 7 +- "abc"
-- [7,"abc"]
--
-- >>> 7 +: "abc" +: 8 +- "def"
-- [7,"abc",8,"def"]
(+-) ::
  s
  -> a
  -> Separated s a
s +- a =
  s +: single a

infixl 9 +-

-- | Zero element values interspersed with one element.
--
-- >>> single 4
-- [4]
--
-- prop> single x ^. separated1Tail == empty
single ::
  a
  -> Separated1 a s
single a =
  Separated1 a empty

-- | The isomorphism that shuffles the elements and separators one position.
--
-- >>> shift # ([], 6)
-- [6]
--
-- >>> shift # ([(5, 'x')], 6)
-- [5,'x',6]
--
-- >>> single 6 ^. shift
-- ([],6)
--
-- >>> (5 +: 'x' +: single 6) ^. shift
-- ([(5,'x')],6)
shift ::
  Iso (Separated1 a s) (Separated1 b t) ([(a, s)], a) ([(b, t)], b)
shift =
  let shiftR ([], a) =
        Separated1 a (Separated [])
      shiftR ((b, s):r, a) =
        let Separated1 z' (Separated w) = shiftR (r, b)
        in Separated1 z' (Separated ((s, a) : w))
      shiftL (Separated1 s' (Separated [])) =
        ([], s')
      shiftL (Separated1 s' (Separated ((a, t') : r))) =
        let (w, z) = shiftL (Separated1 t' (Separated r))
        in ((s', a) : w, z)
  in iso shiftL shiftR

-- | Append two lists of separated values to produce a list of pairs of separator and element values.
--
-- >>> single 7 .++. single 'a'
-- [7,'a']
--
-- 'a' +: single 7 .++. single 'b'
-- ['a',7,'b']
--
-- prop> a +: (b :: Separated Int Int) == a +: b --  (a +: (b .++. c)) == ((a +: b) .++ c)
(.++.) ::
   Separated1 s a
   -> Separated1 a s
   -> Separated s a
Separated1 s x .++. Separated1 t (Separated y) =
  let (q, r') = (s, x) ^. separated1 . shift
  in Separated (q <> ((r', t) : y)) 

infixr 5 .++.

-- | Append element values interspersed with a separator to a list of pairs of separator and element values.
--
-- >>> empty ++. single 7
-- [7]
--
-- >>> empty ++. 6 +: 'x' +: single 7
-- [6,'x',7]
--
-- >>> 'w' +: empty ++. 6 +: 'x' +: single 7
-- ['w',6,'x',7]
(++.) ::
  Separated s a
  -> Separated1 s a
  -> Separated1 s a
Separated x ++. Separated1 t y =
  let (z, w') = separated1 . shift # (x, t)
  in Separated1 z (w' <> y)

infixr 5 ++.

-- | Append a list of pairs of separator and element values to element values interspersed with a separator.
--
-- >>> single 7 .++ empty
-- [7]
--
-- >>> single 6 .++ 'x' +: 7 +: empty
-- [6,'x',7]
--
-- >>> 'w' +: single 6 .++ 'x' +: 7 +: empty
-- ['w',6,'x',7]
(.++) ::
  Separated1 a s
  -> Separated s a
  -> Separated1 a s
Separated1 a x .++ y =
  Separated1 a (x <> y)

infixr 5 .++

-- |
--
-- >>> parse (separatedBy (char ',') digit) "test" ""
-- Right []
--
-- >>> isLeft (parse (separatedBy (char ',') digit) "test" ",")
-- True
--
-- >>> parse (separatedBy (char ',') digit) "test" ",1"
-- Right [',','1']
--
-- >>> isLeft (parse (separatedBy (char ',') digit) "test" ",1,")
-- True
--
-- >>> parse (separatedBy (char ',') digit) "test" ",1,2,3,4,5"
-- Right [',','1',',','2',',','3',',','4',',','5']
separatedBy ::
  Alternative f =>
  f a
  -> f b
  -> f (Separated a b)
separatedBy a b =
  Separated <$>
    many
      ((,) <$> a <*> b)

-- |
--
-- >>> isLeft (parse (separatedBy1 (char ',') digit) "test" "")
-- True
--
-- >>> parse (separatedBy1 (char ',') digit) "test" ","
-- Right [',']
--
-- >>> isLeft (parse (separatedBy1 (char ',') digit) "test" ",1")
-- True
--
-- >>> parse (separatedBy1 (char ',') digit) "test" ",1,"
-- Right [',','1',',']
--
-- >>>  parse (separatedBy1 (char ',') digit) "test" ",1,2,3,4,5,"
-- Right [',','1',',','2',',','3',',','4',',','5',',']
separatedBy1 ::
  Alternative f =>
  f b
  -> f a
  -> f (Separated1 b a)
separatedBy1 b a =
  Separated1 <$> b <*> separatedBy a b

showSeparated ::
 (Show a, Show s, Functor f) =>
 (f String -> [String])
 -> f (s, a)
 -> String
showSeparated f x =
  '[' : intercalate "," (f (fmap (\(s, a) -> show s <> "," <> show a) x)) <> "]"

separatedAp ::
  (s -> s -> s)
  -> Separated s (a -> b)
  -> Separated s a
  -> Separated s b
separatedAp opp (Separated f) (Separated a) =
    Separated (zipWith (\(s, f') (t, a') -> (s `opp` t, f' a')) f a)  

separated1Ap ::
  (a -> a -> a)
  -> Separated1 a (s -> t)
  -> Separated1 a s
  -> Separated1 a t
separated1Ap opp (Separated1 f (Separated fs)) (Separated1 a (Separated as)) =
    Separated1 (f `opp` a) (Separated (zipWith (\(s, f') (t, a') -> (s t, f' `opp` a')) fs as))