/*
    Daimonin, the Massive Multiuser Online Role Playing Game
    Server Applicatiom

    Copyright (C) 2001 Michael Toennies

    A split from Crossfire, a Multiplayer game for X-windows.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    The author can be reached via e-mail to info@daimonin.org
*/

/*
 * This file contains the utility functions used for parsing, creating and
 * maintaining the behaviour sets of mobs
 */

#include <global.h>

#include <aiconfig.h>
#include <ctype.h>

/* TODO: we can speed up searches somewhat by using hashtables here
 * (no a major speedup, since we currently do pointer comparision on a
 * smallish list) */
/* List of behavioursets generated by generate_behaviourset() */
static struct mob_behaviourset *generated_behavioursets = NULL;
/* List of behavioursets parsed by parse_behaviourconfig() */
static struct mob_behaviourset *parsed_behavioursets    = NULL;

static int check_behaviour_parameters(object_t *op, struct mob_behaviour *behaviour);
static int parse_behaviour_parameters(object_t *op, const char *start, const char *end, struct mob_behaviour *behaviour);

/*
 * Memory management functions
 */

/** Dereference and free a behaviourset */
static void free_behaviourset(struct mob_behaviourset *set)
{
    if(set)
    {
        set->refcount--;
        if (set->refcount == 0)
            return_poolchunk(set, pool_mob_behaviourset);
    }
}

/* Destructors for the mm system */
void cleanup_mob_known_obj(struct mob_known_obj *data)
{
    SHSTR_FREE(data->last_map);
}

static inline void cleanup_behaviour_parameters(struct mob_behaviour *behaviour)
{
    unsigned int                i;
    struct mob_behaviour_param *param;

    if (behaviour->parameters)
    {
        for (i = 0; i < behaviour->declaration->nrof_params; i++)
        {
            /* Return any multiply defined parameters */
            for (param = behaviour->parameters[i].next; param; param = param->next)
            {
                SHSTR_FREE(param->stringvalue);
                return_poolchunk(param, pool_mob_behaviourparam);
            }

            SHSTR_FREE(behaviour->parameters[i].stringvalue);
        }
        /* Return the base parameter array */
        return_poolarray(behaviour->parameters, behaviour->declaration->nrof_params, pool_mob_behaviourparam);
    }
}

void cleanup_behaviourset(struct mob_behaviourset *set)
{
    int                     i;
    struct mob_behaviour   *tmp;

    /* Remove from list */
    if (set->next)
        set->next->prev = set->prev;
    if (set->prev)
        set->prev->next = set->next;
    if (set->definition)
    {
        /* Parsed */
        //            LOG(llevDebug, "Parsed behaviourset with refcount==0 being freed...\n");
        if (set == parsed_behavioursets)
            parsed_behavioursets = set->next;
        SHSTR_FREE(set->definition);
    }
    else
    {
        /* Generated */
        //            LOG(llevDebug, "Generated behaviourset with refcount==0 being freed...\n");
        if (set == generated_behavioursets)
            generated_behavioursets = set->next;
    }

    for (i = 0; i < NROF_BEHAVIOURCLASSES; i++)
    {
        for (tmp = set->behaviours[i]; tmp; tmp = tmp->next)
        {
            cleanup_behaviour_parameters(tmp);
            return_poolchunk(tmp, pool_mob_behaviour);
        }
    }
}

void cleanup_mob_data(struct mobdata *data)
{
    struct mob_known_obj       *tmp;

    PATHFINDER_FREE_PATH(data->pathfinding.path);
    SHSTR_FREE(data->pathfinding.goal_map);
    SHSTR_FREE(data->pathfinding.target_map);

    for (tmp = data->known_mobs; tmp; tmp = tmp->next)
        return_poolchunk(tmp, pool_mob_knownobj);
    for (tmp = data->known_objs; tmp; tmp = tmp->next)
        return_poolchunk(tmp, pool_mob_knownobj);
    if(data->known_objs_ht)
        hashtable_delete(data->known_objs_ht);

    free_behaviourset(data->behaviours);
}

/* Initializator for the mm system */
void initialize_mob_data(struct mobdata *data)
{
    data->pathfinding.target_obj = NULL;
    data->pathfinding.target_map = NULL;
    data->pathfinding.flags[0] = 0;
    data->pathfinding.path = NULL;
    data->pathfinding.goal_map = NULL;
    data->pathfinding.best_distance = -1;
    data->pathfinding.tried_steps = 0;

    data->known_mobs = NULL;
    data->known_objs = NULL;
    data->known_objs_ht = NULL;

    data->owner = NULL;
    data->enemy = NULL;

    data->behaviours = NULL;

    data->spawn_info = NULL;
    data->idle_time = 0;
    data->move_speed_factor = 2; /* Normal movement speed */

    data->antiluring_timer = 0;

    /* Intitialize this to something valid so we don't have to worry about it */
    data->last_movement_behaviour = &behaviourclasses[BEHAVIOURCLASS_MOVES].behaviours[AIBEHAVIOUR_FRIENDSHIP];
}

