/***************************************************************************
 * Keyring [C] 2002 Petr [Dingo] Dvorak <dingo@texoma.net>                 *
 *                                                                         *
 * Written for Anime Planet MUD [animeplanet.genesismuds.com:3333]         *
 ***************************************************************************/

#include <stdio.h>
#include <string.h>
#include "merc.h"

// some state definitions so we can handle all the states with one function
// instead repeating the same stuff over and over.
#define KR_OK			0
#define KR_NO_KEYRING		1
#define KR_NO_KEY		2
#define KR_NO_REMKEY		3
#define KR_INVALID_KEYRING	4
#define KR_INVALID_KEY		5
#define KR_INVALID_NAME		6
#define KR_KEY_DUP		7

DECLARE_DO_FUN( do_examine );

/*
 This snippet adds keyring command and ability into the mud, the syntax is 
 simple:
 
 keyring 		- examine the keyring and lists all keys.
 keyring add <key>	- add key to the keyring.
 keyring remove <key>	- remove key from keyring.

 when player adds a key on keyring, the physical key is extracted, when
 player removes key from keyring, the physical key is created and inserted
 in their inventory. There is not that much to this snippet so write your
 own help for it.
 
 I didn't want to add another object type just for something simple like
 this, so the code looks for name prefix 'keyring' in the pObjIndex->name,
 this way the keyrings can still have restring name and work like
 keyrings. You need to make sure that there is no objects with 'keyring'
 in name that you don't want the players to use as keyrings. simple
 'vnum keyring' on the mud should suffice.

 just drop the stuff below into the proper files, places and add keyring.o
 to O_FILES in Makefile.
 
 If you look over the code (like you should of course), you will see i
 like structured calls alot, it keeps the main functions at manageable
 size and somewhat better readable.
 
 there is no warranty of any kind, not even that this will work of course.
 if something breaks, you get to keep both pieces :)
 
 I'm not very big on comments in the code, so this is about as good as it
 gets, if you need more info on what does what just email me.

*/
//--[ merc.h ]--------------------------------------------------------------------
    
typedef struct  key_data		KEY_DATA;	// keyring

struct key_data
{
    KEY_DATA *		next;
    bool		valid;
    OBJ_INDEX_DATA *	index;
};

// keyring.c

KEY_DATA * new_key		args( (void				   ) );
OBJ_DATA * find_keyring		args( (CHAR_DATA * ch 			   ) );
int 	   remove_key		args( (CHAR_DATA * ch, OBJ_DATA * keyring, char * name ) );
int  	   add_key		args( (OBJ_DATA * keyring, OBJ_DATA * key  ) );
void	   free_key		args( (KEY_DATA * key			   ) );
void	   list_keys		args( (CHAR_DATA * ch, OBJ_DATA * keyring  ) );
void 	   load_keyring		args( (FILE * fp, OBJ_DATA * obj	   ) );
void 	   save_keyring		args( (FILE * fp, OBJ_DATA * obj	   ) );

// add in obj_data structure:

    KEY_DATA *		keys;		// keyring

//--[ interp.h ]------------------------------------------------------------------

DECLARE_DO_FUN( do_keyring	);

//--[ interp.c ]------------------------------------------------------------------

//add the hook in command table .. just below "kill"

    { "keyring",	do_keyring,	POS_FIGHTING,	 1,  LOG_NORMAL },

//--[ act_move.c ]----------------------------------------------------------------

//replace the has_key in act_move.c with this one:

bool has_key( CHAR_DATA *ch, int key )
{
    OBJ_DATA *obj;
    KEY_DATA *keys;

    for ( obj = ch->carrying; obj != NULL; obj = obj->next_content )
    {
	if ( obj->pIndexData->vnum == key )
	    return TRUE;
    }

    // look for the keyring in players inventory    
    obj = find_keyring(ch);
    
    // no keyring found ?
    if (!obj) return FALSE;

    // loop thru the keys on keyring and see if there is a matching key
    for (keys = obj->keys; keys != NULL; keys = keys->next)
    {
	if ( keys->index->vnum == key )
	{
	    send_to_char("After some searching of your keyring, you found a key that seems to fit the lock.\n\r", ch);
	    return TRUE;
	}
    }

    // no keys found
    return FALSE;
}

//--[ save.c ]--------------------------------------------------------------------

// add this in fwrite_obj:

    if ( obj->keys )
	save_keyring( fp, obj);

// and this in fread_obj in the switch loop:

	case 'K':
	    if ( !str_cmp( word, "Keyring" ) )
	    {
		// load list of key vnums from pfile
		load_keyring(fp, obj)
		fMatch = TRUE;
	    }
	    break;

//--[ recycle.c ]-----------------------------------------------------------------

