#  Copyright (C) Vladimir Prus and Rene Rivera 2002.
#  Permission to copy, use, modify, sell and
#  distribute this software is granted provided this copyright notice appears in
#  all copies. This software is provided "as is" without express or implied
#  warranty, and with no claim as to its suitability for any purpose.

#   Each subproject is represented by a module with name Jamfile<jamfile-path>.
#   The module interface is:
#
#       rule location ( )
#       rule id ( )
#       rule project-root ( )
#       rule parent ( )
#       rule requirements ( )
#       rule default-build ( )
#       rule source-location ( )
#       rule target ( ) -- returns the 'project-target' for this project.

#       rule subprojects ( )
#
#   Targets defined in Jamfile, as well as target representing the entire
#   Jamfile will be available using facilities in the 'targets' module.
#

import modules : peek poke ;
import numbers ;
import os.path ;
import sequence ;
import targets ;
import errors : error ;
import project-root ;
import print ;


#
#   Loads jamfile at the given location. After loading, project global
#   file and jamfile needed by the loaded one will be loaded recursively.
#
rule load ( jamfile-location )
{
    local loaded = ;
    local module-name = [ load-jamfile $(jamfile-location) loaded ] ;

    if $(loaded)
    {
        .projects += $(jamfile-location) ;

        for local subproject in [ $(module-name).subprojects ]
        {
            load [ os.path.join $(jamfile-location) $(subproject) ] ;
        }
    }
    return $(module-name) ;
}

#
#   Returns the name of project module given its id.
#   Projects can be referred using path@project-id notation. In it, 'path'
#   selects jamfile location relatively to 'current-location' and 'project-id'
#   names project relatively to the selected jamfile.
#   Rooted 'project-id' is possible:
#   "@/boost" will refer to the top-level project called "boost".
#
rule lookup ( id : current-location )
{
    local split = [ MATCH (.*)@(.*) : $(id)  ] ;
    local location = $(split[1]) ;
    # A jam quirk: if there's no part before "@", 'location' will be empty
    # string, and ?= won't change it.
    if $(location)
    {
        location =
            [ os.path.root $(location) $(current-location) ] ;
    }
    else
    {
        location = $(current-location) ;
    }

    local project-id = $(split[2]) ;

    if [ os.path.is-rooted $(project-id) ]
    {
        return $($(project-id).jamfile-location) ;
    }
    else
    {
        if ! $(location)
        {
            error Jamfile location must be specified for relative project-id $(id) ;
        }

        local module-name = [ module-name $(location) ] ;
        local base-id = [ $(module-name).id ] ;

        if ! $(base-id)
        {
            error "Project in $(location) has no project id" ;
        }

        if $(project-id)
        {
            local rooted-id = $(base-id)/$(project-id) ;
            return $($(rooted-id).jamfile-location) ;
        }
        else
        {
            return $($(base-id).jamfile-location) ;
        }
    }
}

# Given an 'id' for a target, return an instance of 'main-target' that
# corresponds to it.
rule find-target ( id : current-location )
{
    error "Not yet implemented" ;
}

rule project ( id ? : option1 * : option2 * : option3 * )
{
    local caller = [ CALLER_MODULE ] ;
    $(id).jamfile-location = [ $(caller).location ] ;

    poke $(caller) : __id__ : $(id) ;

    if $(option1) {
        assign-option [ CALLER_MODULE ] : $(option1) ;
    }
    if $(option2) {
        assign-option [ CALLER_MODULE ] : $(option2) ;
    }
    if $(option3) {
        assign-option [ CALLER_MODULE ] : $(option3) ;
    }
}

