/*
 * Copyright 1993, 1995 Christopher Seiwald.
 *
 * This file is part of Jam - see jam.c for Copyright information.
 */

# include "jam.h"
# include "lists.h"
# include "parse.h"
# include "variable.h"
# include "rules.h"
# include "newstr.h"
# include "hash.h"
# include "modules.h"
# include "search.h"
# include "lists.h"
# include "pathsys.h"
# include "timestamp.h"

/*  This file is ALSO:
 *  Copyright 2001-2004 David Abrahams.
 *  Distributed under the Boost Software License, Version 1.0.
 *  (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
 */

/*
 * rules.c - access to RULEs, TARGETs, and ACTIONs
 *
 * External routines:
 *
 *    bindrule()     - return pointer to RULE, creating it if necessary.
 *    bindtarget()   - return pointer to TARGET, creating it if necessary.
 *    touch_target() - mark a target to simulate being new.
 *    targetlist()   - turn list of target names into a TARGET chain.
 *    targetentry()  - add a TARGET to a chain of TARGETS.
 *    actionlist()   - append to an ACTION chain.
 *    addsettings()  - add a deferred "set" command to a target.
 *    pushsettings() - set all target specific variables.
 *    popsettings()  - reset target specific variables to their pre-push values.
 *    freesettings() - delete a settings list.
 *    rules_done()   - free RULE and TARGET tables.
 *
 * 04/12/94 (seiwald) - actionlist() now just appends a single action.
 * 08/23/94 (seiwald) - Support for '+=' (append to variable)
 */

static void set_rule_actions( RULE *, rule_actions * );
static void set_rule_body   ( RULE *, argument_list *, PARSE * procedure );

static struct hash * targethash = 0;

struct _located_target
{
    char   * file_name;
    TARGET * target;
};
typedef struct _located_target LOCATED_TARGET ;

static struct hash * located_targets = 0;


/*
 * target_include() - adds the 'included' TARGET to the list of targets included
 * by the 'including' TARGET. Such targets are modeled as dependencies of the
 * internal include node belonging to the 'including' TARGET.
 */

void target_include( TARGET * including, TARGET * included )
{
    TARGET * internal;
    if ( !including->includes )
    {
        including->includes = copytarget( including );
        including->includes->original_target = including;
    }
    internal = including->includes;
    internal->depends = targetentry( internal->depends, included );
}


/*
 * enter_rule() - return pointer to RULE, creating it if necessary in
 * target_module.
 */

static RULE * enter_rule( char * rulename, module_t * target_module )
{
    RULE rule;
    RULE * r = &rule;

    r->name = rulename;

    if ( hashenter( demand_rules( target_module ), (HASHDATA * *)&r ) )
    {
        r->name = newstr( rulename );   /* never freed */
        r->procedure = (PARSE *)0;
        r->module = 0;
        r->actions = 0;
        r->arguments = 0;
        r->exported = 0;
        r->module = target_module;
#ifdef HAVE_PYTHON
        r->python_function = 0;
#endif
    }
    return r;
}


/*
 * define_rule() - return pointer to RULE, creating it if necessary in
 * target_module. Prepare it to accept a body or action originating in
 * src_module.
 */

static RULE * define_rule
(
    module_t * src_module,
    char     * rulename,
    module_t * target_module
)
{
    RULE * r = enter_rule( rulename, target_module );
    if ( r->module != src_module ) /* if the rule was imported from elsewhere, clear it now */
    {
        set_rule_body( r, 0, 0 );
        set_rule_actions( r, 0 );
        r->module = src_module; /* r will be executed in the source module */
    }
    return r;
}


void rule_free( RULE * r )
{
    freestr( r->name );
    r->name = "";
    parse_free( r->procedure );
    r->procedure = 0;
    if ( r->arguments )
        args_free( r->arguments );
    r->arguments = 0;
    if ( r->actions )
        actions_free( r->actions );
    r->actions = 0;
}


