{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE GADTs           #-}
{-# LANGUAGE RankNTypes      #-}
{-# LANGUAGE TypeFamilies    #-}

module System.Console.GetOpt.Generics.Internal where

import           Prelude ()
import           Prelude.Compat

import           Data.Char
import           Generics.SOP

import           System.Console.GetOpt.Generics.Result

normalizedDatatypeInfo :: (HasDatatypeInfo a, Code a ~ xss, SingI xss) =>
  Proxy a -> Result (DatatypeInfo xss)
normalizedDatatypeInfo p =
  mapFieldInfoM
    (\ (FieldInfo s) -> FieldInfo <$> normalizeFieldName s)
    (datatypeInfo p)

mapFieldInfoM :: (SingI xss, Applicative m) =>
     (forall a . FieldInfo a -> m (FieldInfo a))
  -> DatatypeInfo xss -> m (DatatypeInfo xss)
mapFieldInfoM f info = case info of
    (ADT mod name constructors) ->
      ADT mod name <$> hsequence' (hliftA (Comp . mapSingleCons f) constructors)
    (Newtype mod name constructor) ->
      Newtype mod name <$> (mapSingleCons f constructor)
  where
    mapSingleCons :: forall m xs . (Applicative m) =>
         (forall a . FieldInfo a -> m (FieldInfo a))
      -> ConstructorInfo xs -> m (ConstructorInfo xs)
    mapSingleCons f c = case c of
      (Record name fields) -> Record name <$> hsequence' (hliftA (Comp . f) fields)
      cons@Infix{} -> pure cons
      cons@Constructor{} -> pure cons

normalizeFieldName :: String -> Result String
normalizeFieldName s =
    let normalized = dropWhile (== '-') $
          filter (\ c -> (isAscii c && isAlpha c) || (c == '-')) s
    in case normalized of
      "" -> Errors ["unsupported field name: " ++ s]
      x -> Success $ slugify x
  where
    slugify (a : r)
      | isUpper a = slugify (toLower a : r)
    slugify (a : b : r)
      | isUpper b = a : '-' : slugify (toLower b : r)
      | otherwise = a : slugify (b : r)
    slugify x = x