-- | Item slots for UI and AI item collections.
module Game.LambdaHack.Client.UI.ItemSlot
  ( ItemSlots(..), SlotChar(..)
  , allSlots, allZeroSlots, intSlots, slotLabel, assignSlot
  ) where

import Prelude ()

import Game.LambdaHack.Common.Prelude

import Data.Binary
import Data.Bits (unsafeShiftL, unsafeShiftR)
import Data.Char
import qualified Data.EnumMap.Strict as EM
import qualified Data.EnumSet as ES
import Data.Ord (comparing)
import qualified Data.Text as T

import Game.LambdaHack.Common.Actor
import Game.LambdaHack.Common.ActorState
import Game.LambdaHack.Common.Faction
import Game.LambdaHack.Common.Item
import Game.LambdaHack.Common.Misc
import Game.LambdaHack.Common.State

data SlotChar = SlotChar {slotPrefix :: !Int, slotChar :: !Char}
  deriving (Show, Eq)

instance Ord SlotChar where
  compare = comparing fromEnum

instance Binary SlotChar where
  put = put . fromEnum
  get = fmap toEnum get

instance Enum SlotChar where
  fromEnum (SlotChar n c) =
    unsafeShiftL n 8 + ord c + (if isUpper c then 100 else 0)
  toEnum e =
    let n = unsafeShiftR e 8
        c0 = e - unsafeShiftL n 8
        c100 = c0 - if c0 > 150 then 100 else 0
    in SlotChar n (chr c100)

data ItemSlots = ItemSlots !(EM.EnumMap SlotChar ItemId)
                           !(EM.EnumMap SlotChar ItemId)
  deriving Show

instance Binary ItemSlots where
  put (ItemSlots is os) = put is >> put os
  get = ItemSlots <$> get <*> get

allSlots :: Int -> [SlotChar]
allSlots n = map (SlotChar n) $ ['a'..'z'] ++ ['A'..'Z']

allZeroSlots :: [SlotChar]
allZeroSlots = allSlots 0

intSlots :: [SlotChar]
intSlots = map (flip SlotChar 'a') [0..]

-- | Assigns a slot to an item, for inclusion in the inventory
-- of a hero. Tries to to use the requested slot, if any.
assignSlot :: CStore -> Item -> FactionId -> Maybe Actor -> ItemSlots
           -> SlotChar -> State
           -> SlotChar
assignSlot store item fid mbody (ItemSlots itemSlots organSlots) lastSlot s =
  assert (maybe True (\b -> bfid b == fid) mbody)
  $ if jsymbol item == '$'
    then SlotChar 0 '$'
    else head $ fresh ++ free
 where
  offset = maybe 0 (+1) (elemIndex lastSlot allZeroSlots)
  onlyOrgans = store == COrgan
  len0 = length allZeroSlots
  candidatesZero = take len0 $ drop offset $ cycle allZeroSlots
  candidates = candidatesZero ++ concat [allSlots n | n <- [1..]]
  onPerson = sharedAllOwnedFid onlyOrgans fid s
  onGround = maybe EM.empty  -- consider floor only under the acting actor
                   (\b -> getFloorBag (blid b) (bpos b) s)
                   mbody
  inBags = ES.unions $ map EM.keysSet $ onPerson : [ onGround | not onlyOrgans]
  lSlots = if onlyOrgans  then organSlots else itemSlots
  f l = maybe True (`ES.notMember` inBags) $ EM.lookup l lSlots
  free = filter f candidates
  g l = l `EM.notMember` lSlots
  fresh = filter g $ take ((slotPrefix lastSlot + 1) * len0) candidates

slotLabel :: SlotChar -> Text
slotLabel x =
  T.snoc (if slotPrefix x == 0 then T.empty else tshow $ slotPrefix x)
         (slotChar x)
  <> ")"