/*
 * bindtarget() - return pointer to TARGET, creating it if necessary.
 */

TARGET * bindtarget( char const * target_name )
{
    TARGET target;
    TARGET * t = &target;

    if ( !targethash )
        targethash = hashinit( sizeof( TARGET ), "targets" );

    /* Perforce added const everywhere. No time to merge that change. */
#ifdef NT
    target_name = short_path_to_long_path( (char *)target_name );
#endif
    t->name = (char *)target_name;

    if ( hashenter( targethash, (HASHDATA * *)&t ) )
    {
        memset( (char *)t, '\0', sizeof( *t ) );
        t->name = newstr( (char *)target_name );  /* never freed */
        t->boundname = t->name;  /* default for T_FLAG_NOTFILE */
    }

    return t;
}


static void bind_explicitly_located_target( void * xtarget, void * data )
{
    TARGET * t = (TARGET *)xtarget;
    if ( !( t->flags & T_FLAG_NOTFILE ) )
    {
        /* Check if there's a setting for LOCATE */
        SETTINGS * s = t->settings;
        for ( ; s ; s = s->next )
        {
            if ( strcmp( s->symbol, "LOCATE" ) == 0 )
            {
                pushsettings( t->settings );
                /* We are binding a target with explicit LOCATE. So third
                 * argument is of no use: nothing will be returned through it.
                 */
                t->boundname = search( t->name, &t->time, 0, 0 );
                popsettings( t->settings );
                break;
            }
        }
    }
}


void bind_explicitly_located_targets()
{
    if ( targethash )
        hashenumerate( targethash, bind_explicitly_located_target, (void *)0 );
}


/* TODO: It is probably not a good idea to use functions in other modules like
  this. */
void call_bind_rule( char * target, char * boundname );


TARGET * search_for_target ( char * name, LIST * search_path )
{
    PATHNAME f[1];
    string buf[1];
    LOCATED_TARGET lt;
    LOCATED_TARGET * lta = &lt;
    time_t time;
    int found = 0;
    TARGET * result;

    string_new( buf );

    path_parse( name, f );

    f->f_grist.ptr = 0;
    f->f_grist.len = 0;

    while ( search_path )
    {
        f->f_root.ptr = search_path->string;
        f->f_root.len = strlen( search_path->string );

        string_truncate( buf, 0 );
        path_build( f, buf, 1 );

        lt.file_name = buf->value ;

        if ( !located_targets )
            located_targets = hashinit( sizeof(LOCATED_TARGET),
                                        "located targets" );

        if ( hashcheck( located_targets, (HASHDATA * *)&lta ) )
        {
            return lta->target;
        }

        timestamp( buf->value, &time );
        if ( time )
        {
            found = 1;
            break;
        }

        search_path = list_next( search_path );
    }

    if ( !found )
    {
        f->f_root.ptr = 0;
        f->f_root.len = 0;

        string_truncate( buf, 0 );
        path_build( f, buf, 1 );

        timestamp( buf->value, &time );
    }

    result = bindtarget( name );
    result->boundname = newstr( buf->value );
    result->time = time;
    result->binding = time ? T_BIND_EXISTS : T_BIND_MISSING;

    call_bind_rule( result->name, result->boundname );

    string_free( buf );

    return result;
}


/*
 * copytarget() - make a new target with the old target's name.
 *
 * Not entered into hash table -- for internal nodes.
 */

TARGET * copytarget( const TARGET * ot )
{
    TARGET * t = (TARGET *)BJAM_MALLOC( sizeof( *t ) );
    memset( (char *)t, '\0', sizeof( *t ) );
    t->name = copystr( ot->name );
    t->boundname = t->name;

    t->flags |= T_FLAG_NOTFILE | T_FLAG_INTERNAL;

    return t;
}


/*
 * touch_target() - mark a target to simulate being new.
 */

