#
# SPDX-License-Identifier: GPL-2.0-only
#
"""OpenEmbedded variable typing support

Types are defined in the metadata by name, using the 'type' flag on a
variable.  Other flags may be utilized in the construction of the types.  See
the arguments of the type's factory for details.
"""

import inspect
import oe.types as types
try:
    # Python 3.7+
    from collections.abc import Callable
except ImportError:
    # Python < 3.7
    from collections import Callable

available_types = {}

class MissingFlag(TypeError):
    """A particular flag is required to construct the type, but has not been
    provided."""
    def __init__(self, flag, type):
        self.flag = flag
        self.type = type
        TypeError.__init__(self)

    def __str__(self):
        return "Type '%s' requires flag '%s'" % (self.type, self.flag)

def factory(var_type):
    """Return the factory for a specified type."""
    if var_type is None:
        raise TypeError("No type specified. Valid types: %s" %
                        ', '.join(available_types))
    try:
        return available_types[var_type]
    except KeyError:
        raise TypeError("Invalid type '%s':\n  Valid types: %s" %
                        (var_type, ', '.join(available_types)))

def create(value, var_type, **flags):
    """Create an object of the specified type, given the specified flags and
    string value."""
    obj = factory(var_type)
    objflags = {}
    for flag in obj.flags:
        if flag not in flags:
            if flag not in obj.optflags:
                raise MissingFlag(flag, var_type)
        else:
            objflags[flag] = flags[flag]

    return obj(value, **objflags)

def get_callable_args(obj):
    """Grab all but the first argument of the specified callable, returning
    the list, as well as a list of which of the arguments have default
    values."""
    if type(obj) is type:
        obj = obj.__init__

    sig = inspect.signature(obj)
    args = list(sig.parameters.keys())
    defaults = list(s for s in sig.parameters.keys() if sig.parameters[s].default != inspect.Parameter.empty)
    flaglist = []
    if args:
        if len(args) > 1 and args[0] == 'self':
            args = args[1:]
        flaglist.extend(args)

    optional = set()
    if defaults:
        optional |= set(flaglist[-len(defaults):])
    return flaglist, optional

def factory_setup(name, obj):
    """Prepare a factory for use."""
    args, optional = get_callable_args(obj)
    extra_args = args[1:]
    if extra_args:
        obj.flags, optional = extra_args, optional
        obj.optflags = set(optional)
    else:
        obj.flags = obj.optflags = ()

    if not hasattr(obj, 'name'):
        obj.name = name

def register(name, factory):
    """Register a type, given its name and a factory callable.

    Determines the required and optional flags from the factory's
    arguments."""
    factory_setup(name, factory)
    available_types[factory.name] = factory


# Register all our included types
for name in dir(types):
    if name.startswith('_'):
        continue

    obj = getattr(types, name)
    if not isinstance(obj, Callable):
        continue

    register(name, obj)