rule assign-option ( module : option + )
{
    local first = $(option[1]) ;
    local tail = $(option[2-]) ;

    switch $(first) {
    case "requirements" :
        local inherited = [ peek $(module) : __requirements__ ] ;
        local specified = $(tail) ;
        local result = [ property.refine $(inherited) : $(specified) ] ;

        if $(result[1]) = "@error"
        {
            local location = [ $(module).location ] ;
            print.wrapped-text
                "Requirements for project at '$(location)'"
                "conflict with parent's." ;
            print.wrapped-text
                "Explanation: " $(result[2-]) ;
            EXIT ;
        }
        else
        {
            poke $(module) : __requirements__ : $(result) ;
        }

    case "default-build" :
         poke $(module) : __default-build__ : $(tail) ;
    case "source-location" :
         poke $(module) : __source-location__
              : [ os.path.join [ $(module).location ] $(tail) ] ;
    case * :
         print.wrapped-text "Invalid project option '$(first)' specified "
                            "for project '$(module)'" ;
         EXIT ;
    }
}


#
# Returns the name of module corresponding to 'jamfile-location'.
#
rule module-name ( jamfile-location )
{
    return Jamfile<$(jamfile-location)> ;
}

# Default patterns to search for the Jamfiles to use for build
# declarations.
#
JAMFILE = [ modules.peek : JAMFILE ] ;
JAMFILE ?= [Jj]amfile [Jj]amfile.jam ;

# Find the Jamfile at the given location. This returns the exact names of
# all the Jamfiles in the given directory. The optional parent-root argument
# causes this to search not the given directory but the ones above it up
# to the directory given in it.
#
local rule find-jamfile (
    dir # The directory(s) to look for a Jamfile.
    parent-root ? # Optional flag indicating to search for the parent Jamfile.
    )
{
    # Glob for all the possible Jamfiles according to the match pattern.
    #
    local jamfile-glob = ;
    if $(parent-root)
    {
        if $(parent-root) != $(dir) {
            # 'find-to-root' does not handle relative paths gracefully,
            # so use os.path module

            local parent-dirs = [ os.path.all-parents $(dir) : $(parent-root) ] ;
            # Remove dir and add parent-root to the list of searched dirs
            parent-dirs = $(parent-dirs[2-]) $(parent-root) ;

            while $(parent-dirs) && ! $(jamfile-glob)
            {
                jamfile-glob = [ os.path.glob $(parent-dirs[1]) : $(JAMFILE) ] ;
                parent-dirs = $(parent-dirs[2-]) ;
            }
        }
    }
    else
    {
        jamfile-glob = [ os.path.glob $(dir) : $(JAMFILE) ] ;
    }

    return $(jamfile-glob) ;
}

# Load a Jamfile at the given directory. Will attempt to load
# the file as indicated by the JAMFILE patterns. We return the
# module for the Jamfile.
#
local rule load-jamfile (
    dir # The directory of the project Jamfile.
    loaded-var ? # Name of variable to indicated we loaded the Jamfile.
    )
{
    # See if the Jamfile is where it should be.
    #
    local jamfile-to-load = [ find-jamfile $(dir) ] ;

    # Could not find it, error.
    #
    if ! $(jamfile-to-load)
    {
        EXIT
            "Unable to load Jamfile. Could not find a Jamfile in"
            "this directory:" $(dir)"." "Attempted to find it with"
            "this pattern:" $(JAMFILE)"."
            "Please consult the documentation at 'http://www.boost.org'." ;
    }

    # The module of the jamfile.
    #
    local jamfile-module = [ module-name  [ os.path.parent $(jamfile-to-load[1]) ] ] ;

    # Don't even bother with the rest if we know the file is already loaded.
    #
    if ! [ modules.binding $(jamfile-module) ]
    {
        # Multiple Jamfiles found in the same place. Warn about this.
        # And ensure we use only one of them.
        #
        if $(jamfile-to-load[2-])
        {
            ECHO
                "WARNING: Found multiple Jamfiles at this '"$(dir)"' location!"
                "Loading the first one: '" [ os.path.basename $(jamfile-to-load[1]) ]  "'." ;
        }

        jamfile-to-load = $(jamfile-to-load[1]) ;

        # Initialize the jamfile module before loading.
        #
        initialize $(jamfile-module) : $(jamfile-to-load) ;

        # Setup, by coordinating with project-root.
        #
        local project-root-module = [ $(jamfile-module).project-root-module ] ;
        $(project-root-module).register-project $(jamfile-module) ;

        # Now load the Jamfile in it's own context.
        #
        modules.load $(jamfile-module) :  [ os.path.native $(jamfile-to-load) ] : . ;

        # Indicate we loaded the Jamfile.
        #
        if $(loaded-var)
        {
            $(loaded-var) = true ;
        }
    }

    # Return the Jamfile's filename/module.
    #
    return $(jamfile-module) ;
}