void touch_target( char * t )
{
    bindtarget( t )->flags |= T_FLAG_TOUCHED;
}


/*
 * targetlist() - turn list of target names into a TARGET chain.
 *
 * Inputs:
 *  chain   existing TARGETS to append to
 *  targets list of target names
 */

TARGETS * targetlist( TARGETS * chain, LIST * target_names )
{
    for ( ; target_names; target_names = list_next( target_names ) )
        chain = targetentry( chain, bindtarget( target_names->string ) );
    return chain;
}


/*
 * targetentry() - add a TARGET to a chain of TARGETS.
 *
 * Inputs:
 *  chain   existing TARGETS to append to
 *  target  new target to append
 */

TARGETS * targetentry( TARGETS * chain, TARGET * target )
{
    TARGETS * c = (TARGETS *)BJAM_MALLOC( sizeof( TARGETS ) );
    c->target = target;

    if ( !chain ) chain = c;
    else chain->tail->next = c;
    chain->tail = c;
    c->next = 0;

    return chain;
}


/*
 * targetchain() - append two TARGET chains.
 *
 * Inputs:
 *  chain   exisitng TARGETS to append to
 *  target  new target to append
 */

TARGETS * targetchain( TARGETS * chain, TARGETS * targets )
{
    if ( !targets ) return chain;
    if ( !chain   ) return targets;

    chain->tail->next = targets;
    chain->tail = targets->tail;

    return chain;
}

/*
 * actionlist() - append to an ACTION chain.
 */

ACTIONS * actionlist( ACTIONS * chain, ACTION * action )
{
    ACTIONS * actions = (ACTIONS *)BJAM_MALLOC( sizeof( ACTIONS ) );

    actions->action = action;

    if ( !chain ) chain = actions;
    else chain->tail->next = actions;
    chain->tail = actions;
    actions->next = 0;

    return chain;
}

static SETTINGS * settings_freelist;


/*
 * addsettings() - add a deferred "set" command to a target.
 *
 * Adds a variable setting (varname=list) onto a chain of settings for a
 * particular target. 'flag' controls the relationship between new and old
 * values in the same way as in var_set() function (see variable.c). Returns
 * the head of the settings chain.
 */

SETTINGS * addsettings( SETTINGS * head, int flag, char * symbol, LIST * value )
{
    SETTINGS * v;

    /* Look for previous settings. */
    for ( v = head; v; v = v->next )
        if ( !strcmp( v->symbol, symbol ) )
            break;

    /* If not previously set, alloc a new. */
    /* If appending, do so. */
    /* Else free old and set new. */
    if ( !v )
    {
        v = settings_freelist;

        if ( v )
            settings_freelist = v->next;
        else
            v = (SETTINGS *)BJAM_MALLOC( sizeof( *v ) );

        v->symbol = newstr( symbol );
        v->value = value;
        v->next = head;
        v->multiple = 0;
        head = v;
    }
    else if ( flag == VAR_APPEND )
    {
        v->value = list_append( v->value, value );
    }
    else if ( flag != VAR_DEFAULT )
    {
        list_free( v->value );
        v->value = value;
    }
    else
        list_free( value );

    /* Return (new) head of list. */
    return head;
}


/*
 * pushsettings() - set all target specific variables.
 */

void pushsettings( SETTINGS * v )
{
    for ( ; v; v = v->next )
        v->value = var_swap( v->symbol, v->value );
}


/*
 * popsettings() - reset target specific variables to their pre-push values.
 */

void popsettings( SETTINGS * v )
{
    pushsettings( v );  /* just swap again */
}


/*
 * copysettings() - duplicate a settings list, returning the new copy.
 */

SETTINGS * copysettings( SETTINGS * head )
{
    SETTINGS * copy = 0;
    SETTINGS * v;
    for ( v = head; v; v = v->next )
        copy = addsettings( copy, VAR_SET, v->symbol, list_copy( 0, v->value ) );
    return copy;
}