/*
 *  General cleanup function
 */
void cleanup_all_behavioursets()
{
    struct mob_behaviourset *set;
    LOG(llevDebug, "Freeing all behaviour sets\n");
    for(set = generated_behavioursets; set; set = set->next)
        return_poolchunk(set, pool_mob_behaviourset);
    for(set = parsed_behavioursets; set; set = set->next)
        return_poolchunk(set, pool_mob_behaviourset);
}

/*
 * Behaviourset definition util functions
 */

/* Generate a 32 bit unique hash from the data we use to generate
 * behavioursets */
uint32 bghash(object_t *op)
{
    uint32  hash    = 0;
    if (QUERY_FLAG(op, FLAG_NO_ATTACK))
        hash |= 1;
    if (QUERY_FLAG(op, FLAG_STAND_STILL))
        hash |= 2;
    if(find_waypoint(op, NULL))
        hash |= 4;
    if (QUERY_FLAG(op, FLAG_RANDOM_MOVE))
        hash |= 8;
    if (QUERY_FLAG(op, FLAG_READY_SPELL))
        hash |= 16;
    if (QUERY_FLAG(op, FLAG_READY_BOW))
        hash |= 32;

    hash <<= 16; /* can reduce to about 5 bits each */
    hash |= ((op->item_race & 0xff) << 8) | ((op->item_level & 0xff));

    hash <<= 8; /* Can reduce to 7 bits at least */
    hash |= (op->run_away & 0xff);

    return hash;
}

/* Set up pointers to default ai definitions in the archetypes */
void init_arch_default_behaviours()
{
    archetype_t  *at;

    LOG(llevDebug, "Init arch default AI: ");
    for (at = first_archetype; at != NULL; at = (at->more == NULL) ? at->next : at->more)
        if (at->clone.type == TYPE_AI && at->clone.msg && at->clone.other_arch)
        {
            LOG(llevDebug, "%s->'%s' ", STRING_OBJ_NAME(&at->clone.other_arch->clone), STRING_OBJ_NAME(&at->clone));
            at->clone.other_arch->ai = parse_behaviourconfig(at->clone.msg, &at->clone);
        }
    LOG(llevDebug, " Done\n");
}

/* Set up the structs for a behaviour and its parameters */
struct mob_behaviour * init_behaviour(behaviourclass_t classid, int behaviourid)
{
    struct mob_behaviour   *behaviour;

    behaviour = get_poolchunk(pool_mob_behaviour);
    behaviour->declaration = &behaviourclasses[classid].behaviours[behaviourid];
    behaviour->next = NULL;
    if (behaviour->declaration->nrof_params > 0)
    {
        unsigned int i;
        behaviour->parameters = get_poolarray(pool_mob_behaviourparam, behaviour->declaration->nrof_params);
        for (i = 0; i < behaviour->declaration->nrof_params; i++)
        {
            behaviour->parameters[i].next = NULL;
            behaviour->parameters[i].stringvalue = NULL;
            behaviour->parameters[i].intvalue = -1;
            behaviour->parameters[i].flags = 0;
        }
    }
    else
    {
        behaviour->parameters = NULL;
    }

    return behaviour;
}

/* Backwards-compability function that creates a behaviourset
 * from old-style mob parameters (attributes of mob object) */
struct mob_behaviourset * generate_behaviourset(object_t *op)
{
    struct mob_behaviourset    *set;
    struct mob_behaviour       *last;
    uint32                      hash    = bghash(op);
    int                         i;

    /* First try to find an already generated behaviour with identical
     * parameters */
    for (set = generated_behavioursets; set; set = set->next)
        if (set->bghash == hash)
        {
            //            LOG(llevDebug,"Found existing generated behaviourset for '%s'. refcount = %d\n", STRING_OBJ_NAME(op), set->refcount);
            set->refcount++;
            return set;
        }

    /* Otherwise generate a new behaviourset */
    //    LOG(llevDebug,"Generating behaviourset for %s\n", STRING_OBJ_NAME(op));
    set = get_poolchunk(pool_mob_behaviourset);
    for (i = 0; i < NROF_BEHAVIOURCLASSES; i++)
        set->behaviours[i] = NULL;
    set->refcount = 1;
    set->definition = NULL;
    set->bghash = hash;
    set->next = set->prev = NULL;
    set->attitudes = NULL; /* By keeping this NULL we can fake default
                              values in ai_attitudes() and save us some
                              time for the vast majority of objects */
    set->groups = NULL;
    set->attractions = NULL;

