# Copyright 2018 Google LLC
#
# 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.

from typing import Any, Callable, Mapping, Sequence
import types
from inspect import Parameter, Signature


class KwParameter(Parameter):
    def __init__(self, name, default=Parameter.empty, annotation=Parameter.empty):
        super().__init__(name, Parameter.POSITIONAL_OR_KEYWORD, default=default, annotation=annotation)


def create_function_from_parameter_names(func: Callable[[Mapping[str, Any]], Any], parameter_names: Sequence[str], documentation=None, func_name=None, func_filename=None):
    return create_function_from_parameters(func, [KwParameter(name) for name in parameter_names], documentation, func_name, func_filename)


def create_function_from_parameters(func: Callable[[Mapping[str, Any]], Any], parameters: Sequence[Parameter], documentation=None, func_name=None, func_filename=None):
    new_signature = Signature(parameters) # Checks the parameter consistency
    
    def pass_locals():
        return dict_func(locals())

    code = pass_locals.__code__
    mod_co_argcount = len(parameters)
    mod_co_nlocals = len(parameters)
    mod_co_varnames = tuple(param.name for param in parameters)
    mod_co_name = func_name or code.co_name
    if func_filename:
        mod_co_filename = func_filename
        mod_co_firstlineno = 1
    else:
        mod_co_filename = code.co_filename
        mod_co_firstlineno = code.co_firstlineno

    modified_code = types.CodeType(
        mod_co_argcount,
        code.co_kwonlyargcount,
        mod_co_nlocals,
        code.co_stacksize,
        code.co_flags,
        code.co_code,
        code.co_consts,
        code.co_names,
        mod_co_varnames,
        mod_co_filename,
        mod_co_name,
        mod_co_firstlineno,
        code.co_lnotab
    )
    
    default_arg_values = tuple( p.default for p in parameters if p.default != Parameter.empty ) #!argdefs "starts from the right"/"is right-aligned"
    modified_func = types.FunctionType(modified_code, {'dict_func': func, 'locals': locals}, name=func_name, argdefs=default_arg_values)
    modified_func.__doc__ = documentation
    modified_func.__signature__ = new_signature
    
    return modified_func
