-- | Module for custom instance of Data.HashMap.Lazy that decodes from any list of objects that have an id field
module Calamity.Internal.SnowflakeMap where

import           Calamity.Internal.Utils  ()
import           Calamity.Types.Snowflake

import           Control.DeepSeq
import           Control.Lens.At
import           Control.Lens.Iso
import           Control.Lens.Wrapped

import           Data.Aeson               ( FromJSON(..), ToJSON(..), withArray )
import           Data.Data
import qualified Data.Foldable            as F
import           Data.HashMap.Lazy        ( HashMap )
import qualified Data.HashMap.Lazy        as LH
import           Data.Hashable

import           GHC.Exts                 ( IsList )
import           GHC.Generics

import           TextShow
import qualified TextShow.Generic         as TSG

import           Unsafe.Coerce

newtype SnowflakeMap a = SnowflakeMap
  { unSnowflakeMap :: HashMap (Snowflake a) a
  }
  deriving ( Generic, Eq, Data, Ord, Show )
  deriving ( TextShow ) via TSG.FromGeneric (SnowflakeMap a)
  deriving newtype ( IsList, Semigroup, Monoid )
  deriving anyclass ( NFData, Hashable )

-- instance At (SnowflakeMap a) where
--   at k f m = at (unSnowflakeMap k) f m

instance Functor SnowflakeMap where
  fmap f = SnowflakeMap . coerceSnowflakeMap . fmap f . unSnowflakeMap

instance Foldable SnowflakeMap where
  foldr f b = Prelude.foldr f b . unSnowflakeMap

instance Traversable SnowflakeMap where
  traverse f = fmap (SnowflakeMap . coerceSnowflakeMap) . traverse f . unSnowflakeMap

-- deriving instance NFData a => NFData (SnowflakeMap a)

-- deriving instance Hashable a => Hashable (SnowflakeMap a)

instance Wrapped (SnowflakeMap a) where
  type Unwrapped (SnowflakeMap a) = HashMap (Snowflake a) a

  _Wrapped' = iso unSnowflakeMap SnowflakeMap

type instance (Index (SnowflakeMap a)) = Snowflake a

type instance (IxValue (SnowflakeMap a)) = a

instance SnowflakeMap a ~ t => Rewrapped (SnowflakeMap b) a

instance Ixed (SnowflakeMap a) where
  ix i = _Wrapped . ix i

instance At (SnowflakeMap a) where
  at i = _Wrapped . at i