    /* Insert in list */
    set->next = generated_behavioursets;
    if (set->next)
        set->next->prev = set;
    generated_behavioursets = set;

    /* Processes */
    last = set->behaviours[BEHAVIOURCLASS_PROCESSES] = init_behaviour(BEHAVIOURCLASS_PROCESSES, AIBEHAVIOUR_LOOK_FOR_OTHER_MOBS);
    /* We initialize this for completeness, even though we use default values */
    last = last->next = init_behaviour(BEHAVIOURCLASS_PROCESSES, AIBEHAVIOUR_FRIENDSHIP);
    last->parameters[AIPARAM_FRIENDSHIP_SAME_ALIGNMENT].intvalue = (long)behaviourclasses[BEHAVIOURCLASS_PROCESSES].behaviours[AIBEHAVIOUR_FRIENDSHIP].params[AIPARAM_FRIENDSHIP_SAME_ALIGNMENT].defaultvalue;
    last->parameters[AIPARAM_FRIENDSHIP_SAME_ALIGNMENT].flags |= AI_PARAM_PRESENT;
    last->parameters[AIPARAM_FRIENDSHIP_OPPOSITE_ALIGNMENT].intvalue = (long)behaviourclasses[BEHAVIOURCLASS_PROCESSES].behaviours[AIBEHAVIOUR_FRIENDSHIP].params[AIPARAM_FRIENDSHIP_OPPOSITE_ALIGNMENT].defaultvalue;
    last->parameters[AIPARAM_FRIENDSHIP_OPPOSITE_ALIGNMENT].flags |= AI_PARAM_PRESENT;
    check_behaviour_parameters(op, last);

    last = last->next = init_behaviour(BEHAVIOURCLASS_PROCESSES, AIBEHAVIOUR_ATTRACTION);
    check_behaviour_parameters(op, last);
    if (!QUERY_FLAG(op, FLAG_NO_ATTACK))
    {
        last = last->next = init_behaviour(BEHAVIOURCLASS_PROCESSES, AIBEHAVIOUR_CHOOSE_ENEMY);
        last->parameters[AIPARAM_CHOOSE_ENEMY_ANTILURE_DISTANCE].intvalue = (long)
            behaviourclasses[BEHAVIOURCLASS_PROCESSES].behaviours[AIBEHAVIOUR_CHOOSE_ENEMY].params[AIPARAM_CHOOSE_ENEMY_ANTILURE_DISTANCE].defaultvalue;
        check_behaviour_parameters(op, last);
    }

    /* Behaviours for melee-only fighters */
    if(!QUERY_FLAG(op, FLAG_READY_SPELL) && !QUERY_FLAG(op, FLAG_READY_BOW))
    {
        last = last->next = init_behaviour(BEHAVIOURCLASS_PROCESSES, AIBEHAVIOUR_LOOK_FOR_ENEMY_MISSILES);
        check_behaviour_parameters(op, last);
    }