# Initialize the module for a Jamfile.
#
local rule initialize (
    module-name # The name of the Jamfile module.
    : jamfile # The location (binding) of the jamfile for the project to initialize.
    )
{
    # Make sure we've loaded the project-root corresponding to this
    # Jamfile.
    #
    local project-root-module = [ project-root.load [ os.path.parent $(jamfile) ] ] ;
    local project-root = [ $(project-root-module).location ] ;

    local parent = [ find-jamfile [ os.path.parent $(jamfile) ] $(project-root) ] ;
    local parent-module = ;
    if $(parent)
    {
        parent-module = [ load [ os.path.parent $(parent[1]) ] ] ;
    }

    module $(module-name)
    {
        import project : project ;
    }


    # Import rules common to all project modules from project-rules module,
    # define at the end of this file.
    modules.clone-rules project-rules $(module-name) ;

    modules.poke $(module-name) : __jamfile-location__ : $(jamfile-location) ;
    modules.poke $(module-name) : __source-location__ : $(jamfile-location) ;
    modules.poke $(module-name) : __project-root__ : $(project-root) ;
    modules.poke $(module-name) : __project-root-module__ : $(project-root-module) ;
    if $(parent-module)
    {
        modules.poke $(module-name) : __parent__ : [ os.path.parent $(parent) ] ;
        modules.poke $(module-name) : __default-build__ : [ $(parent-module).default-build ] ;
        modules.poke $(module-name) : __requirements__ : [ $(parent-module).requirements ] ;
    }
    else
    {
        modules.poke $(module-name) : __default-build__ : debug ;
    }
}

# This module defines rules common to all projects
module project-rules {

    rule location ( )
    {
        return $(__jamfile-location__) ;
    }

    rule id ( )
    {
        return $(__id__) ;
    }

    rule project-root ( )
    {
        return $(__project-root__) ;
    }

    rule project-root-module ( )
    {
        return $(__project-root-module__) ;
    }

    rule parent ( )
    {
        return $(__parent__) ;
    }

    rule requirements ( )
    {
        return $(__requirements__) ;
    }

    rule default-build ( )
    {
        return $(__default-build__) ;
    }

    rule source-location ( )
    {
        return $(__source-location__) ;
    }

    rule subproject ( jamfile-location )
    {
        __subprojects__ += $(jamfile-location) ;
    }

    rule subprojects ( )
    {
        return $(__subprojects__) ;
    }

    rule target ( )
    {
        # FIXME: this should be done better!
        import class : new ;
        if ! $(__target__)
        {
            __target__ = [ new project-target "<project>" : $(__name__) ] ;
        }
        return $(__target__) ;
    }

    rule print ( )
    {
        import sequence ;
        import print ;

        local id = [ id ] ; id ?= (none) ;
        local parent = [ parent ] ; parent ?= (none) ;
        print.section "'"$(id)"'" ;
        print.list-start ;
        print.list-item "Project root:" [ project-root ] ;
        print.list-item "Parent project:" $(parent) ;
        print.list-item "Requirements:" [ requirements ] ;
        print.list-item "Default build:" [ default-build ] ;
        print.list-item "Source location:" [ source-location ] ;
        print.list-item "Subprojects:" [ sequence.insertion-sort [ subprojects ] ] ;
        print.list-end ;
    }
}