// add the following in apropriate places in free_obj:

// on the begining:
 
    KEY_DATA	* key, * key_next;

// somewhere on the end:

    if (obj->keys)
    {
	// loop thru the keys and free the allocated memory
	for ( key = obj->keys; key != NULL; key = key_next)
	{
	    key_next = key->next;
	    free_key(key);
	}
    }

//--------------------------------------------------------------------------------


// allocates memory for new key on the keyring
KEY_DATA * new_key( void )
{
    KEY_DATA * key;
    
    // allocate memory for the key, calloc 0's the allocated memory so we don't
    // have to initialize the variables.
    key = calloc(1,sizeof(*key));
    VALIDATE(key);

    return key;
}

// frees memory allocated by new_key
void free_key( KEY_DATA * key )
{
    // usual sanity checks
    if (!key || !IS_VALID(key))	
	return;

    // clean up, makes debugging easier :)
    key->next  = NULL;
    key->index = NULL;	// this can NEVER be free'd here !!!
    INVALIDATE(key);

    // free the memory
    free(key);

    return;
}

// adds key to the keyring
int add_key( OBJ_DATA * keyring, OBJ_DATA * key)
{
    KEY_DATA * nkey;

    // the usual sanity checks
    if (!keyring)		return KR_NO_KEYRING;
    if (!key)			return KR_NO_KEY;
    if (!IS_VALID(keyring))	return KR_INVALID_KEYRING;
    if (!IS_VALID(key))		return KR_INVALID_KEY;
    
    // loop thru the keyring and make sure that the key is not already on it
    for ( nkey = keyring->keys; nkey != NULL; nkey = nkey->next )
	if (nkey->index == key->pIndexData)
	    return KR_KEY_DUP;
    
    nkey = new_key();

    // fill in the data
    nkey->index = key->pIndexData;

    // add the key to the keyring
    nkey->next = keyring->keys;
    keyring->keys = nkey;

    // get rid of the 'real' key
    obj_from_char(key);
    extract_obj(key);

    return KR_OK;
}

// removes key from the keyring
int remove_key( CHAR_DATA * ch, OBJ_DATA * keyring, char * name )
{
    KEY_DATA	* key;
    KEY_DATA	* key_next;

    // usual sanity check
    if (!keyring)		return KR_NO_KEYRING;
    if (!keyring->keys)		return KR_NO_KEY;
    if (!name)			return KR_INVALID_NAME;
    if (name[0] == '\0')	return KR_INVALID_NAME;

    // let's find the key
    for ( key = keyring->keys; key != NULL; key = key_next )
    {
	key_next = key->next;

	// check the name
	if (key && key->index->name && !str_prefix(name, key->index->name))
	{
	    OBJ_DATA *phys_key;
	    
	    // create physical key and put it in players inventory
	    phys_key = create_object(key->index,key->index->level);
	    obj_to_char(phys_key, ch);
	
	    // if the key is first on the ring, its easy to drop it    
	    if ( keyring->keys == key )	keyring->keys = key->next;
	    else // we gotta find the previous key
	    {
		KEY_DATA *prev;

		for ( prev = keyring->keys; prev != NULL; prev = prev->next )
		{
		    if ( prev->next == key )
		    {
			// drop the key from the ring;
			prev->next = key->next;
			break;
		    }
		}
	    }
	    // free the key structure
	    free_key(key);
	    
	    // time to bail.
	    return KR_OK;
	}
    }
    // hmm, we are still around ? key was not found.	
    return KR_NO_REMKEY;
}

void save_keyring( FILE * fp, OBJ_DATA * obj)
{
    KEY_DATA *  keys;

    // the usual sanity check
    if (!obj || !obj->keys)
	return;
	
    fprintf(fp, "Keyring  ");
    
    // loop thru the keys on keyring and save the vnums
    for (keys = obj->keys; keys != NULL; keys = keys->next)
    {
	if ( keys->index->vnum )
	    fprintf(fp, "%d ", keys->index->vnum);
    }
    
    // save -1 on the end of the list to terminate it
    fprintf(fp,"-1\n\r");

    return;
}

// load keys from the pfile
void load_keyring( FILE * fp, OBJ_DATA * obj)
{
    int 	vnum = 0;
    KEY_DATA *  nkey;

    do
    {
	// read one vnum from the pfile
	vnum = FREAD_NUMBER(fp);
	if (vnum > 0)
	{
	    // create new key
	    nkey = new_key();
	    
	    // fill in the blanks
	    nkey->index = get_obj_index(vnum);
	    nkey->next = obj->keys;
	    obj->keys = nkey;
	}
    } 
    while (vnum > 0);

    return;
}

