#  Copyright (C) Vladimir Prus 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.


#   There are three kinds of targets: "abstract", which correspond to
#   targets explicitly defined in Jamfile; "virtual", which correspond
#   to possible build product with defined properties and "actual", which
#   are targets in Jam sense. The "virtual" targets are generated during
#   search for the best transformation sequence, and some of them can be
#   later 'actualized'.
#
#   Actual targets are all derived from 'abstract-target' class. First
#   abstract target is 'project-target', which is created for each
#   Jamfile, and can be obtained by rule 'target' in Jamfile's module.
#   (see project.jam). Project targets keep a list of 'main-target' instances.
#   Main target is what is explicitly defined in Jamfile. It is possible
#   to have several definition for a main target, for example to have
#   different list of sources on different platforms. So, main targets
#   keep a list of variants -- i.e. instances of 'abstract-target' class.
#   Precise type of variant can be anything  -- each rule that declares
#   main targets can do anything.
#
#   However, we'll use only targets derived from 'basic-target' class,
#   which will provide some default behaviour. Now, there's only one
#   class, 'make-target', which will be create by 'make' rule.


import sequence ;
import class : class ;
import regex ;
import property ;

# Base class for all abstract targets.
# local-name: name of the target in Jamfile
# project: the project module where the target is declared
#
# Note: it might seem that we don't need either name or project at all.
# However, there are places where we really need it. One example is error
# messages which should name problematic targets. Another is setting correct
# paths for sources and generated files.
rule abstract-target ( local-name : project )
{
    self.local-name = $(local-name) ;
    self.project = $(project) ;

    # Returns a user-readable name for this target.
    rule name ( )
    {
        local location = [ $(self.project).location ] ;
        return $(location)/$(self.local-name) ;
    }

    # Takes properties in split form ("<feature1>foo <feature2>bar").
    # Generates virtual targets for this abstract targets which are matching
    # 'properties' as closely as possible. It 'properties' are not specified,
    # default values are used. If it not possible to build anything because
    # of some problem returns a list with "@error" as the first element
    # and explanation in all others. (CONSIDER: need some utilities for
    # this method of error reporting? 'is-error'?)
    rule generate ( properties * )
    {
        # Yes, it is empty.
    }
}
class abstract-target ;

#  Project target class (derived from 'abstract-target')
rule project-target ( local-name : project : requirements * : default-build * )
{
    abstract-target.__init__ $(local-name) : $(project) ;

    self.requirements = $(requirements) ;
    self.default-build = $(default-build) ;

    # Generates all possible targets contained in this project.
    rule generate ( properties * )
    {
        local result ;
        for local name in $(self.main-targets)
        {
            local t = [ main-target $(name) ] ;
            result += [ $(t).generate $(properties) ] ;
        }
        for local pn in [ $(self.project).subprojects ]
        {
            local p = [ project.module-name $(pn) ] ;
            local t = [ $(p).target ] ;
            result += [ $(t).generate $(properties) ] ;
        }
        return $(result) ;
    }

    # Returns a 'main-target' class instance corresponding to the 'name'.
    # Creates the instance if needed.
    rule main-target ( name )
    {
        # FIXME
        import class : new ;
        if ! $(self.main-target<$(name)>)
        {
            self.main-targets += $(name) ;
            self.main-target<$(name)> =
                [ new main-target $(self.local-name) : $(self.project) ] ;
        }
        return $(self.main-target<$(name)>) ;
    }

}
class project-target : abstract-target ;