/*
 * freetargets() - delete a targets list.
 */

void freetargets( TARGETS * chain )
{
    while ( chain )
    {
        TARGETS * n = chain->next;
        BJAM_FREE( chain );
        chain = n;
    }
}


/*
 * freeactions() - delete an action list.
 */

void freeactions( ACTIONS * chain )
{
    while ( chain )
    {
        ACTIONS * n = chain->next;
        BJAM_FREE( chain );
        chain = n;
    }
}


/*
 * freesettings() - delete a settings list.
 */

void freesettings( SETTINGS * v )
{
    while ( v )
    {
        SETTINGS * n = v->next;
        freestr( v->symbol );
        list_free( v->value );
        v->next = settings_freelist;
        settings_freelist = v;
        v = n;
    }
}


static void freetarget( void * xt, void * data )
{
    TARGET * t = (TARGET *)xt;
    if ( t->settings ) freesettings( t->settings            );
    if ( t->depends  ) freetargets ( t->depends             );
    if ( t->includes ) freetarget  ( t->includes, (void *)0 );
    if ( t->actions  ) freeactions ( t->actions             );
}


/*
 * rules_done() - free RULE and TARGET tables.
 */

void rules_done()
{
    hashenumerate( targethash, freetarget, 0 );
    hashdone( targethash );
    while ( settings_freelist )
    {
        SETTINGS * n = settings_freelist->next;
        BJAM_FREE( settings_freelist );
        settings_freelist = n;
    }
}


/*
 * args_new() - make a new reference-counted argument list.
 */

argument_list * args_new()
{
    argument_list * r = (argument_list *)BJAM_MALLOC( sizeof(argument_list) );
    r->reference_count = 0;
    lol_init( r->data );
    return r;
}


/*
 * args_refer() - add a new reference to the given argument list.
 */

void args_refer( argument_list * a )
{
    ++a->reference_count;
}


/*
 * args_free() - release a reference to the given argument list.
 */

void args_free( argument_list * a )
{
    if ( --a->reference_count <= 0 )
    {
        lol_free( a->data );
        BJAM_FREE( a );
    }
}


/*
 * actions_refer() - add a new reference to the given actions.
 */

void actions_refer( rule_actions * a )
{
    ++a->reference_count;
}


/*
 * actions_free() - release a reference to the given actions.
 */

void actions_free( rule_actions * a )
{
    if ( --a->reference_count <= 0 )
    {
        freestr( a->command );
        list_free( a->bindlist );
        BJAM_FREE( a );
    }
}


/*
 * set_rule_body() - set the argument list and procedure of the given rule.
 */

static void set_rule_body( RULE * rule, argument_list * args, PARSE * procedure )
{
    if ( args )
        args_refer( args );
    if ( rule->arguments )
        args_free( rule->arguments );
    rule->arguments = args;

    if ( procedure )
        parse_refer( procedure );
    if ( rule->procedure )
        parse_free( rule->procedure );
    rule->procedure = procedure;
}


/*
 * global_name() - given a rule, return the name for a corresponding rule in the
 * global module.
 */

static char * global_rule_name( RULE * r )
{
    if ( r->module == root_module() )
        return r->name;

    {
        char name[4096] = "";
        strncat( name, r->module->name, sizeof( name ) - 1 );
        strncat( name, r->name, sizeof( name ) - 1 );
        return newstr( name);
    }
}


/*
 * global_rule() - given a rule, produce the corresponding entry in the global
 * module.
 */

static RULE * global_rule( RULE * r )
{
    if ( r->module == root_module() )
        return r;

    {
        char * name = global_rule_name( r );
        RULE * result = define_rule( r->module, name, root_module() );
        freestr( name );
        return result;
    }
}


/*
 * new_rule_body() - make a new rule named rulename in the given module, with
 * the given argument list and procedure. If exported is true, the rule is
 * exported to the global module as modulename.rulename.
 */

