# Status: being ported by Steven Watanabe
# Base revision: 47077
#
#  Copyright (C) Andre Hentz 2003. 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.
#
#  Copyright (c) 2006 Rene Rivera.
#
#  Copyright (c) 2008 Steven Watanabe
#
#  Use, modification and distribution is subject to the Boost Software
#  License Version 1.0. (See accompanying file LICENSE_1_0.txt or
#  http://www.boost.org/LICENSE_1_0.txt)

##import type ;
##import generators ;
##import feature ;
##import errors ;
##import scanner ;
##import toolset : flags ;

import os.path
import re

import bjam

from b2.build import type, toolset, generators, scanner, feature
from b2.exceptions import AlreadyDefined
from b2.tools import builtin
from b2.util import regex
from b2.build.toolset import flags
from b2.manager import get_manager
from b2.util import utility

__debug = None

def debug():
    global __debug
    if __debug is None:
        __debug = "--debug-configuration" in bjam.variable("ARGV")
    return __debug

type.register('RC', ['rc'])

def init():
    pass

def configure (command = None, condition = None, options = None):
    """
        Configures a new resource compilation command specific to a condition,
        usually a toolset selection condition. The possible options are:

            * <rc-type>(rc|windres) - Indicates the type of options the command
              accepts.

        Even though the arguments are all optional, only when a command, condition,
        and at minimum the rc-type option are given will the command be configured.
        This is so that callers don't have to check auto-configuration values
        before calling this. And still get the functionality of build failures when
        the resource compiler can't be found.
    """
    rc_type = feature.get_values('<rc-type>', options)
    if rc_type:
        assert(len(rc_type) == 1)
        rc_type = rc_type[0]

    if command and condition and rc_type:
        flags('rc.compile.resource', '.RC', condition, command)
        flags('rc.compile.resource', '.RC_TYPE', condition, [rc_type.lower()])
        flags('rc.compile.resource', 'DEFINES', [], ['<define>'])
        flags('rc.compile.resource', 'INCLUDES', [], ['<include>'])
        if debug():
            print 'notice: using rc compiler ::', condition, '::', command

engine = get_manager().engine()

class RCAction:
    """Class representing bjam action defined from Python.
    The function must register the action to execute."""

    def __init__(self, action_name, function):
        self.action_name = action_name
        self.function = function

    def __call__(self, targets, sources, property_set):
        if self.function:
            self.function(targets, sources, property_set)

# FIXME: What is the proper way to dispatch actions?
def rc_register_action(action_name, function = None):
    global engine
    if action_name in engine.actions:
        raise AlreadyDefined("Bjam action %s is already defined" % action_name)
    engine.actions[action_name] = RCAction(action_name, function)

def rc_compile_resource(targets, sources, properties):
    rc_type = bjam.call('get-target-variable', targets, '.RC_TYPE')
    rc_type = rc_type[0] if rc_type else ''
    global engine
    engine.set_update_action('rc.compile.resource.' + rc_type, targets, sources, properties)

rc_register_action('rc.compile.resource', rc_compile_resource)


engine.register_action(
    'rc.compile.resource.rc',
    '"$(.RC)" -l 0x409 "-U$(UNDEFS)" "-D$(DEFINES)" -I"$(>:D)" -I"$(<:D)" -I"$(INCLUDES)" -fo "$(<)" "$(>)"')

engine.register_action(
    'rc.compile.resource.windres',
    '"$(.RC)" "-U$(UNDEFS)" "-D$(DEFINES)" -I"$(>:D)" -I"$(<:D)" -I"$(INCLUDES)" -o "$(<)" -i "$(>)"')

# FIXME: this was originally declared quietly
engine.register_action(
    'compile.resource.null',
    'as /dev/null -o "$(<)"')

# Since it's a common practice to write
# exe hello : hello.cpp hello.rc
# we change the name of object created from RC file, to
# avoid conflict with hello.cpp.
# The reason we generate OBJ and not RES, is that gcc does not
# seem to like RES files, but works OK with OBJ.
# See http://article.gmane.org/gmane.comp.lib.boost.build/5643/
#
# Using 'register-c-compiler' adds the build directory to INCLUDES
# FIXME: switch to generators
builtin.register_c_compiler('rc.compile.resource', ['RC'], ['OBJ(%_res)'], [])

__angle_include_re = "#include[ ]*<([^<]+)>"

# Register scanner for resources
class ResScanner(scanner.Scanner):

    def __init__(self, includes):
        scanner.__init__ ;
        self.includes = includes

    def pattern(self):
        return "(([^ ]+[ ]+(BITMAP|CURSOR|FONT|ICON|MESSAGETABLE|RT_MANIFEST)" +\
               "[ ]+([^ \"]+|\"[^\"]+\"))|(#include[ ]*(<[^<]+>|\"[^\"]+\")))" ;

    def process(self, target, matches, binding):
        binding = binding[0]
        angle = regex.transform(matches, "#include[ ]*<([^<]+)>")
        quoted = regex.transform(matches, "#include[ ]*\"([^\"]+)\"")
        res = regex.transform(matches,
                              "[^ ]+[ ]+(BITMAP|CURSOR|FONT|ICON|MESSAGETABLE|RT_MANIFEST)" +\
                              "[ ]+(([^ \"]+)|\"([^\"]+)\")", [3, 4])

        # Icons and other includes may referenced as
        #
        # IDR_MAINFRAME ICON "res\\icon.ico"
        #
        # so we have to replace double backslashes to single ones.
        res = [ re.sub(r'\\\\', '/', match) for match in res if match is not None ]

        # CONSIDER: the new scoping rule seem to defeat "on target" variables.
        g = bjam.call('get-target-variable', target, 'HDRGRIST')[0]
        b = os.path.normpath(os.path.dirname(binding))

        # Attach binding of including file to included targets.
        # When target is directly created from virtual target
        # this extra information is unnecessary. But in other
        # cases, it allows to distinguish between two headers of the
        # same name included from different places.
        # We don't need this extra information for angle includes,
        # since they should not depend on including file (we can't
        # get literal "." in include path).
        g2 = g + "#" + b

        g = "<" + g + ">"
        g2 = "<" + g2 + ">"
        angle = [g + x for x in angle]
        quoted = [g2 + x for x in quoted]
        res = [g2 + x for x in res]

        all = angle + quoted

        bjam.call('mark-included', target, all)

        engine = get_manager().engine()

        engine.add_dependency(target, res)
        bjam.call('NOCARE', all + res)
        engine.set_target_variable(angle, 'SEARCH', [utility.get_value(inc) for inc in self.includes])
        engine.set_target_variable(quoted, 'SEARCH', [b + utility.get_value(inc) for inc in self.includes])
        engine.set_target_variable(res, 'SEARCH', [b + utility.get_value(inc) for inc in self.includes])

        # Just propagate current scanner to includes, in a hope
        # that includes do not change scanners.
        get_manager().scanners().propagate(self, angle + quoted)

scanner.register(ResScanner, 'include')
type.set_scanner('RC', ResScanner)