// lists the keys on the keyring
void list_keys( CHAR_DATA * ch, OBJ_DATA * keyring )
{
    KEY_DATA	* key;
    char 	  buf[MSL];
    
    // no keys on the keyring
    if ( !keyring->keys )
    {
	send_to_char("The keyring is empty.\n\r",ch);
	return;
    }

    // list the keys
    send_to_char("\n\rThe keyring contains the following keys:\n\r\n\r",ch);
    
    for ( key = keyring->keys; key != NULL; key = key->next)
    {
	buf[0] = '\0';
	
	// imms can see vnums of the keys in the list.
	if (IS_IMMORTAL(ch)) sprintf(buf,"{D[{C%5.5d{D]{x",key->index->vnum);

	// send out the formatted line
	printf_to_char(ch,"    %s %s from %s.\n\r", 
	    buf,
	    key->index->short_descr ? key->index->short_descr : "Unknown",
	    key->index->area->name ? key->index->area->name : "Unknown place"
	    );
	       
    }

    return;
}

// searches players inventory for keyring
OBJ_DATA * find_keyring( CHAR_DATA * ch )
{
    OBJ_DATA *obj;

    // search players inventory for keyring object
    for ( obj = ch->carrying; obj != NULL; obj = obj->next_content )
    {
	if ( (can_see_obj( ch, obj ) ) 
	&&   !str_prefix( "keyring", obj->name ) )
		return obj;
    }
    return NULL;
}

void keyring_message(int message, CHAR_DATA * ch, OBJ_DATA * key)
{
    switch (message)
    {
	case KR_OK:			// everything ok .. what are we doing here ?
	    return;
	    break;
	case KR_NO_KEYRING:		// no keyring
	    send_to_char("You don't have a keyring, go buy one.\n\r",ch);
	    break;
	case KR_NO_KEY:			// no key
	    send_to_char("You don't have that key.\n\r",ch);
	    break;
	case KR_NO_REMKEY:		// no key on keyring
	    send_to_char("You don't have that key on your keyring.\n\r",ch);
	    break;
	case KR_KEY_DUP:
	    printf_to_char(ch, "You already have %s on your keyring.\n\r",key->short_descr);
	    break;
	case KR_INVALID_NAME:
	case KR_INVALID_KEYRING:
	case KR_INVALID_KEY:
	default:
	    send_to_char("Something gone horribly wrong, please tell the nearest immortal.\n\r",ch);
	    // add your own debugging code here
	    break;
    }

    return;
}

// the command function
void do_keyring( CHAR_DATA * ch, char *argument )
{
    OBJ_DATA *  keyring = NULL;
    OBJ_DATA *  key = NULL;
    char 	arg1[MIL];
    char 	arg2[MIL];
    int		message = KR_OK;

    keyring = find_keyring(ch);

    if (!keyring)
    {
	keyring_message(KR_NO_KEYRING,ch,NULL);
	return;
    }

    // no arguments ? just show list of the keys on the keyring then.
    if (argument[0] == '\0')
    {
	// yes i know, this is really filthy way to do this, but this way it 
	// will show the same stuff like if people examine normal object
	// every mud is little different :)
	do_examine(ch, keyring->name);
	return;
    }

    argument = one_argument( argument, arg1 );	// command - add | remove
    argument = one_argument( argument, arg2 );  // key name

    if (arg2[0] == '\0' )
    {
	send_to_char("You must specify the name of the key you wish to add or remove.\n\r", ch);
	return;
    }

    if (is_name( arg1, "add") )
    {
	key = get_obj_carry( ch, arg2, ch );

        if (!key)
	{
	    send_to_char("You don't have that key.\n\r", ch);
	    return;
        }
	
	if (key->item_type != ITEM_KEY)
	{
	    send_to_char("That is not a key!\n\r", ch);
	    return;
	}

	message = add_key(keyring, key);

	if (!message)
	{
	    act("$n adds $p on $s keyring.", ch, key, NULL, TO_ROOM, POS_RESTING);
	    act("You added $p on your keyring.", ch, key, NULL, TO_CHAR, POS_RESTING);
	} else
	{
	    keyring_message(message,ch,key);
	}
    }
    else if (is_name( arg1, "remove" ) )
    {
	message = remove_key(ch, keyring, arg2);
	if (!message)
	{
	    act("$n removes a key from $s keyring.", ch, NULL, NULL, TO_ROOM, POS_RESTING);
	    act("You removed a key from your keyring.", ch, NULL, NULL, TO_CHAR, POS_RESTING);
	} else
	{
	    keyring_message(message,ch,NULL);
	}
    }
    else
    {
	send_to_char("What exactly do you want to do with the keyring ?\n\r", ch);
	return;
    }
    
    return;
}