RULE * new_rule_body( module_t * m, char * rulename, argument_list * args, PARSE * procedure, int exported )
{
    RULE * local = define_rule( m, rulename, m );
    local->exported = exported;
    set_rule_body( local, args, procedure );

    /* Mark the procedure with the global rule name, regardless of whether the
     * rule is exported. That gives us something reasonably identifiable that we
     * can use, e.g. in profiling output. Only do this once, since this could be
     * called multiple times with the same procedure.
     */
    if ( procedure->rulename == 0 )
        procedure->rulename = global_rule_name( local );

    return local;
}


static void set_rule_actions( RULE * rule, rule_actions * actions )
{
    if ( actions )
        actions_refer( actions );
    if ( rule->actions )
        actions_free( rule->actions );
    rule->actions = actions;
}


static rule_actions * actions_new( char * command, LIST * bindlist, int flags )
{
    rule_actions * result = (rule_actions *)BJAM_MALLOC( sizeof( rule_actions ) );
    result->command = copystr( command );
    result->bindlist = bindlist;
    result->flags = flags;
    result->reference_count = 0;
    return result;
}


RULE * new_rule_actions( module_t * m, char * rulename, char * command, LIST * bindlist, int flags )
{
    RULE * local = define_rule( m, rulename, m );
    RULE * global = global_rule( local );
    set_rule_actions( local, actions_new( command, bindlist, flags ) );
    set_rule_actions( global, local->actions );
    return local;
}


/*
 * Looks for a rule in the specified module, and returns it, if found. First
 * checks if the rule is present in the module's rule table. Second, if name of
 * the rule is in the form name1.name2 and name1 is in the list of imported
 * modules, look in module 'name1' for rule 'name2'.
 */

RULE * lookup_rule( char * rulename, module_t * m, int local_only )
{
    RULE       rule;
    RULE     * r = &rule;
    RULE     * result = 0;
    module_t * original_module = m;

    r->name = rulename;

    if ( m->class_module )
        m = m->class_module;

    if ( m->rules && hashcheck( m->rules, (HASHDATA * *)&r ) )
        result = r;
    else if ( !local_only && m->imported_modules )
    {
        /* Try splitting the name into module and rule. */
        char *p = strchr( r->name, '.' ) ;
        if ( p )
        {
            *p = '\0';
            /* Now, r->name keeps the module name, and p+1 keeps the rule name.
             */
            if ( hashcheck( m->imported_modules, (HASHDATA * *)&r ) )
                result = lookup_rule( p + 1, bindmodule( rulename ), 1 );
            *p = '.';
        }
    }

    if ( result )
    {
        if ( local_only && !result->exported )
            result = 0;
        else
        {
            /* Lookup started in class module. We have found a rule in class
             * module, which is marked for execution in that module, or in some
             * instances. Mark it for execution in the instance where we started
             * the lookup.
             */
            int execute_in_class = ( result->module == m );
            int execute_in_some_instance = ( result->module->class_module &&
                ( result->module->class_module == m ) );
            if ( ( original_module != m ) &&
                ( execute_in_class || execute_in_some_instance ) )
                result->module = original_module;
        }
    }

    return result;
}


RULE * bindrule( char * rulename, module_t * m )
{
    RULE * result = lookup_rule( rulename, m, 0 );
    if ( !result )
        result = lookup_rule( rulename, root_module(), 0 );
    /* We have only one caller, 'evaluate_rule', which will complain about
     * calling an undefined rule. We could issue the error here, but we do not
     * have the necessary information, such as frame.
     */
    if ( !result )
        result = enter_rule( rulename, m );
    return result;
}


RULE * import_rule( RULE * source, module_t * m, char * name )
{
    RULE * dest = define_rule( source->module, name, m );
    set_rule_body( dest, source->arguments, source->procedure );
    set_rule_actions( dest, source->actions );
    return dest;
}
