-- |
-- There are two ways to use this library: the way with extra boilerplate,
-- or the way with a leaky abstraction. A truly good solution would fix it
-- all with Template Haskell; but the author is not a truly good Haskeller,
-- so this will have to do for now.
-- 
-- The boilerplate way:
-- 
-- @
-- data Hideous = Hideous { yak :: [String] }
-- instance ToJSON Hideous where
--   toJSON x = object [ "yak" .= hairy (yak x) ]
-- instance FromJSON Hideous where
--   parseJSON (Object o) = Hideous \<$> (shave \<$> o .:? "yak")
-- @
-- >>> encode $ Hideous ["foo","bar"]
-- "{\"yak\":[\"foo\",\"bar\"]}"
-- >>> encode $ Hideous ["baz"]
-- "{\"yak\":\"baz\"}"
-- >>> encode $ Hideous []
-- "{\"yak\":null}"
-- >>> yak <$> decode "{\"yak\":[\"foo\",\"bar\"]}"
-- Just ["foo", "bar"]
-- >>> yak <$> decode "{\"yak\":\"baz\"}"
-- Just ["baz"]
-- >>> yak <$> decode "{}"
-- Just []
-- 
-- The leaky way:
-- 
-- @
-- data Abhorrent = Abhorrent { yak :: Yak String }
-- $(deriveJSON defaultOptions{omitNothingFields=True} ''Abhorrent)
-- @
-- >>> encode . Abhorrent . hairy $ ["shaven","shorn","sheared"]
-- "{\"yak\":[\"shaven\",\"shorn\",\"sheared\"]}"
-- >>> shave . yak <$> decode "{}"
-- Just []
-- 
-- Which to prefer depends on how many yaks you need to deal with /vs./ how
-- much you hate cleaning up yak droppings in the rest of your codebase.
module Data.Aeson.Yak
( Yak
, hairy
, shave)
where

import           Data.Aeson
import           Data.Foldable

-- | Data whose JSON representation may legally be an array, a single element,
--   or null\/absent. No, please, calm down. It'll be okay. Mostly.
--   
--   /('Lousy' is not exposed to avoid namespace infestation./
--   /This is open for discussion if a use case can be shown.)/
type Yak a = Maybe (Lousy a)

-- | Convert a @'Foldable'@ to a 'Yak'. Should probably be specific to lists,
--   but what's life without a little adventure?
hairy :: (Foldable f) => f a -> Yak a
hairy beast = case toList beast of
  []  -> Nothing
  yak -> Just (Lousy yak)

-- | Convert a 'Yak' to a list. Relax, and allow yourself to breathe.
shave :: Yak a -> [a]
shave Nothing = []
shave (Just nit) = pick nit

newtype Lousy a = Lousy { pick :: [a] } deriving (Eq)
instance (Show a) => Show (Lousy a) where
  show = ("Lousy " ++) . show . pick

instance (ToJSON a) => ToJSON (Lousy a) where
  toJSON nit = case pick nit of
    [x] -> toJSON x
    xs  -> toJSON xs

instance (FromJSON a) => FromJSON (Lousy a) where
  parseJSON vs@(Array _) = Lousy <$> parseJSON vs
  parseJSON v = (\a -> Lousy [a]) <$> parseJSON v