    /* Moves */
    if (QUERY_FLAG(op, FLAG_STAND_STILL))
    {
        last = set->behaviours[BEHAVIOURCLASS_MOVES] = init_behaviour(BEHAVIOURCLASS_MOVES, AIBEHAVIOUR_STAND_STILL);
        check_behaviour_parameters(op, last);
    }
    else
    {
        last = set->behaviours[BEHAVIOURCLASS_MOVES] = init_behaviour(BEHAVIOURCLASS_MOVES, AIBEHAVIOUR_SLEEP);
        check_behaviour_parameters(op, last);

        if (op->run_away)
        {
            last = last->next = init_behaviour(BEHAVIOURCLASS_MOVES, AIBEHAVIOUR_RUN_AWAY_FROM_ENEMY);
            last->parameters[AIPARAM_RUN_AWAY_FROM_ENEMY_HP_THRESHOLD].intvalue = op->run_away;
            last->parameters[AIPARAM_RUN_AWAY_FROM_ENEMY_HP_THRESHOLD].flags |= AI_PARAM_PRESENT;
            check_behaviour_parameters(op, last);
        }

        if (!QUERY_FLAG(op, FLAG_NO_ATTACK))
        {
            /* Behaviours for ranged-only fighters */
            if (!QUERY_FLAG(op, FLAG_READY_WEAPON) &&
                (QUERY_FLAG(op, FLAG_READY_SPELL) || QUERY_FLAG(op, FLAG_READY_BOW)))
            {
                last = last->next = init_behaviour(BEHAVIOURCLASS_MOVES, AIBEHAVIOUR_KEEP_DISTANCE_TO_ENEMY);
                last->parameters[AIPARAM_KEEP_DISTANCE_TO_ENEMY_MIN_DIST].intvalue = (long)
                    behaviourclasses[BEHAVIOURCLASS_MOVES].behaviours[AIBEHAVIOUR_KEEP_DISTANCE_TO_ENEMY].params[AIPARAM_KEEP_DISTANCE_TO_ENEMY_MIN_DIST].defaultvalue;
                last->parameters[AIPARAM_KEEP_DISTANCE_TO_ENEMY_MAX_DIST].intvalue = (long)
                    behaviourclasses[BEHAVIOURCLASS_MOVES].behaviours[AIBEHAVIOUR_KEEP_DISTANCE_TO_ENEMY].params[AIPARAM_KEEP_DISTANCE_TO_ENEMY_MAX_DIST].defaultvalue;
                check_behaviour_parameters(op, last);

                last = last->next = init_behaviour(BEHAVIOURCLASS_MOVES, AIBEHAVIOUR_OPTIMIZE_LINE_OF_FIRE);
                check_behaviour_parameters(op, last);
            }

            /* Behaviours for melee-only fighters */
            if(!QUERY_FLAG(op, FLAG_READY_SPELL) && !QUERY_FLAG(op, FLAG_READY_BOW))
            {
                last = last->next = init_behaviour(BEHAVIOURCLASS_MOVES, AIBEHAVIOUR_AVOID_LINE_OF_FIRE);
                check_behaviour_parameters(op, last);
            }

            /* TODO: any behaviours for melee figheters
                last = last->next = init_behaviour(
                        BEHAVIOURCLASS_MOVES, AIBEHAVIOUR_STEP_BACK_AFTER_SWING);
                last->parameters[AIPARAM_STEP_BACK_AFTER_SWING_DIST].intvalue = (int)behaviourclasses[BEHAVIOURCLASS_MOVES].behaviours[AIBEHAVIOUR_STEP_BACK_AFTER_SWING].params[AIPARAM_STEP_BACK_AFTER_SWING_DIST].defaultvalue;
            */

            last = last->next = init_behaviour(BEHAVIOURCLASS_MOVES, AIBEHAVIOUR_MOVE_TOWARDS_ENEMY);
            check_behaviour_parameters(op, last);
            last = last->next = init_behaviour(BEHAVIOURCLASS_MOVES, AIBEHAVIOUR_MOVE_TOWARDS_ENEMY_LAST_KNOWN_POS);
            check_behaviour_parameters(op, last);
            last = last->next = init_behaviour(BEHAVIOURCLASS_MOVES, AIBEHAVIOUR_SEARCH_FOR_LOST_ENEMY);
            check_behaviour_parameters(op, last);
        }
        if(find_waypoint(op, NULL))
        {
            last = last->next = init_behaviour(BEHAVIOURCLASS_MOVES, AIBEHAVIOUR_MOVE_TOWARDS_WAYPOINT);
            check_behaviour_parameters(op, last);
        }
        if (QUERY_FLAG(op, FLAG_RANDOM_MOVE))
        {
            last = last->next = init_behaviour(BEHAVIOURCLASS_MOVES, AIBEHAVIOUR_MOVE_RANDOMLY);
            if (op->item_race != 255)
            {
                last->parameters[AIPARAM_MOVE_RANDOMLY_XLIMIT].intvalue = op->item_race;
                last->parameters[AIPARAM_MOVE_RANDOMLY_XLIMIT].flags |= AI_PARAM_PRESENT;
            }
            if (op->item_level != 255)
            {
                last->parameters[AIPARAM_MOVE_RANDOMLY_YLIMIT].intvalue = op->item_level;
                last->parameters[AIPARAM_MOVE_RANDOMLY_YLIMIT].flags |= AI_PARAM_PRESENT;
            }
            check_behaviour_parameters(op, last);
        }
        else
        {
            last = last->next = init_behaviour(BEHAVIOURCLASS_MOVES, AIBEHAVIOUR_MOVE_TOWARDS_HOME);
            check_behaviour_parameters(op, last);
        }
    }

    /* Actions */
    if (!QUERY_FLAG(op, FLAG_NO_ATTACK))
    {
        last = set->behaviours[BEHAVIOURCLASS_ACTIONS] = init_behaviour(BEHAVIOURCLASS_ACTIONS,
                                                                        AIBEHAVIOUR_MELEE_ATTACK_ENEMY);
        if (QUERY_FLAG(op, FLAG_READY_SPELL))
            last = last->next = init_behaviour(BEHAVIOURCLASS_ACTIONS, AIBEHAVIOUR_SPELL_ATTACK_ENEMY);
        if (QUERY_FLAG(op, FLAG_READY_BOW))
            last = last->next = init_behaviour(BEHAVIOURCLASS_ACTIONS, AIBEHAVIOUR_BOW_ATTACK_ENEMY);
    }

    return set;
}

/* Parse a stringint value in the form "string:int"
 * to separate string and int values.
 * Returns: 0 at success, non-zero on failure */
