{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DeriveFunctor       #-}
{-# LANGUAGE FlexibleInstances   #-}
{-# LANGUAGE RecordWildCards     #-}
{-# LANGUAGE TupleSections       #-}
{-# LANGUAGE TypeFamilies        #-}
module Salak.Types.Source where

import           Control.Monad.Writer
import qualified Data.Map.Strict      as M
import           Data.Maybe
import qualified Data.Text            as T
import           Salak.Types.Selector
import           Salak.Types.Value

data SourceT v = Source
  { value    :: v
  , mapValue :: M.Map Selector (SourceT v)
  } deriving (Eq, Functor)

instance Foldable SourceT where
  foldr g b Source{..} = M.foldl (foldr g) (g value b) mapValue

type Source = SourceT QV

showKey :: String -> Selector -> String
showKey p (SStr k)
  | null p    = T.unpack k
  | otherwise = p <> "." <> T.unpack k
showKey p (SNum k) = p <> "[" <> show k <> "]"

instance Show Source where
  show = unlines . go ""
    where
      go p Source{..} = concat $ M.foldrWithKey (\k v b -> go (showKey p k) v : b) [ g2 p value] mapValue
      g2 x v = if nullQ v then [] else [x <> "=" <> show v]

emptySource :: Source
emptySource = Source mempty mempty

foldSource :: (Value -> b -> b) -> b -> Source -> b
foldSource f = foldr (\q b -> maybe b (`f` b) $ getQ q)

sizeSource :: Source -> Int
sizeSource = foldSource  (const (+1)) 0

nullSource :: Source -> Bool
nullSource = foldSource (\_ _ -> False) True

selectSource :: Selector -> Source -> Source
selectSource n Source{..} = fromMaybe emptySource $ M.lookup n mapValue

updateSource :: Monad m => Selector -> (Source -> m Source) -> Source -> m Source
updateSource n f ss = do
  ss' <- f $ selectSource n ss
  return $ ss { mapValue = M.insert n ss' (mapValue ss) }

updateSources :: Monad m => [Selector] -> (Source -> m Source) -> Source -> m Source
updateSources = flip (foldr updateSource)

replace :: Priority -> Source -> Source -> Writer [String] Source
replace = replace' []

replace' :: [Selector] -> Priority -> Source -> Source -> Writer [String] Source
replace' ss i ns os = do
  q' <- replaceQ (toKey ss) i (value ns) (value os)
  m' <- mapM snd $ M.mapWithKey g2 $ M.unionWithKey go (f 0 $ mapValue ns) (f 1 $ mapValue os)
  return (Source q' $ M.filter (not.nullSource) m')
  where
    f j m = (j :: Int,) . return <$> m
    go k (_, a) (_, b) = (2, a >>= \a' -> b >>= \b' -> replace' (k:ss) i a' b')
    g2 k (0, a) = (0, a >>= \a' -> replace' (k:ss) i a' emptySource)
    g2 k (1, a) = (1, a >>= \a' -> replace' (k:ss) i emptySource a')
    g2 _ (_, a) = (2 :: Int, a)

insert :: Monad m => T.Text -> Value -> Source -> WriterT [String] m Source
insert k v s = case selectors k of
  Left  e  -> tell [e] >> return s
  Right k' -> return (insert' k' v s)

insert' :: [Selector] -> Value -> Source -> Source
insert' ns v = foldr go (insertSource v) ns
  where
    go n f s = s { mapValue = M.alter (Just . f . fromMaybe emptySource) n $ mapValue s}

insertSource :: Value -> Source -> Source
insertSource v s = s { value = insertQ v $ value s}