{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleContexts #-}
module Hpack.Syntax.UnknownFields (
  FieldName
, HasFieldNames(..)
, hyphenize

, CaptureUnknownFields
, Preposition(..)
, formatUnknownFields
) where

import           Control.Monad
import           Data.Data
import qualified Data.HashMap.Lazy as HashMap
import           Data.List
import qualified Data.Text as T
import           GHC.Generics

import           Hpack.Syntax.GenericsUtil
import           Hpack.Syntax.Util

newtype FieldName = FieldName {unFieldName :: String}

class HasFieldNames a where
  fieldNames :: Proxy a -> [FieldName]

  default fieldNames :: (HasTypeName a d m, Selectors (Rep a)) => Proxy a -> [FieldName]
  fieldNames proxy = map (FieldName . hyphenize (typeName proxy)) (selectors proxy)

  ignoreUnderscoredUnknownFields :: Proxy a -> Bool
  ignoreUnderscoredUnknownFields _ = False

data CaptureUnknownFields a = CaptureUnknownFields [FieldName] a
  deriving Functor

instance Applicative CaptureUnknownFields where
  pure = return
  (<*>) = ap

instance Monad CaptureUnknownFields where
  return = CaptureUnknownFields mempty
  (CaptureUnknownFields xs x) >>= f = CaptureUnknownFields (xs `mappend` ys) y
    where
      CaptureUnknownFields ys y = f x

captureUnknownFields :: forall a. (HasFieldNames a, FromJSON a) => Value -> Parser (CaptureUnknownFields a)
captureUnknownFields v = CaptureUnknownFields unknown <$> parseJSON v
  where
    unknown = getUnknownFields v (Proxy :: Proxy a)

instance (HasFieldNames a, FromJSON a) => FromJSON (CaptureUnknownFields a) where
  parseJSON = captureUnknownFields

getUnknownFields :: forall a. HasFieldNames a => Value -> Proxy a -> [FieldName]
getUnknownFields v _ = case v of
  Object o -> map FieldName (ignoreUnderscored unknown)
    where
      unknown = keys \\ fields
      keys = map T.unpack (HashMap.keys o)
      fields = map unFieldName $ fieldNames (Proxy :: Proxy a)
      ignoreUnderscored
        | ignoreUnderscoredUnknownFields (Proxy :: Proxy a) = filter (not . isPrefixOf "_")
        | otherwise = id
  _ -> []

data Preposition = In | For

formatUnknownFields :: Preposition -> String -> CaptureUnknownFields a -> ([String], a)
formatUnknownFields p name (CaptureUnknownFields unknownFields a) = (formatUnknownFields_ preposition name unknownFields, a)
  where
    preposition = case p of
      In -> "in"
      For -> "for"

formatUnknownFields_ :: String -> String -> [FieldName] -> [String]
formatUnknownFields_ preposition name = map f
  where
    f (FieldName field) = "Ignoring unknown field " ++ show field ++ " " ++ preposition ++ " " ++ name