static int parse_stringint_parameter(struct mob_behaviour_param *param, const char *value)
{
    char *sep = strchr(value, ':');
    if(sep && sep > value && *(sep+1) != '\0')
    {
        /* TODO: get rid of shstr_add_lstring call. */
        param->stringvalue = shstr_add_lstring(value, sep-value);
        param->intvalue = atoi(sep+1);
//        LOG(llevDebug, "Stringint: %s:%d\n", param->stringvalue, param->intvalue);
        return 0;
    } else
        return -1;
}

/** Ensures all mandatory parameters were supplied, and
 * fills in default values for non-present optional parameters */
static int check_behaviour_parameters(object_t *op, struct mob_behaviour *behaviour)
{
    unsigned int i;

    for (i = 0; i < behaviour->declaration->nrof_params; i++)
    {
        if (!behaviour->parameters[i].flags & AI_PARAM_PRESENT)
        {
            if (behaviour->declaration->params[i].attribs & AI_MANDATORY_PARAM)
            {
                LOG(llevMapbug, "MAPBUG:: Mandatory parameter %s not given for behaviour %s (%s[%d] @ %s %d %d)!\n",
                    behaviour->declaration->params[i].name, behaviour->declaration->name,
                    STRING_OBJ_NAME(op), TAG(op), STRING_MAP_PATH(op->map), op->x, op->y);
                cleanup_behaviour_parameters(behaviour);
                behaviour->parameters = NULL;
                return -1;
            }
            else
            {
                switch (behaviour->declaration->params[i].type)
                {
                    case AI_INTEGER_TYPE:
                      behaviour->parameters[i].intvalue = (long) behaviour->declaration->params[i].defaultvalue;
                      break;
                    case AI_STRING_TYPE:
                      behaviour->parameters[i].stringvalue = shstr_add_string(behaviour->declaration->params[i].defaultvalue);
                      break;
                    case AI_STRINGINT_TYPE:
                      if(parse_stringint_parameter(&behaviour->parameters[i], behaviour->declaration->params[i].defaultvalue))
                      {
                          LOG(llevMapbug, "MAPBUG:: Parameter %s for behaviour %s, bad STRINGINT default value '%s' (%s[%d] @ %s %d %d)!\n",
                              behaviour->declaration->params[i].name, behaviour->declaration->name, (char *)behaviour->declaration->params[i].defaultvalue,
                              STRING_OBJ_NAME(op), TAG(op), STRING_MAP_PATH(op->map), op->x, op->y);
                      }
                      break;
                }
            }
        }
    }

    return 0;
}

/* Parse a single parameter=value pair into a
 * mob_behaviour_param struct
 * Returns: 0 at success, non-zero on failure */
