#    Copyright (c) 2014 Mirantis, Inc.
#
#    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.

import itertools
import types

import eventlet
import yaql.context
import yaql.exceptions

import muranoapi.dsl.exceptions as exceptions
import muranoapi.dsl.helpers as helpers
import muranoapi.dsl.murano_object as murano_object


def _resolve(name, obj):
    @yaql.context.EvalArg('this', murano_object.MuranoObject)
    @yaql.context.ContextAware()
    def invoke(context, this, *args):
        try:
            executor = helpers.get_executor(context)
            murano_class = helpers.get_type(context)
            return executor.invoke_method(name, this, context,
                                          murano_class, *args)
        except exceptions.NoMethodFound:
            raise yaql.exceptions.YaqlExecutionException()
        except exceptions.AmbiguousMethodName:
            raise yaql.exceptions.YaqlExecutionException()

    if not isinstance(obj, murano_object.MuranoObject):
        return None

    return invoke


@yaql.context.EvalArg('value', murano_object.MuranoObject)
def _id(value):
    return value.object_id


@yaql.context.EvalArg('value', murano_object.MuranoObject)
@yaql.context.EvalArg('type', str)
@yaql.context.ContextAware()
def _cast(context, value, type):
    if not '.' in type:
        murano_class = context.get_data('$?type')
        type = murano_class.namespace_resolver.resolve_name(type)
    class_loader = helpers.get_class_loader(context)
    return value.cast(class_loader.get_class(type))


@yaql.context.EvalArg('name', str)
@yaql.context.ContextAware()
def _new(context, name, *args):
    murano_class = helpers.get_type(context)
    name = murano_class.namespace_resolver.resolve_name(name)
    parameters = {}
    arg_values = [t() for t in args]
    if len(arg_values) == 1 and isinstance(
            arg_values[0], types.DictionaryType):
        parameters = arg_values[0]
    elif len(arg_values) > 0:
        for p in arg_values:
            if not isinstance(p, types.TupleType) or \
                    not isinstance(p[0], types.StringType):
                    raise SyntaxError()
            parameters[p[0]] = p[1]

    object_store = helpers.get_object_store(context)
    class_loader = helpers.get_class_loader(context)
    new_context = yaql.context.Context(parent_context=context)
    for key, value in parameters.iteritems():
        new_context.set_data(value, key)
    return class_loader.get_class(name).new(
        None, object_store, new_context, parameters=parameters)


@yaql.context.EvalArg('value', murano_object.MuranoObject)
def _super(value):
    return [value.cast(type) for type in value.type.parents]


@yaql.context.EvalArg('value', murano_object.MuranoObject)
def _super2(value, func):
    return itertools.imap(func, _super(value))


@yaql.context.EvalArg('value', murano_object.MuranoObject)
def _psuper2(value, func):
    helpers.parallel_select(_super(value), func)


@yaql.context.EvalArg('value', object)
def _require(value):
    if value is None:
        raise ValueError()
    return value


@yaql.context.EvalArg('obj', murano_object.MuranoObject)
@yaql.context.EvalArg('class_name', str)
@yaql.context.ContextAware()
def _get_container(context, obj, class_name):
    namespace_resolver = helpers.get_type(context).namespace_resolver
    class_loader = helpers.get_class_loader(context)
    class_name = namespace_resolver.resolve_name(class_name)
    murano_class = class_loader.get_class(class_name)
    p = obj.parent
    while p is not None:
        if murano_class.is_compatible(p):
            return p
        p = p.parent
    return None


@yaql.context.EvalArg('seconds', (int, float))
def _sleep(seconds):
    eventlet.sleep(seconds)


def register(context):
    context.register_function(_resolve, '#resolve')
    context.register_function(_cast, 'cast')
    context.register_function(_new, 'new')
    context.register_function(_id, 'id')
    context.register_function(_super2, 'super')
    context.register_function(_psuper2, 'psuper')
    context.register_function(_super, 'super')
    context.register_function(_require, 'require')
    context.register_function(_get_container, 'find')
    context.register_function(_sleep, 'sleep')