/*
 * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc.
 *
 * This file is part of Jam - see jam.c for Copyright information.
 */

/* 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 copy at
 * http://www.boost.org/LICENSE_1_0.txt)
 */

#include "jam.h"
#include "search.h"

#include "compile.h"
#include "filesys.h"
#include "hash.h"
#include "lists.h"
#include "object.h"
#include "pathsys.h"
#include "jam_strings.h"
#include "timestamp.h"
#include "variable.h"
#include "output.h"

#include <string.h>


typedef struct _binding
{
    OBJECT * binding;
    OBJECT * target;
} BINDING;

static struct hash * explicit_bindings = 0;


void call_bind_rule( OBJECT * target_, OBJECT * boundname_ )
{
    LIST * const bind_rule = var_get( root_module(), constant_BINDRULE );
    if ( !list_empty( bind_rule ) )
    {
        OBJECT * target = object_copy( target_ );
        OBJECT * boundname = object_copy( boundname_ );
        if ( boundname && target )
        {
            /* Prepare the argument list. */
            FRAME frame[ 1 ];
            frame_init( frame );

            /* First argument is the target name. */
            lol_add( frame->args, list_new( target ) );

            lol_add( frame->args, list_new( boundname ) );
            if ( lol_get( frame->args, 1 ) )
            {
                OBJECT * rulename = list_front( bind_rule );
                list_free( evaluate_rule( bindrule( rulename, root_module() ), rulename, frame ) );
            }

            /* Clean up */
            frame_free( frame );
        }
        else
        {
            if ( boundname )
                object_free( boundname );
            if ( target )
                object_free( target );
        }
    }
}

/* Records the binding of a target with an explicit LOCATE. */
void set_explicit_binding( OBJECT * target, OBJECT * locate )
{
    OBJECT * boundname;
    OBJECT * key;
    PATHNAME f[ 1 ];
    string buf[ 1 ];
    int found;
    BINDING * ba;

    if ( !explicit_bindings )
        explicit_bindings = hashinit( sizeof( BINDING ), "explicitly specified "
            "locations" );

    string_new( buf );

    /* Parse the filename. */
    path_parse( object_str( target ), f );

    /* Ignore the grist. */
    f->f_grist.ptr = 0;
    f->f_grist.len = 0;

    /* Root the target path at the given location. */
    f->f_root.ptr = object_str( locate );
    f->f_root.len = int32_t(strlen( object_str( locate ) ));

    path_build( f, buf );
    boundname = object_new( buf->value );
    if ( DEBUG_SEARCH )
        out_printf( "explicit locate %s: %s\n", object_str( target ), buf->value );
    string_free( buf );
    key = path_as_key( boundname );
    object_free( boundname );

    ba = (BINDING *)hash_insert( explicit_bindings, key, &found );
    if ( !found )
    {
        ba->binding = key;
        ba->target = target;
    }
    else
        object_free( key );
}

/*
 * search.c - find a target along $(SEARCH) or $(LOCATE).
 *
 * First, check if LOCATE is set. If so, use it to determine the location of
 * target and return, regardless of whether anything exists at that location.
 *
 * Second, examine all directories in SEARCH. If the file exists there or there
 * is another target with the same name already placed at this location via the
 * LOCATE setting, stop and return the location. In case of a previous target,
 * return its name via the 'another_target' argument.
 *
 * This behaviour allows handling dependencies on generated files.
 *
 * If caller does not expect that the target is generated, 0 can be passed as
 * 'another_target'.
 */

OBJECT * search( OBJECT * target, timestamp * const time,
    OBJECT * * another_target, int const file )
{
    PATHNAME f[ 1 ];
    LIST * varlist;
    string buf[ 1 ];
    int found = 0;
    OBJECT * boundname = 0;

    if ( another_target )
        *another_target = 0;

    if ( !explicit_bindings )
        explicit_bindings = hashinit( sizeof( BINDING ), "explicitly specified "
            "locations" );

    string_new( buf );

    /* Parse the filename. */
    path_parse( object_str( target ), f );

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

    varlist = var_get( root_module(), constant_LOCATE );
    if ( !list_empty( varlist ) )
    {
        OBJECT * key;
        f->f_root.ptr = object_str( list_front( varlist ) );
        f->f_root.len = int32_t(strlen( object_str( list_front( varlist ) ) ));

        path_build( f, buf );

        if ( DEBUG_SEARCH )
            out_printf( "locate %s: %s\n", object_str( target ), buf->value );

        key = object_new( buf->value );
        timestamp_from_path( time, key );
        object_free( key );
        found = 1;
    }
    else if ( varlist = var_get( root_module(), constant_SEARCH ),
        !list_empty( varlist ) )
    {
        LISTITER iter = list_begin( varlist );
        LISTITER const end = list_end( varlist );
        for ( ; iter != end; iter = list_next( iter ) )
        {
            BINDING * ba;
            file_info_t * ff;
            OBJECT * key;
            OBJECT * test_path;

            f->f_root.ptr = object_str( list_item( iter ) );
            f->f_root.len = int32_t(strlen( object_str( list_item( iter ) ) ));

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

            if ( DEBUG_SEARCH )
                out_printf( "search %s: %s\n", object_str( target ), buf->value );

            test_path = object_new( buf->value );
            key = path_as_key( test_path );
            object_free( test_path );
            ff = file_query( key );
            timestamp_from_path( time, key );

            if ( ( ba = (BINDING *)hash_find( explicit_bindings, key ) ) )
            {
                if ( DEBUG_SEARCH )
                    out_printf(" search %s: found explicitly located target %s\n",
                        object_str( target ), object_str( ba->target ) );
                if ( another_target )
                    *another_target = ba->target;
                found = 1;
                object_free( key );
                break;
            }
            else if ( ff )
            {
                if ( !file || ff->is_file )
                {
                    found = 1;
                    object_free( key );
                    break;
                }
            }
            object_free( key );
        }
    }

    if ( !found )
    {
        /* Look for the obvious. */
        /* This is a questionable move. Should we look in the obvious place if
         * SEARCH is set?
         */
        OBJECT * key;

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

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

        if ( DEBUG_SEARCH )
            out_printf( "search %s: %s\n", object_str( target ), buf->value );

        key = object_new( buf->value );
        timestamp_from_path( time, key );
        object_free( key );
    }

    boundname = object_new( buf->value );
    string_free( buf );

    /* Prepare a call to BINDRULE if the variable is set. */
    call_bind_rule( target, boundname );

    return boundname;
}


static void free_binding( void * xbinding, void * data )
{
    object_free( ( (BINDING *)xbinding )->binding );
}


void search_done( void )
{
    if ( explicit_bindings )
    {
        hashenumerate( explicit_bindings, free_binding, 0 );
        hashdone( explicit_bindings );
    }
}