static int parse_behaviour_parameters(object_t *op, const char *start, const char *end, struct mob_behaviour *behaviour)
{
    char                        namebuf[256], valuebuf[256], *ptr;
    struct mob_behaviour_param *param;
    struct behaviourparam_decl *paramdecl;
    unsigned int                i;

    while (start < end)
    {
        while (isspace(*start))
            start++;
        if (start >= end)
            break;

        /* Copy parameter name */
        ptr = namebuf;
        while (*start && !isspace(*start) && *start != '=')
            *ptr++ = *start++;
        *ptr = '\0';

        /* make sure we have an equals sign and a value */
        if (*start++ != '=' || start >= end)
            return -1;

        /* Copy parameter value */
        ptr = valuebuf;
        while (*start && !isspace(*start))
            *ptr++ = *start++;
        *ptr = '\0';

        if (ptr == valuebuf)
            return -1;

        /* Find the parameter in the declaration */
        /* TODO: speedup by storing parameter declarations
         * in hashtable. need to convert case first. */
        paramdecl = NULL;
        for (i = 0; i < behaviour->declaration->nrof_params; i++)
        {
            if (strcasecmp(namebuf, behaviour->declaration->params[i].name) == 0)
            {
                paramdecl = &behaviour->declaration->params[i];
                param = &behaviour->parameters[i];
                break;
            }
        }

        if (paramdecl == NULL)
        {
            LOG(llevMapbug, "MAPBUG:: Undefined parameter %s for behaviour %s (%s[%d] @ %s %d %d)!\n",
                namebuf, behaviour->declaration->name,
                STRING_OBJ_NAME(op), TAG(op), STRING_MAP_PATH(op->map), op->x, op->y);
            continue;
        }

        /* Handle multiply defined parameters */
        if (param->flags & AI_PARAM_PRESENT)
        {
            if (paramdecl->attribs & AI_MULTI_PARAM)
            {
                param->next = get_poolchunk(pool_mob_behaviourparam);
                param = param->next;
                param->next = NULL;
                param->stringvalue = NULL;
                param->intvalue = 0;
                param->flags = 0;
            }
            else
            {
                LOG(llevMapbug, "MAPBUG:: Parameter %s given twice for behaviour %s (%s[%d] @ %s %d %d)!\n",
                    namebuf, behaviour->declaration->name,
                    STRING_OBJ_NAME(op), TAG(op), STRING_MAP_PATH(op->map), op->x, op->y);
                continue;
            }
        }

        /* Fill in fields depending on type */
        switch (paramdecl->type)
        {
            case AI_INTEGER_TYPE:
              param->intvalue = atoi(valuebuf);
              param->flags |= AI_PARAM_PRESENT;
              break;

            case AI_STRING_TYPE:
              param->stringvalue = shstr_add_string(valuebuf);
              param->flags |= AI_PARAM_PRESENT;
//              LOG(llevMapbug, "MAPBUG:: Parameter %s for behaviour %s, string value '%s' (%s[%d] @ %s %d %d)!\n",
//                  namebuf, behaviour->declaration->name, valuebuf,
//                  STRING_OBJ_NAME(op), TAG(op), STRING_MAP_PATH(op->map), op->x, op->y);
              break;

            case AI_STRINGINT_TYPE:
              if(parse_stringint_parameter(param, valuebuf))
              {
                  LOG(llevMapbug, "MAPBUG:: Parameter %s for behaviour %s, bad STRINGINT format '%s' (%s[%d] @ %s %d %d)!\n",
                      namebuf, behaviour->declaration->name, valuebuf,
                      STRING_OBJ_NAME(op), TAG(op), STRING_MAP_PATH(op->map), op->x, op->y);
              }
              else
              {
                  param->flags |= AI_PARAM_PRESENT;
//                  LOG(llevMapbug, "MAPBUG:: Parameter %s for behaviour %s, stringint value '%s':%d (%s[%d] @ %s %d %d)!\n",
//                      namebuf, behaviour->declaration->name, param->stringvalue, param->intvalue,
//                      STRING_OBJ_NAME(op), TAG(op), STRING_MAP_PATH(op->map), op->x, op->y);
              }
              break;

            default:
              LOG(llevMapbug, "MAPBUG:: Parameter %s for behaviour %s, unknown type (%s[%d] @ %s %d %d)!\n",
                  namebuf, behaviour->declaration->name,
                  STRING_OBJ_NAME(op), TAG(op), STRING_MAP_PATH(op->map), op->x, op->y);
              break;
        }
    }

    return check_behaviour_parameters(op, behaviour);
}

static struct mob_behaviour *setup_plugin_behaviour(
        behaviourclass_t class,
        char *buf, char *colonpos, const char *tok_end, const char *conf_text)
{
    int plugin_index, behaviour_index, options_index;
    int behaviour_id = 0;
    struct mob_behaviour *new_behaviour = NULL;
    const char *line_end = conf_text;

    /* Set up a plugin:behaviour */
    switch(class) {
        case BEHAVIOURCLASS_PROCESSES:
            behaviour_id = AIBEHAVIOUR_PLUGIN_PROCESS;
            plugin_index = AIPARAM_PLUGIN_PROCESS_PLUGIN;
            behaviour_index = AIPARAM_PLUGIN_PROCESS_BEHAVIOUR;
            options_index = AIPARAM_PLUGIN_PROCESS_OPTIONS;
            break;
        case BEHAVIOURCLASS_MOVES:
            behaviour_id = AIBEHAVIOUR_PLUGIN_MOVE;
            plugin_index = AIPARAM_PLUGIN_MOVE_PLUGIN;
            behaviour_index = AIPARAM_PLUGIN_MOVE_BEHAVIOUR;
            options_index = AIPARAM_PLUGIN_MOVE_OPTIONS;
            break;
        case BEHAVIOURCLASS_ACTIONS:
            behaviour_id = AIBEHAVIOUR_PLUGIN_ACTION;
            plugin_index = AIPARAM_PLUGIN_ACTION_PLUGIN;
            behaviour_index = AIPARAM_PLUGIN_ACTION_BEHAVIOUR;
            options_index = AIPARAM_PLUGIN_ACTION_OPTIONS;
            break;
        default:
            LOG(llevBug, "BUG: behaviour %s without class\n", buf);
            break;
    }
    new_behaviour = init_behaviour(class, behaviour_id);

    /* Split up plugin and behaviour names. Validate plugin */
    *colonpos = '\0';
    if(findPlugin(buf) == -1)
    {
        LOG(llevBug, "BUG: behaviour plugin %s unknown\n", buf);
        return NULL;
    }

    new_behaviour->parameters[plugin_index].stringvalue = shstr_add_string(buf);
    new_behaviour->parameters[plugin_index].flags |= AI_PARAM_PRESENT;

    new_behaviour->parameters[behaviour_index].stringvalue = shstr_add_string(colonpos+1);
    new_behaviour->parameters[behaviour_index].flags |= AI_PARAM_PRESENT;

    /* See if there were any parameters */
    while (*line_end == '\n' || *line_end == '\r')
        line_end--;

    if(tok_end < line_end) {
        /* TODO: get rid of shstr_add_lstring() call */
        new_behaviour->parameters[options_index].stringvalue = shstr_add_lstring(tok_end + 1, line_end - tok_end);
        new_behaviour->parameters[options_index].flags |= AI_PARAM_PRESENT;
    }

    return new_behaviour;
}