# A named top-level target in Jamfile
rule main-target ( name : project )
{
    import errors : error ;

    abstract-target.__init__ $(name) : $(project) ;

    # Add a new build variant for this target
    rule add-variant ( target )
    {
        self.variants += $(target) ;
    }

    # Select a variant return virtual targets that it generates
    rule generate ( properties * )
    {
        # At this stage we just try to generate each variant and if
        # more than one succeeds, consider it as an error.
        local alternatives ;

        for local v in $(self.variants)
        {
            local vtargets = [ $(v).generate $(properties) ] ;
            if $(vtargets) && $(vtargets[1]) != "@error"
            {
                alternatives += $(v) $(vtargets) "@" ;
            }
        }
        if ! $(alternatives)
        {
            # TODO: Should print a name
            error "No viable variant found for main target" [ name ] ;
        }
        # We have "@" at the end. Another one means two or more alts.
        if "@" in $(alternatives[1--2])
        {
            error "Ambiguous variants for main target" [ name ]  ;
        }
        # Now return virtual targets for the only alternative
        return $(alternatives[2--2]) ;
    }
}

class main-target : abstract-target ;

# Provides default implementation for building source targets and
# similar activities.
# Sources may be either files, or target ids. The former have the form
# location@project-id/target-id, or
# location@project-id/target-id/<feature1>foo
# The second variant specifies additional properties that should be used
# when building the target.
rule basic-target ( name : project
                    : sources * : requirements * : default-build * )
{
    abstract-target.__init__ $(name) : $(project) ;

    self.name = $(name) ;
    self.sources = $(sources) ;
    self.requirements = $(requirements) ;
    self.default-build = $(default-build) ;

    # Applies default-build if 'properties' are empty.
    # Generates sources. Calls 'construct'
    # This method should not be overriden.
    #
    # Note: historical perspectives of this rule are not clear....
    # since generators will be allowed to change requirements as they
    # search from targets to sources, we won't be able to call
    # generate on sources right here, because we don't know properties
    # that should be used.
    rule generate ( properties * )
    {
        if ! $(properties)
        {
            # CONSIDER: I'm really not sure if this is correct...
            properties = [ build-request.expand $(self.default-build) ] ;
            for local p in $(properties)
            {
                generate [ feature.split $(p) ] ;
            }
        }

        local rproperties =
            [ property.refine $(properties) : $(self.requirements) ] ;
        if $(rproperties[1]) = "@error"
        {
            print.wrapped-text
                "Cannot satisfy request to build" [ name ]
                "with properties " $(properties) ;
            print.wrapped-text
                "Explanation:" $(rproperties[2-]) ;
            EXIT ;
        }
        # TODO: issue a warning when requirements change properties, but
        # link-compatibility is still not broken.

        local source-targets ;
        for local s in $(self.sources)
        {
            # This assumes property list never contain "@".
            if [ MATCH .*@.* $(s) ]
            {
                # Reference to other main target
                source-targets += [ recurse $(s) $(properties) ] ;
            }
            else
            {
                # Just a source file
                source-targets +=
                    [ new virtual-target $(self.name) : $(self.project) ] ;
            }
        }
        return [ construct $(source-targets) : $(properties) ] ;
    }

    # Given a source specification, generates virtual targets for that source.
    rule recurse ( source : properties * )
    {
        # Separate target name from properties override
        local split = [ feature.split $(source) ] ;
        local id = $(split[1]) ;
        local sproperties = $(split[2-]) ;

        # Apply source-specific properties
        local rproperties = [ property.refine $(properties) : $(sproperties) ] ;
        if $(rproperties[1]) = "@error"
        {
            error
                "When building" [ name ] " with properties " $(properties)
                "Invalid properties specified for " $(source) ":"
                $(rproperties[2-]) ;
        }

        # Try to generate source
        local target = [ project.find-target $(id) ] ;
        return [ $(target).generate $(rproperties) ] ;
    }

    # Constructs the virtual targets for this abstract targets and
    # the dependecy graph. Returns the list of virtual targets.
    # Should be overrided in derived classes.
    rule construct ( source-targets * : properties * )
    {
    }

}

class basic-target : abstract-target ;


