{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}

module Deck.Deck (
    -- * Types
    Card (..),
    DeckList,
    CardName,
    PositiveNumber (..),
    NonEmptyText (..),
    CategoryName,
    Copies,

    -- * Defaults
    mainboard,
    sideboard,

    -- * Functions
    insert,
    delete,
    -- applyDiff,
    runDeckList,
    partialEqCard,
    find,
    mkCard,
    -- Lenses
    categories,
    name,
) where

import Data.Map (Map)
import qualified Data.Map as Map
import Data.NanoID
import Data.Text (Text)

import Control.Lens

import System.Random.MWC

import Refined

import Deck.Category (Category (..), CategoryName, Copies)
import Util

type CardName = NonEmptyText

-- | Represent a card in a `DeckList`
data Card = Card
    { _internalName :: !CardName
    , _categories :: Map Text Category
    }
    deriving (Show, Eq, Ord)

makeLenses ''Card

name :: Getter Card NonEmptyText
name = to (^. internalName)

mainboard, sideboard :: CategoryName
mainboard = NonEmptyText $$(refineTH "Mainboard")
sideboard = NonEmptyText $$(refineTH "Sideboard")

mkCard :: CardName -> CategoryName -> Copies -> Card
mkCard cardName primary copies =
    let categoryName = toText primary
     in Card
            { _internalName = cardName
            , _categories = Map.singleton categoryName (Category primary copies)
            }

-- | A decklist is a sequence of `Card`s
newtype DeckList = DeckList {runDeckList :: Map Text Card}
    deriving (Show, Eq, Ord)

-- | Insert a `Card`. If the `Card` already exists then replace it with the new value.
insert :: Card -> DeckList -> DeckList
insert a b =
    let key = toText $ a ^. name
     in DeckList . Map.insert key a . runDeckList $ b

-- | Delete a card from a decklist if it exists
delete :: CardName -> DeckList -> DeckList
delete x =
    let cardName = unrefine . runText $ x
     in DeckList . Map.delete cardName . runDeckList

-- | Find @cname@ in the list @xs@, return Nothing if it doesn't exist in the list
find :: CardName -> DeckList -> Maybe Card
find cname xs =
    let cname' = unrefine . runText $ cname
     in (Map.lookup cname' . runDeckList $ xs)

partialEqCard :: (Eq a) => (Card -> a) -> Card -> Card -> Bool
partialEqCard f x y = f x == f y

-- initDeckList :: IO DeckList
-- initDeckList = do
--     gen <- createSystemRandom
--     nid <- nanoID gen
--     return . DeckList . Set.fromList $ []