static struct mob_behaviour *setup_behaviour(
        object_t *op, behaviourclass_t class,
        char *buf, const char *tok_end, const char *conf_text)
{
    int behaviour_id;
    struct mob_behaviour *new_behaviour = NULL;

    /* find the corresponding behaviour declaration */
    /* TODO: here we can speed up search significantly using a
     * perfect hash function. A standard hashtable will do too. (need to convert case) */

    for (behaviour_id = 0; behaviourclasses[class].behaviours[behaviour_id].func; behaviour_id++)
    {
        if (!strcasecmp(buf, behaviourclasses[class].behaviours[behaviour_id].name))
        {
            new_behaviour = init_behaviour(class, behaviour_id);
            //                    LOG(llevDebug,"    behaviour %s\n", buf);
            break;
        }
    }

    if (new_behaviour == NULL)
    {
        LOG(llevBug, "BUG: unknown %s behaviour %s of %s\n", behaviourclasses[class].name, buf,
            STRING_OBJ_NAME(op));
        return NULL;
    }

    /* Parse parameters to behaviour */
    if (new_behaviour->parameters)
    {
        /* Parse behaviour parameters */
        if (parse_behaviour_parameters(op, tok_end, conf_text, new_behaviour) == -1)
        {
            LOG(llevBug, "BUG: bad parameterlist for %s of %s\n", buf, STRING_OBJ_NAME(op));
            return_poolchunk(new_behaviour, pool_mob_behaviour);
            return NULL;
        }
    }

    return new_behaviour;
}

struct mob_behaviourset * parse_behaviourconfig(const char *conf_text, object_t *op)
{
    struct mob_behaviourset    *behaviourset;
    struct mob_behaviour       *last_behaviour[NROF_BEHAVIOURCLASSES];
    struct mob_behaviour       *new_behaviour;
    behaviourclass_t            class = BEHAVIOURCLASS_NONE;

    int         i;

    const char *tok_start, *tok_end;
    char        buf[HUGE_BUF];

    /* First see if we have already parsed an identical behaviourset */
    for (behaviourset = parsed_behavioursets; behaviourset; behaviourset = behaviourset->next)
    {
        /* This assumes that conf_text is a hashed string! */
        if (behaviourset->definition == conf_text)
        {
            //            LOG(llevDebug,"Found previously parsed behaviourset for '%s'. refcount = %d\n", STRING_OBJ_NAME(op), behaviourset->refcount);
            behaviourset->refcount++;
            return behaviourset;
        }
    }

    /* init */
    behaviourset = get_poolchunk(pool_mob_behaviourset);
    for (i = 0; i < NROF_BEHAVIOURCLASSES; i++)
    {
        behaviourset->behaviours[i] = NULL;
        last_behaviour[i] = NULL;
    }
    behaviourset->prev = behaviourset->next = NULL;
    behaviourset->refcount = 1;
    behaviourset->definition = shstr_add_refcount(conf_text);
    behaviourset->bghash = 0;
    behaviourset->attitudes = NULL;
    behaviourset->attractions = NULL;
    behaviourset->groups = NULL;

    /* Insert in list */
    behaviourset->next = parsed_behavioursets;
    if (behaviourset->next)
        behaviourset->next->prev = behaviourset;
    parsed_behavioursets = behaviourset;

