'''
consumable.py

Consumable item type. By default, implements edible and drinkable. Allows
players to add new consumable types, and subtypes (liquor, water, juice, ...?).
Comes with OLC object interface.

This module has been designed to allow for easy addition of a potion/pill
system. When an item is consumed, consume hooks are run (in addition to eat
and drink hooks). Magical affects can piggyback on to this, in addition to
other special things like extracting depleted flasks.
'''
import olc, mudsys, auxiliary, mudsock, storage, mud, utils, hooks



################################################################################
# item type definition
################################################################################

# a map from our type to possible subtypes.
# Also the default subtype when we create an item of the specified type.
ctype_map  = { }
ctype_dflt = { }

class ConsumableData:
    '''Data for Consumables. Contains a type (food, drink), a subtype (liquor,
       water, juice), a maximum number of consumptions, and a current number
       of consumptions.'''

    # this line is needed for registering the item type, and using OLC
    __item_type__ = "consumable"

    def __init__(self, set = None):
        # set our default values
        self.type         = "edible"
        self.subtype      = get_consume_dflt(self.type)
        self.max_consumes = 1
        self.consumes     = 1

        # do we have any other values to read in?
        if set != None:
            self.type    = set.readString("type")
            self.subtype = get_consume_dflt(self.type)
            if set.contains("subtype"):
                self.subtype = set.readString("subtype")
            if set.contains("max_consumes"):
                self.max_consumes = set.readInt("max_consumes")
            if set.contains("consumes"):
                self.consumes = set.readInt("consumes")

    def copyTo(self, to):
        to.type         = self.type
        to.subtype      = self.subtype
        to.max_consumes = self.max_consumes
        to.consumes     = self.consumes

    def copy(self):
        newdata = ConsumableData()
        self.copyTo(newdata)
        return newdata

    def store(self):
        set = storage.StorageSet()
        set.storeString("type", self.type)
        if self.subtype != get_consume_dflt(self.type):
            set.storeString("subtype", self.subtype)
        if self.max_consumes != 1:
            set.storeInt("max_consumes", self.max_consumes)
        if self.consumes != 1:
            set.storeInt("consumes", self.consumes)
        return set

def __obj_isConsumeType__(obj, type):
    '''Return whether the object is consumable, and of the specified type
       of consumable.'''
    if not obj.istype("consumable"):
        return False
    if obj.get_type_data("consumable").type != type:
        return False
    return True



################################################################################
# creating and interacting with consumable types
################################################################################
def register_consume_type(type, dflt_subtype = "unknown"):
    '''create a new consumable type, and add a default subtype'''
    subtypes = [dflt_subtype]
    ctype_map[type]  = subtypes
    ctype_dflt[type] = dflt_subtype

def register_consume_subtype(type, subtype):
    '''create a new subtype for the specified consumable type'''
    if ctype_map.has_key(type):
        subtype  = subtype.lower()
        subtypes = ctype_map[type]
        if not subtype in subtypes:
            subtypes.append(subtype)

def get_consume_dflt(type):
    '''get the default subtype for a consumable'''
    if ctype_dflt.has_key(type):
        return ctype_dflt[type]
    return None

def get_consume_types():
    '''list all consumable types'''
    return ctype_map.keys()

def get_consume_subtypes(type):
    '''list all subtypes for a consumable'''
    if ctype_map.has_key(type):
        return [t for t in ctype_map[type]]
    return None

def is_consume_type(type):
    '''return whether type is a registered consumable type'''
    return ctype_map.has_key(type)

def is_consume_subtype(type, subtype):
    '''return whether subtype is a registered subtype for type'''
    if ctype_map.has_key(type):
        return subtype in ctype_map[type]
    return False
    


################################################################################
# Consumable OLC
################################################################################
__CONSOLC_TYPE__     = 1
__CONSOLC_SUBTYPE__  = 2
__CONSOLC_MAX_USES__ = 3
__CONSOLC_USES__     = 4

consolc_opt_map = {
    '1' : (__CONSOLC_TYPE__,     "Choose a type: "),
    '2' : (__CONSOLC_SUBTYPE__,  "Choose a subtype: "),
    '3' : (__CONSOLC_MAX_USES__, "Enter max number of uses: "),
    '4' : (__CONSOLC_USES__,     "Enter default number of uses: ")
    }

def consolc_menu(sock, data):
    '''display our menu interface'''
    sock.send("{g1) Type        : {c" + data.type    + "\n" + \
              "{g2) Subtype:    : {c" + data.subtype + "\n" + \
              "{g3) Max uses    : {c" + str(data.max_consumes) + "\n" + \
              "{g4) Default uses: {c" + str(data.consumes))