# Class which represents a virtual target
rule virtual-target ( name : project : subvariant * )
{
    self.name = $(name) ;
    self.subvariant = $(subvariant) ;
    self.project = $(project) ;

    self.includes  = ;
    self.dependencies = ;
    self.action = ;

    self.actual-name = ;


    # Name of the target
    rule name ( ) { return $(self.name) ; }

    # Property set that distinguished different variants of a target.
    # May be a subset of the property set that is used for building.
    # Determines the location of target, in an unspecified way.
    rule subvariant ( ) { return $(self.subvariant) ; }

    # Project where this target was declared
    rule project ( ) { return $(self.project) ; }

    rule includes ( ) { return $(self.includes) ; }
    rule add_includes ( i + )
    {
        self.includes = [ sequence.merge $(self.includes)
                            : [ sequence.insertion-sort $(i) ] ] ;
    }

    rule dependencies ( ) { return $(self.dependencies) ; }
    rule depends ( d + )
    {
        self.dependencies = [ sequence.merge $(self.dependencies)
                               : [ sequence.insertion-sort $(d) ] ] ;
    }

    # If 'a' is supplied, sets action to 'a'.
    # Returns the action currently set.
    rule action ( a ? )
    {
        if $(a) {
            self.action = $(a) ;
        }
        return $(self.action) ;
    }

    # Generates all the actual targets and build instructions needed to build
    # this target. Returns the actual target name. Can be called several times.
    # Does no processing for other targets that 'action' will generate.
    # Rationale: we might need only one file from the set created by an
    # action, and there's no need to run the action if the file is up-to-date,
    # only because some other file in set is out-of-date.
    rule actualize ( )
    {
        ECHO "Actualizing target $(self.name)" ;
        if ! $(self.actual-name) {

            self.actual-name = [ actual-name ] ;
            for local i in $(dependencies) {
                DEPENDS $(name) : [ $(i).actualize ] ;
            }
            for local i in $(includes) {
                Includes $(name) : [ $(i).actualize ] ;
            }
            local a = [ action ] ;
            if $(a) {
                $(a).actualize ;
                local path = [ os.path.join [ $(self.project).location ] "bin" ] ;
                path = [ os.path.native $(path) ] ;
                MakeLocate $(self.actual-name) : $(path) ;

            } else {
                SEARCH on $(self.actual-name) =
                    [ os.path.native [ $(self.project).source-location ] ] ;
            }
        }
        return $(self.actual-name) ;
    }

# private:
    rule actual-name ( )
    {
        if ! $(self.actual-name)
        {
            local project-location = [ $(self.project).location ] ;
            local location-grist =
                [ sequence.join [ regex.split $(project-location) "/" ] : "!" ] ;
            local property-grist =
                [ property.as-path $(self.subvariant) ] ;
            local grist = $(location-grist)/$(property-grist) ;
            self.actual-name = <$(grist)>$(self.name) ;
        }
        ECHO "Actual name will be " $(self.actual-name) ;
        return $(self.actual-name) ;
    }
}
class virtual-target ;

# Class which represents an action.
# Both 'targets' and 'sources' should list instances of 'virtual-target'.
# Action name should name a rule with this prototype
#     rule action_name ( targets + : sources * : properties * )
# Targets and sources are passed as actual jam targets. The rule may
# not establish dependency relationship, but should do everything else.
rule action ( targets + : sources * : action_name : properties * )
{
    self.targets = $(targets) ;
    self.sources = $(sources) ;
    self.action_name = $(action_name) ;
    self.properties = $(properties) ;

    rule targets ( )
    {
        return $(self.targets) ;
    }

    rule sources ( )
    {
        return $(self.sources) ;
    }

    rule action_name ( )
    {
        return $(self.action_name) ;
    }

    rule properties ( )
    {
        return $(self.properties) ;
    }

    # Generates actual build instructions.
    rule actualize ( )
    {
        local actual_targets ;
        for local i in [ targets ]
        {
            actual_targets += [ $(i).actualize ] ;
        }

        local actual_sources ;
        for local i in [ sources ]
        {
            actual_sources += [ $(i).actualize ] ;
        }

        DEPENDS $(actual_targets) : $(actual_sources) ;

        $(self.action_name)
            $(actual_targets) : $(actual_sources) : [ properties ] ;
    }
}
class action ;


