{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# OPTIONS_GHC -Wno-x-partial #-}

module T17722B (setHelper) where

import Data.List.NonEmpty (NonEmpty (..))
import Data.Sequence (Seq)
import Data.Text (Text)
import Data.Void (Void)
import qualified Data.Foldable
import qualified Data.List
import qualified Data.Sequence
import qualified Data.Text

import T17722A

data Expr s a
  = App (Expr s a) (Expr s a)
  | List
  | ListLit (Maybe (Expr s a)) (Seq (Expr s a))

data Src

type Extractor s a = Validation (ExtractErrors s a)

typeError :: Expr s a -> Expr s a -> Extractor s a b
typeError expected actual =
    Failure . ExtractErrors . pure . TypeMismatch $ InvalidDecoder expected actual

extractError :: Text -> Extractor s a b
extractError = Failure . ExtractErrors . pure . ExtractError

newtype ExtractErrors s a = ExtractErrors (NonEmpty (ExtractError s a))
  deriving Semigroup

data ExtractError s a =
    TypeMismatch (InvalidDecoder s a)
  | ExtractError Text

data InvalidDecoder s a = InvalidDecoder (Expr s a) (Expr s a)

data Decoder a = Decoder
    (Expr Src Void -> Extractor Src Void a)
    (Expr Src Void)

setHelper :: (Eq a, Foldable t, Show a)
          => (t a -> Int)
          -> ([a] -> t a)
          -> Decoder a
          -> Decoder (t a)
setHelper size toSet (Decoder extractIn expectedIn) = Decoder extractOut expectedOut
  where
    extractOut (ListLit _ es) = case traverse extractIn es of
        Success vSeq
            | sameSize               -> Success vSet
            | otherwise              -> extractError err
          where
            vList = Data.Foldable.toList vSeq
            vSet = toSet vList
            sameSize = size vSet == Data.Sequence.length vSeq
            duplicates = vList Data.List.\\ Data.Foldable.toList vSet
            err | length duplicates == 1 =
                     "One duplicate element in the list: "
                     <> (Data.Text.pack $ show $ head duplicates)
                | otherwise              = Data.Text.pack $ unwords
                     [ show $ length duplicates
                     , "duplicates were found in the list, including"
                     , show $ head duplicates
                     ]
        Failure f -> Failure f
    extractOut expr = typeError expectedOut expr

    expectedOut = App List expectedIn
