# coding: utf-8
# 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 re

import mock
import semantic_version
import yaql
from yaql.language import exceptions
from yaql.language import utils

import murano.dsl.helpers as helpers
import murano.dsl.namespace_resolver as ns_resolver
import murano.dsl.yaql_expression as yaql_expression
from murano.tests.unit import base

ROOT_CLASS = 'io.murano.Object'


class TestNamespaceResolving(base.MuranoTestCase):
    def setUp(self):
        super(TestNamespaceResolving, self).setUp()

    def test_fails_w_empty_name(self):
        resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'})

        self.assertRaises(ValueError, resolver.resolve_name, None)

    def test_fails_w_unknown_prefix(self):
        resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'})
        name = 'unknown_prefix:example.murano'

        self.assertRaises(KeyError, resolver.resolve_name, name)

    def test_fails_w_prefix_wo_name(self):
        resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'})
        name = 'sys:'

        self.assertRaises(NameError, resolver.resolve_name, name)

    def test_fails_w_excessive_prefix(self):
        ns = {'sys': 'com.example.murano.system'}
        resolver = ns_resolver.NamespaceResolver(ns)
        invalid_name = 'sys:excessive_ns:muranoResource'

        self.assertRaises(NameError, resolver.resolve_name, invalid_name)

    def test_cuts_empty_prefix(self):
        resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'})
        # name without prefix delimiter
        name = 'some.arbitrary.name'

        resolved_name = resolver.resolve_name(':' + name)

        self.assertEqual(name, resolved_name)

    def test_resolves_specified_ns_prefix(self):
        ns = {'sys': 'com.example.murano.system'}
        resolver = ns_resolver.NamespaceResolver(ns)
        short_name, full_name = 'sys:File', 'com.example.murano.system.File'

        resolved_name = resolver.resolve_name(short_name)

        self.assertEqual(full_name, resolved_name)

    def test_resolves_current_ns(self):
        resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'})
        short_name, full_name = 'Resource', 'com.example.murano.Resource'

        resolved_name = resolver.resolve_name(short_name)

        self.assertEqual(full_name, resolved_name)

    def test_resolves_explicit_base(self):
        resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'})

        resolved_name = resolver.resolve_name('Resource', relative='com.base')

        self.assertEqual('com.base.Resource', resolved_name)

    def test_resolves_explicit_base_w_empty_namespaces(self):
        resolver = ns_resolver.NamespaceResolver({})

        resolved_name = resolver.resolve_name('File', 'com.base')

        self.assertEqual('com.base.File', resolved_name)

    def test_resolves_w_empty_namespaces(self):
        resolver = ns_resolver.NamespaceResolver({})

        resolved_name = resolver.resolve_name('Resource')

        self.assertEqual('Resource', resolved_name)


class Bunch(object):
    def __init__(self, **kwargs):
        super(Bunch, self).__init__()
        for key, value in kwargs.iteritems():
            setattr(self, key, value)


class TestHelperFunctions(base.MuranoTestCase):
    def setUp(self):
        super(TestHelperFunctions, self).setUp()

    def test_generate_id(self):
        generated_id = helpers.generate_id()

        self.assertTrue(re.match(r'[a-z0-9]{32}', generated_id))

    def test_evaluate(self):
        yaql_value = mock.Mock(yaql_expression.YaqlExpression,
                               return_value='atom')
        complex_value = {yaql_value: ['some', (1, yaql_value), 'hi!'],
                         'sample': [yaql_value, xrange(5)]}
        complex_literal = utils.FrozenDict({
            'atom': ('some', (1, 'atom'), 'hi!'),
            'sample': ('atom', (0, 1, 2, 3, 4))
        })
        context = yaql.create_context()
        evaluated_value = helpers.evaluate(yaql_value, context)
        evaluated_complex_value = helpers.evaluate(complex_value, context)

        self.assertEqual('atom', evaluated_value)
        self.assertEqual(complex_literal, evaluated_complex_value)


class TestYaqlExpression(base.MuranoTestCase):
    def setUp(self):
        self._version = semantic_version.Version.coerce('1.0')
        super(TestYaqlExpression, self).setUp()

    def test_expression(self):
        yaql_expr = yaql_expression.YaqlExpression('string', self._version)

        self.assertEqual('string', yaql_expr.expression)

    def test_unicode_expression(self):
        yaql_expr = yaql_expression.YaqlExpression(u"'yaql ♥ unicode'",
                                                   self._version)

        self.assertEqual(u"'yaql ♥ unicode'".encode('utf-8'),
                         yaql_expr.expression)

    def test_unicode_expression_expression(self):
        yaql_expr = yaql_expression.YaqlExpression(u"'yaql ♥ unicode'",
                                                   self._version)
        yaql_expr2 = yaql_expression.YaqlExpression(yaql_expr, self._version)

        self.assertEqual(u"'yaql ♥ unicode'".encode('utf-8'),
                         yaql_expr2.expression)

    def test_evaluate_calls(self):
        string = 'string'
        expected_calls = [mock.call(string, self._version),
                          mock.call().evaluate(context=None)]

        with mock.patch('murano.dsl.yaql_integration.parse') as mock_parse:
            yaql_expr = yaql_expression.YaqlExpression(string, self._version)
            yaql_expr(None)

        self.assertEqual(expected_calls, mock_parse.mock_calls)

    def test_is_expression_returns(self):
        expr = yaql_expression.YaqlExpression('string', self._version)

        with mock.patch('murano.dsl.yaql_integration.parse'):
            self.assertTrue(expr.is_expression('$some', self._version))
            self.assertTrue(expr.is_expression('$.someMore', self._version))

        with mock.patch('murano.dsl.yaql_integration.parse') as parse_mock:
            parse_mock.side_effect = exceptions.YaqlGrammarException
            self.assertFalse(expr.is_expression('', self._version))

        with mock.patch('murano.dsl.yaql_integration.parse') as parse_mock:
            parse_mock.side_effect = exceptions.YaqlLexicalException
            self.assertFalse(expr.is_expression('', self._version))