    //    LOG(llevDebug,"parse_behaviourconfig(): Parsing for %s:\n", STRING_OBJ_NAME(op));
    while (*conf_text)
    {
        /* skip whitespace */
        if (isspace(*conf_text))
        {
            conf_text++;
            continue;
        }

        /* skip comments */
        if (*conf_text == '#')
        {
            while (*conf_text && *conf_text != '\r' && *conf_text != '\n')
                conf_text++;
            continue;
        }

        /* Search for next whitespace */
        tok_start = tok_end = conf_text;
        while (*tok_end && !isspace(*tok_end))
            tok_end++;

        if (tok_end - tok_start >= 2 && *(tok_end - 1) == ':')
        {
            int i;

            /* extract class header */
            while (*tok_start != ':')
            {
                buf[tok_start - conf_text] = *tok_start;
                tok_start++;
            }
            buf[tok_start - conf_text] = '\0';

            //            LOG(llevDebug,"  class %s:\n", buf);

            /* look up class header */
            for (i = 0; i < NROF_BEHAVIOURCLASSES; i++)
            {
                if (strcasecmp(buf, behaviourclasses[i].name) == 0)
                {
                    class = i;
                    break;
                }
            }
            if (i == NROF_BEHAVIOURCLASSES)
            {
                LOG(llevBug, "BUG: unknown class %s of %s\n", buf, STRING_OBJ_NAME(op));
                break;
            }

            /* find EOL */
            while (*conf_text && *conf_text != '\r' && *conf_text != '\n')
                conf_text++;
        }
        else
        {
            char *colonpos = NULL;

            /* extract behaviour name */
            while (tok_start < tok_end)
            {
                buf[tok_start - conf_text] = *tok_start;

                /* Separator char for plugin:behaviour */
                if(*tok_start == ':')
                    colonpos = &buf[tok_start - conf_text];

                tok_start++;
            }
            buf[tok_start - conf_text] = '\0';

            /* behaviour line, find EOL */
            while (*conf_text && *conf_text != '\r' && *conf_text != '\n')
                conf_text++;

            /* Make sure current class is valid */
            if (class == BEHAVIOURCLASS_NONE)
            {
                LOG(llevBug, "BUG: behaviour %s of %s without class\n", buf, STRING_OBJ_NAME(op));
                continue;
            }

            if(colonpos)
                new_behaviour = setup_plugin_behaviour(class, buf, colonpos, tok_end, conf_text);
            else
            {
                new_behaviour = setup_behaviour(op, class, buf, tok_end, conf_text);
                /* Special handling for some behaviours */
                if(new_behaviour && class == BEHAVIOURCLASS_PROCESSES)
                {
                    switch(new_behaviour->declaration->id)
                    {
                        case AIBEHAVIOUR_FRIENDSHIP:
                            behaviourset->attitudes = new_behaviour->parameters;
                            break;
                        case AIBEHAVIOUR_ATTRACTION:
                            behaviourset->attractions = new_behaviour->parameters;
                            break;
                        case AIBEHAVIOUR_GROUPS:
                            behaviourset->groups = new_behaviour->parameters;
                            break;
                    }
                }
            }

            if(new_behaviour == NULL)
            {
                LOG(llevBug, "BUG: Failed setting up behaviour %s for %s\n", buf, STRING_OBJ_NAME(op));
                continue;
            }

            /* If everything checks out, add the behaviour to the list */
            if (last_behaviour[class] == NULL)
                behaviourset->behaviours[class] = new_behaviour;
            else
                last_behaviour[class]->next = new_behaviour;
            last_behaviour[class] = new_behaviour;
            new_behaviour->next = NULL;
        }
    }

    return behaviourset;
}

/*
 * In the case of no mob ai object, first we will first
 * fall back to an arch-default for that mob, then a race-default and
 * finally a global default.
 */
struct mob_behaviourset * setup_behaviours(object_t *op)
{
    object_t *conf_obj,
           *next;

    /* Find mob's behaviour configuration object_t */
    FOREACH_OBJECT_IN_OBJECT(conf_obj, op, next)
    {
        if (conf_obj->type == TYPE_AI)
        {
            break;
        }
    }

    /* Configuration from mob, arch, race or generator? */
    if (conf_obj && conf_obj->msg)
        return parse_behaviourconfig(conf_obj->msg, op);
    else
    {
        //        LOG(llevDebug,"parse_behaviourconfig(): No object AI for '%s', looking for arch AI, then race AI.\n", STRING_OBJ_NAME(op));
        if (op->arch->ai)
        {
            //            LOG(llevDebug,"parse_behaviourconfig(): found arch AI for %s.\n", STRING_OBJ_NAME(op));
            op->arch->ai->refcount++;
            return op->arch->ai;
        }
        else if (op->race)
        {
            racelink_t   *race    = find_racelink(op->race);
            if (race && race->ai)
            {
                //                LOG(llevDebug,"parse_behaviourconfig(): found race AI for %s.\n", STRING_OBJ_NAME(op));
                race->ai->refcount++;
                return race->ai;
            }
        }

        //            LOG(llevDebug,"parse_behaviourconfig(): No arch or race AI for '%s'. Generating a custom one\n", STRING_OBJ_NAME(op));
    }

    return generate_behaviourset(op);
}

/** Free the mob's current behaviourset and generate a new one */
void reload_behaviours(object_t *op)
{
    if(MOB_DATA(op) == NULL)
    {
        LOG(llevBug, "BUG: reload_behaviours(op='%s') called and MOB_DATA(op) == NULL\n", STRING_OBJ_NAME(op));
        return;
    }

    free_behaviourset(MOB_DATA(op)->behaviours);
    MOB_DATA(op)->behaviours = setup_behaviours(op);
}
