# Copyright 2017 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Rules that allows select() to differentiate between Apple OS versions."""

def _strip_version(version):
    """Strip trailing characters that aren't digits or '.' from version names.

    Some OS versions look like "9.0gm", which is not useful for select()
    statements. Thus, we strip the trailing "gm" part.

    Args:
      version: the version string

    Returns:
      The version with trailing letters stripped.
    """
    result = ""
    string = str(version)
    for i in range(len(string)):
        ch = string[i]
        if not ch.isdigit() and ch != ".":
            break

        result += ch

    return result

def _strip_or_pad_version(version, num_components):
    """Strips or pads a version string to the given number of components.

    If the version string contains fewer than the requested number of
    components, it will be padded with zeros.

    Args:
        version: The version string.
        num_components: The desired number of components.

    Returns:
        The version, stripped or padded to the requested number of components.
    """
    version_string = str(version)
    components = version_string.split(".")
    if num_components <= len(components):
        return ".".join(components[:num_components])
    return version_string + (".0" * (num_components - len(components)))

_VERSION_PRECISION_COMPONENTS = {
    "major": 1,
    "minor": 2,
    "patch": 3,
}

def _xcode_version_flag_impl(ctx):
    """A rule that allows select() to differentiate between Xcode versions."""
    xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig]
    xcode_version = xcode_config.xcode_version()
    precision = ctx.attr.precision

    if not precision:
        value = _strip_version(xcode_version)
    elif precision == "exact":
        value = str(xcode_version)
    else:
        num_components = _VERSION_PRECISION_COMPONENTS[precision]
        value = _strip_or_pad_version(xcode_version, num_components)

    return config_common.FeatureFlagInfo(value = value)

def _ios_sdk_version_flag_impl(ctx):
    """A rule that allows select() to select based on the iOS SDK version."""
    xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig]

    return config_common.FeatureFlagInfo(value = _strip_version(
        xcode_config.sdk_version_for_platform(
            apple_common.platform.ios_device,
        ),
    ))

def _tvos_sdk_version_flag_impl(ctx):
    """A rule that allows select() to select based on the tvOS SDK version."""
    xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig]

    return config_common.FeatureFlagInfo(value = _strip_version(
        xcode_config.sdk_version_for_platform(
            apple_common.platform.tvos_device,
        ),
    ))

def _watchos_sdk_version_flag_impl(ctx):
    """A rule that allows select() to select based on the watchOS SDK version."""
    xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig]

    return config_common.FeatureFlagInfo(value = _strip_version(
        xcode_config.sdk_version_for_platform(
            apple_common.platform.watchos_device,
        ),
    ))

def _macos_sdk_version_flag_impl(ctx):
    """A rule that allows select() to select based on the macOS SDK version."""
    xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig]

    return config_common.FeatureFlagInfo(value = _strip_version(
        xcode_config.sdk_version_for_platform(
            apple_common.platform.macos,
        ),
    ))

xcode_version_flag = rule(
    implementation = _xcode_version_flag_impl,
    attrs = {
        "precision": attr.string(
            doc = """\
The desired precision with which the version number will be provided. The
permitted values of this attribute are given below, with examples of the value
that the rule would provide if the selected `xcode_version` target reported
`version = "1.2.3.4X789"`:

-   `major`: Provide only the major component of the version number (e.g., `1`).
-   `minor`: Provide only the major and minor components of the version number
    (e.g., `1.2`).
-   `patch`: Provide only the major, minor, and patch components of the version
    number (e.g., `1.2.3`).
-   `exact`: Provide the version number reported by the `xcode_version` target
    unmodified (e.g., `1.2.3.4X789`).

If `minor` or `patch` precision is requested and the version number reported by
the `xcode_version` has a lower precision, the result will be padded with `.0`
segments as necessary to ensure that it is consistent. For example, if the
selected `xcode_version` reported `version = "1"`, then `minor` precision would
be `1.0` and `patch` precision would be `1.0.0`.

Setting the precision lets you write `config_setting`s that test for more or
less specific versions of Xcode. For example, if you want to handle all `11.*`
versions of Xcode identically but do something different for Xcode 12.0 and
12.1, you can express that as follows:

```
config_setting(
    name = "xcode_11_any",
    flag_values = {
        "//tools/osx:xcode_version_flag_major": "11",
    },
)

config_setting(
    name = "xcode_12_0",
    flag_values = {
        "//tools/osx:xcode_version_flag_minor": "12.0",
    },
)

config_setting(
    name = "xcode_12_1",
    flag_values = {
        "//tools/osx:xcode_version_flag_minor": "12.1",
    },
)
```
""",
            mandatory = False,
            values = ["major", "minor", "patch", "exact"],
        ),
        "_xcode_config": attr.label(default = configuration_field(
            fragment = "apple",
            name = "xcode_config_label",
        )),
    },
)

ios_sdk_version_flag = rule(
    implementation = _ios_sdk_version_flag_impl,
    attrs = {
        "_xcode_config": attr.label(default = configuration_field(
            fragment = "apple",
            name = "xcode_config_label",
        )),
    },
)

tvos_sdk_version_flag = rule(
    implementation = _tvos_sdk_version_flag_impl,
    attrs = {
        "_xcode_config": attr.label(default = configuration_field(
            fragment = "apple",
            name = "xcode_config_label",
        )),
    },
)

watchos_sdk_version_flag = rule(
    implementation = _watchos_sdk_version_flag_impl,
    attrs = {
        "_xcode_config": attr.label(default = configuration_field(
            fragment = "apple",
            name = "xcode_config_label",
        )),
    },
)

macos_sdk_version_flag = rule(
    implementation = _macos_sdk_version_flag_impl,
    attrs = {
        "_xcode_config": attr.label(default = configuration_field(
            fragment = "apple",
            name = "xcode_config_label",
        )),
    },
)