def consolc_chooser(sock, data, option):
    '''figure out what field we want to edit, and display a prompt'''
    if not consolc_opt_map.has_key(option):
        return olc.MENU_CHOICE_INVALID
    ret, mssg = consolc_opt_map[option]

    # list our type and subtype options
    opts = None
    if ret == __CONSOLC_TYPE__:
        opts = get_consume_types()
    elif ret == __CONSOLC_SUBTYPE__:
        opts = get_consume_subtypes(data.type)

    # do we have to list subtype options?
    if opts != None:
        utils.olc_display_table(sock, opts, 4)
    sock.send_raw(mssg)
    return ret

def consolc_parser(sock, data, choice, arg):
    '''parse out what we want to change a specified field to'''
    try:
        arg = int(arg)
    except: return False
    
    if choice == __CONSOLC_TYPE__:
        opts = get_consume_types()
        arg  = int(arg)
        if arg < 0 or arg >= len(opts):
            return False

        newtype = opts[arg]
        if newtype != data.type:
            data.consumes = 1
            data.max_consumes = 1
            data.subtype = get_consume_dflt(newtype)
            data.type = newtype

    elif choice == __CONSOLC_SUBTYPE__:
        opts = get_consume_subtypes(data.type)
        arg  = int(arg)
        if arg < 0 or arg >= len(opts):
            return False
        data.subtype = opts[arg]

    elif choice == __CONSOLC_MAX_USES__:
        data.max_consumes = max(int(arg), 1)
    elif choice == __CONSOLC_USES__:
        data.consumes     = max(int(arg), 0)
        data.max_consumes = max(data.max_consumes, data.consumes)
    else:
        return False
    return True

def consolc_to_proto(data):
    '''return a script that will generate relevant info to create this item'''
    buf  = 'me.get_type_data("consumable").type = "' + data.type + '"\n'
    buf += 'me.get_type_data("consumable").subtype = "' + data.subtype + '"\n'
    if data.max_consumes != 1:
        buf += 'me.get_type_data("consumable").max_consumes = ' + \
               str(data.max_consumes)+'\n'
    if data.consumes != 1:
        buf += 'me.get_type_data("consumable").consumes = ' + \
               str(data.consumes)+'\n'
    return buf



################################################################################
# game hooks
################################################################################
def consumable_desc_info(info):
    '''appends consumable information about an object to the description
       buffer when a person looks at it.'''
    obj, ch = hooks.parse_info(info)

    # no point in continuing
    if not obj.istype("consumable"):
        return

    # get our consumable info, and make a description
    data = obj.get_type_data("consumable")

    # what word do we describe its state 
    usable, unusable = "is", "finished"
    try:
        usable, unusable = { "drinkable" : ("contains", "empty"),
                             "edible"    : ("is",       "finished")
                             }[data.type]
    except: pass

    # is it usable or unusable?
    if data.consumes == 0:
        desc = " It is " + unusable + "."
    else:
        desc = (" It %s %s %s and can be consumed %d more times." %
                (usable, data.type, data.subtype, data.consumes))

    # append our description
    ch.look_buf += desc



################################################################################
# game commands
################################################################################
def cmd_eat(ch, cmd, arg):
    '''Allows players to eat food they are carrying.'''
    try:
        obj, = mud.parse_args(ch, True, cmd, arg, "[the] obj.inv")
    except: return

    if not obj.isConsumeType("edible"):
        mud.message(ch, None, obj, None, True, "to_char", "$o is not edible!")
    else:
        # take a bite and send our message
        data = obj.get_type_data("consumable")
        if data.max_consumes > 0:
            data.consumes -= 1
        mud.message(ch, None, obj, None, True, "to_char",
                    "You take a bite out of $o.")

        # do we want to make edible effects? Here's where to hook onto
        hooks.run("consume", hooks.build_info("ch obj", (ch, obj)))
        hooks.run("eat",     hooks.build_info("ch obj", (ch, obj)))

        # have we eaten the entire object?
        if data.max_consumes > 0 and data.consumes <= 0:
            mud.message(ch, None, obj, None, True, "to_char",
                        "You finish eating $o.")
            mud.extract(obj)

