#  (C) Copyright David Abrahams 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.

import sequence ;
import set ;
import regex ;
import feature ;
import numbers ;

# Transform property-set by applying f to each component property.
local rule apply-to-property-set ( f property-set )
{
    local properties = [ feature.split $(property-set) ] ;
    return [ string.join [ $(f) $(properties) ] : / ] ;
}

# expand the given build request by combining all property-sets which don't
# specify conflicting non-free features.
rule expand ( property-sets * : feature-space ? )
{
    feature-space ?= feature ;
    
    # First make all features and subfeatures explicit
    local expanded-property-sets = [ 
      sequence.transform apply-to-property-set $(feature-space).expand-subfeatures
        : $(property-sets) ] ;
    
    # Now combine all of the expanded property-sets
    local product = [ x-product $(expanded-property-sets) : $(feature-space) ] ;
    
    return [ 
      sequence.transform apply-to-property-set $(feature-space).expand-composites
        : $(product) ] ;
}

local rule x-product-aux ( property-sets + : feature-space )
{
    local result ;
    local p = [ feature.split $(property-sets[1]) ] ;
    local f = [ set.difference $(p:G) : [ $(feature-space).free-features ] ] ;
    local seen ;
    # No conflict with things used at a higher level?
    if ! [ set.intersection $(f) : $(x-product-used) ]
    {
        local x-product-seen ;
        {
            # don't mix in any conflicting features
            local x-product-used = $(x-product-used) $(f) ;
            
            if $(property-sets[2])
            {
                local rest = [ x-product-aux $(property-sets[2-]) : $(feature-space) ] ;
                result = $(property-sets[1])/$(rest) ;
            }
            
            result ?= $(property-sets[1]) ;
        }
        
        # If we didn't encounter a conflicting feature lower down,
        # don't recurse again.
        if ! [ set.intersection $(f) : $(x-product-seen) ]
        {
            property-sets = ;
        }
        
        seen = $(x-product-seen) ;
    }
    
    if $(property-sets[2])
    {
        result += [ x-product-aux $(property-sets[2-]) : $(feature-space) ] ;
    }
    
    # Note that we've seen these features so that higher levels will
    # recurse again without them set.
    x-product-seen += $(f) $(seen) ;
    return $(result) ;
}

local rule x-product ( property-sets * : feature-space )
{
    local x-product-seen x-product-used ;
    return [ x-product-aux $(property-sets) : $(feature-space) ] ;
}

local rule __test__ ( )
{
    import assert ;
    import errors : try catch ;
    import class ;
    
    local test-space = [ class.new feature-space ] ;
    
    module $(test-space)
    {
        local test-space = [ modules.peek build-request : test-space ] ;
        
        import build-request ;

        feature toolset : gcc msvc : implicit ;
        subfeature toolset gcc : version : 2.95.2 2.95.3 2.95.4
          3.0 3.0.1 3.0.2 ;

        feature variant : debug release : implicit composite ;
        feature inlining : on off ;
        feature "include" : : free ;
        
        feature stdlib : native stlport : implicit ;
        
        assert.result
          <toolset>gcc/<toolset-version>3.0.1/<stdlib>stlport/<variant>debug
          <toolset>msvc/<stdlib>stlport/<variant>debug
          <toolset>msvc/<variant>debug 
          
          : build-request.expand gcc-3.0.1/stlport msvc/stlport msvc debug
          : $(test-space) ;
        
        assert.result
          <toolset>gcc/<toolset-version>3.0.1/<stdlib>stlport/<variant>debug
          <toolset>msvc/<variant>debug 
          <variant>debug/<toolset>msvc/<stdlib>stlport
          
          : build-request.expand gcc-3.0.1/stlport msvc debug msvc/stlport
          : $(test-space) ;
        
        assert.result
          <toolset>gcc/<toolset-version>3.0.1/<stdlib>stlport/<variant>debug/<inlining>off
          <toolset>gcc/<toolset-version>3.0.1/<stdlib>stlport/<variant>release/<inlining>off
          
          : build-request.expand gcc-3.0.1/stlport debug release <inlining>off
          : $(test-space) ;        

        assert.result
          <include>a/b/c/<toolset>gcc/<toolset-version>3.0.1/<stdlib>stlport/<variant>debug/<include>x/y/z
          <include>a/b/c/<toolset>msvc/<stdlib>stlport/<variant>debug/<include>x/y/z
          <include>a/b/c/<toolset>msvc/<variant>debug/<include>x/y/z 
          
          : build-request.expand <include>a/b/c gcc-3.0.1/stlport msvc/stlport msvc debug  <include>x/y/z
          : $(test-space) ;
    }
}