over :: (HashMap (Snowflake a) a -> HashMap (Snowflake b) b) -> SnowflakeMap a -> SnowflakeMap b
over f =  SnowflakeMap . f . unSnowflakeMap
{-# INLINABLE over #-}

-- I guess I could just do this by unwrapping everything and using coerceSnowflake
coerceSnowflakeMap :: HashMap (Snowflake a) v -> HashMap (Snowflake b) v
coerceSnowflakeMap = unsafeCoerce
{-# INLINABLE coerceSnowflakeMap #-}

empty :: SnowflakeMap a
empty = SnowflakeMap LH.empty
{-# INLINABLE empty #-}

singleton :: HasID' a => a -> SnowflakeMap a
singleton v = SnowflakeMap $ LH.singleton (getID v) v
{-# INLINABLE singleton #-}

null :: SnowflakeMap a -> Bool
null = LH.null . unSnowflakeMap
{-# INLINABLE null #-}

size :: SnowflakeMap a -> Int
size = LH.size . unSnowflakeMap
{-# INLINABLE size #-}

member :: Snowflake a -> SnowflakeMap a -> Bool
member k = LH.member k . unSnowflakeMap
{-# INLINABLE member #-}

lookup :: Snowflake a -> SnowflakeMap a -> Maybe a
lookup k = LH.lookup k . unSnowflakeMap
{-# INLINABLE lookup #-}

lookupDefault :: a -> Snowflake a -> SnowflakeMap a -> a
lookupDefault d k = LH.lookupDefault d k . unSnowflakeMap
{-# INLINABLE lookupDefault #-}

(!) :: SnowflakeMap a -> Snowflake a -> a
(!) m k = unSnowflakeMap m LH.! k
{-# INLINABLE (!) #-}

insert :: HasID' a => a -> SnowflakeMap a -> SnowflakeMap a
insert v = over $ LH.insert (getID v) v
{-# INLINABLE insert #-}

insertWith :: HasID' a => (a -> a -> a) -> a -> SnowflakeMap a -> SnowflakeMap a
insertWith f v = over $ LH.insertWith f (getID v) v
{-# INLINABLE insertWith #-}

delete :: Snowflake a -> SnowflakeMap a -> SnowflakeMap a
delete k = over $ LH.delete k
{-# INLINABLE delete #-}

adjust :: (a -> a) -> Snowflake a -> SnowflakeMap a -> SnowflakeMap a
adjust f k = over $ LH.adjust f k
{-# INLINABLE adjust #-}

update :: (a -> Maybe a) -> Snowflake a -> SnowflakeMap a -> SnowflakeMap a
update f k = over $ LH.update f k
{-# INLINABLE update #-}

alter :: (Maybe a -> Maybe a) -> Snowflake a -> SnowflakeMap a -> SnowflakeMap a
alter f k = over $ LH.alter f k
{-# INLINABLE alter #-}

union :: SnowflakeMap a -> SnowflakeMap a -> SnowflakeMap a
union m m' = SnowflakeMap $ LH.union (unSnowflakeMap m) (unSnowflakeMap m')
{-# INLINABLE union #-}

unionWith :: (a -> a -> a) -> SnowflakeMap a -> SnowflakeMap a -> SnowflakeMap a
unionWith f m m' = SnowflakeMap $ LH.unionWith f (unSnowflakeMap m) (unSnowflakeMap m')
{-# INLINABLE unionWith #-}

unionWithKey :: (Snowflake a -> a -> a -> a) -> SnowflakeMap a -> SnowflakeMap a -> SnowflakeMap a
unionWithKey f m m' = SnowflakeMap $ LH.unionWithKey f (unSnowflakeMap m) (unSnowflakeMap m')
{-# INLINABLE unionWithKey #-}

unions :: [SnowflakeMap a] -> SnowflakeMap a
unions = SnowflakeMap . LH.unions . Prelude.map unSnowflakeMap
{-# INLINABLE unions #-}

map :: (a1 -> a2) -> SnowflakeMap a1 -> SnowflakeMap a2
map f = over $ coerceSnowflakeMap . LH.map f
{-# INLINABLE map #-}

mapWithKey :: (Snowflake a1 -> a1 -> a2) -> SnowflakeMap a1 -> SnowflakeMap a2
mapWithKey f = over $ coerceSnowflakeMap . LH.mapWithKey f
{-# INLINABLE mapWithKey #-}

traverseWithKey :: Applicative f => (Snowflake a1 -> a1 -> f a2) -> SnowflakeMap a1 -> f (SnowflakeMap a2)
traverseWithKey f = fmap (SnowflakeMap . coerceSnowflakeMap) . LH.traverseWithKey f . unSnowflakeMap
{-# INLINABLE traverseWithKey #-}

difference :: SnowflakeMap a -> SnowflakeMap a -> SnowflakeMap a
difference m m' = SnowflakeMap $ LH.difference (unSnowflakeMap m) (unSnowflakeMap m')
{-# INLINABLE difference #-}

differenceWith :: (a -> a -> Maybe a) -> SnowflakeMap a -> SnowflakeMap a -> SnowflakeMap a
differenceWith f m m' = SnowflakeMap $ LH.differenceWith f (unSnowflakeMap m) (unSnowflakeMap m')
{-# INLINABLE differenceWith #-}

intersection :: SnowflakeMap a -> SnowflakeMap a -> SnowflakeMap a
intersection m m' = SnowflakeMap $ LH.intersection (unSnowflakeMap m) (unSnowflakeMap m')
{-# INLINABLE intersection #-}

intersectionWith :: (a -> a -> b) -> SnowflakeMap a -> SnowflakeMap a -> SnowflakeMap b
intersectionWith f m m' = SnowflakeMap . coerceSnowflakeMap $ LH.intersectionWith f (unSnowflakeMap m) (unSnowflakeMap m')
{-# INLINABLE intersectionWith #-}

intersectionWithKey :: (Snowflake a -> a -> a -> b) -> SnowflakeMap a -> SnowflakeMap a -> SnowflakeMap b
intersectionWithKey f m m' = SnowflakeMap . coerceSnowflakeMap $ LH.intersectionWithKey f (unSnowflakeMap m) (unSnowflakeMap m')
{-# INLINABLE intersectionWithKey #-}

foldl' :: (a -> b -> a) -> a -> SnowflakeMap b -> a
foldl' f s m = LH.foldl' f s $ unSnowflakeMap m
{-# INLINABLE foldl' #-}

foldlWithKey' :: (a -> Snowflake b -> b -> a) -> a -> SnowflakeMap b -> a
foldlWithKey' f s m = LH.foldlWithKey' f s $ unSnowflakeMap m
{-# INLINABLE foldlWithKey' #-}

foldr :: (b -> a -> a) -> a -> SnowflakeMap b -> a
foldr f s m = LH.foldr f s $ unSnowflakeMap m
{-# INLINABLE foldr #-}

foldrWithKey :: (Snowflake b -> b -> a -> a) -> a -> SnowflakeMap b -> a
foldrWithKey f s m = LH.foldrWithKey f s $ unSnowflakeMap m
{-# INLINABLE foldrWithKey #-}

filter :: (a -> Bool) -> SnowflakeMap a -> SnowflakeMap a
filter f = over $ LH.filter f
{-# INLINABLE filter #-}

filterWithKey :: (Snowflake a -> a -> Bool) -> SnowflakeMap a -> SnowflakeMap a
filterWithKey f = over $ LH.filterWithKey f
{-# INLINABLE filterWithKey #-}

mapMaybe :: (a -> Maybe b) -> SnowflakeMap a -> SnowflakeMap b
mapMaybe f = over $ coerceSnowflakeMap . LH.mapMaybe f
{-# INLINABLE mapMaybe #-}

mapMaybeWithKey :: (Snowflake a -> a -> Maybe b) -> SnowflakeMap a -> SnowflakeMap b
mapMaybeWithKey f = over $ coerceSnowflakeMap . LH.mapMaybeWithKey f
{-# INLINABLE mapMaybeWithKey #-}

keys :: SnowflakeMap a -> [Snowflake a]
keys = LH.keys . unSnowflakeMap
{-# INLINABLE keys #-}

elems :: SnowflakeMap a -> [a]
elems = LH.elems . unSnowflakeMap
{-# INLINABLE elems #-}

toList :: SnowflakeMap a -> [(Snowflake a, a)]
toList = LH.toList . unSnowflakeMap
{-# INLINABLE toList #-}

fromList :: HasID' a => [a] -> SnowflakeMap a
fromList = SnowflakeMap . LH.fromList . Prelude.map (\v -> (getID v, v))
{-# INLINABLE fromList #-}

fromListWith :: HasID' a => (a -> a -> a) -> [a] -> SnowflakeMap a
fromListWith f = SnowflakeMap . LH.fromListWith f . Prelude.map (\v -> (getID v, v))
{-# INLINABLE fromListWith #-}

instance (FromJSON a, HasID' a) => FromJSON (SnowflakeMap a) where
  parseJSON = withArray "SnowflakeMap" $ \l -> do
    parsed <- traverse parseJSON l
    pure . Calamity.Internal.SnowflakeMap.fromList . F.toList $ parsed

instance ToJSON a => ToJSON (SnowflakeMap a) where
  toEncoding = toEncoding . elems