def cmd_drink(ch, cmd, arg):
    '''Allows a player to sip a drinkable object they are carrying, or
       are around.'''
    try:
        obj, = mud.parse_args(ch, True, cmd, arg, "[from] obj.room.inv")
    except: return

    if not obj.isConsumeType("drinkable"):
        mud.message(ch, None, obj, None, True, "to_char",
                    "You cannot drink from $o!")
    elif obj.get_type_data("consumable").consumes <= 0:
        mud.message(ch, None, obj, None, True, "to_char",
                    "You cannot drink from $o, it is empty!")
    else:
        # take a sip and send our message
        data = obj.get_type_data("consumable")
        data.consumes -= 1
        mud.message(ch, None, obj, None, True, "to_char",
                    "You drink %s from $o." % data.subtype)

        # Do we want to make potions? Here's where to hook on to
        hooks.run("consume", hooks.build_info("ch obj", (ch, obj)))
        hooks.run("drink",   hooks.build_info("ch obj", (ch, obj)))

def cmd_fill(ch, cmd, arg):
    '''Attempt to fill one drinkable source with liquid from another.'''
    try:
        dest, src = mud.parse_args(ch, True, cmd, arg,
                                   "obj.inv [from] obj.room.inv")
    except: return

    # make sure we can drink from both, and they are different items
    if dest == src:
        mud.message(ch, None, None, None, True, "to_char",
                    "That would not accomplish much!")
    elif not dest.isConsumeType("drinkable"):
        mud.message(ch, None, dest, None, True, "to_char",
                    "$o cannot contain liquids.")
    elif (not src.isConsumeType("drinkable") or
          src.get_type_data("consumable").consumes == 0):
        mud.message(ch, None, dest, None, True, "to_char",
                    "$o does not contain liquids.")
    elif (dest.get_type_data("consumable").consumes > 0 and
          dest.get_type_data("consumable").subtype !=
          src.get_type_data("consumable").subtype):
        mud.message(ch, None, dest, src, True, "to_char",
                    "$o and $O contain different liquids; you must empty $o first.")
    elif (dest.get_type_data("consumable").max_consumes ==
          dest.get_type_data("consumable").consumes):
        mud.message(ch, None, dest, None, True, "to_char",
                    "$o is as full as it will ever be.")

    # ok, we're good! Let's fill 'er up
    else:
        mud.message(ch, None, dest, src, True, "to_char",
                    "You fill $o from $O.")
        mud.message(ch, None, dest, src, True, "to_room",
                    "$n fills $o from $O.")

        # get our data for a little easier working
        sdata = src.get_type_data("consumable")
        ddata = dest.get_type_data("consumable")

        # figure out what our limit is, and change our type if neccessary
        amnt = min((ddata.max_consumes - ddata.consumes), sdata.consumes)
        ddata.consumes += amnt
        sdata.consumes -= amnt
        ddata.subtype   = sdata.subtype

        # run our hooks
        hooks.run("fill", hooks.build_info("ch obj obj", (ch, dest, src)))

def cmd_empty(ch, cmd, arg):
    '''Empty out the liquids in a drink consumable.'''
    try:
        obj, = mud.parse_args(ch, True, cmd, arg, "obj.inv")
    except: return

    if not obj.isConsumeType("drinkable"):
        mud.message(ch, None, obj, None, True, "to_char",
                    "You can only empty drinking containers.")
    elif obj.get_type_data("consumable").consumes == 0:
        mud.message(ch, None, obj, None, True, "to_char",
                    "$o is already empty.")
    else:
        data = obj.get_type_data("consumable")
        mud.message(ch, None, obj, None, True, "to_char",
                    "You empty out the " + data.subtype + " in $o.")
        data.consumes = 0

        # run our hooks
        hooks.run("empty", hooks.build_info("ch obj", (ch, obj)))



################################################################################
# initialization
################################################################################

# initialize our item data, and its OLC
mudsys.item_add_type(ConsumableData.__item_type__, ConsumableData)
olc.item_add_olc(ConsumableData.__item_type__,
                 consolc_menu, consolc_chooser, consolc_parser,
                 None, consolc_to_proto)

# add methods to Objs
mudsys.add_obj_method("isConsumeType",  __obj_isConsumeType__)

# add commands
mudsys.add_cmd("eat",   None, cmd_eat,   "player", True)
mudsys.add_cmd("drink", None, cmd_drink, "player", True)
mudsys.add_cmd("fill",  None, cmd_fill,  "player", True)
mudsys.add_cmd("empty", None, cmd_empty, "player", True)

# display hooks for food and drink
hooks.add("append_obj_desc", consumable_desc_info)

# initialize some default consumable types and subtypes
register_consume_type("edible",    "bread")
register_consume_subtype("edible", "potato")
register_consume_subtype("edible", "steak")

register_consume_type("drinkable",    "water")
register_consume_subtype("drinkable", "liquor")
register_consume_subtype("drinkable", "wine")
register_consume_subtype("drinkable", "beer")