# Copyright 2013 Amazon.com, Inc. or its affiliates. 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. A copy of
# the License is located at
#
#     http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file 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 awscli.testutils import mock, unittest

from awscli.arguments import CLIArgument
from awscli.customizations import utils
from awscli.customizations.flatten import FlattenedArgument, FlattenArguments


def _hydrate(params, container, cli_type, key, value):
    """
    An example to hydrate a complex structure with custom value logic. In this
    case we create a nested structure and divide the value by 100.
    """
    params['bag'] = {
        'ArgumentBaz': {
            'SomeValueAbc': value / 100.0
        }
    }


FLATTEN_CONFIG = {
    'command-name': {
        'original-argument': {
            "keep": False,
            "flatten": {
                "ArgumentFoo": {
                    "name": "foo"
                },
                "ArgumentBar": {
                    "name": "bar",
                    "help_text": "Some help text",
                    "required": True,
                    "hydrate_value": lambda x: x.upper()
                }
            }
        },
        'another-original-argument': {
            "keep": True,
            "flatten": {
                "ArgumentBaz.SomeValue": {
                    "name": "baz",
                    "hydrate": _hydrate
                }
            }
        }
    }
}

class TestFlattenedArgument(unittest.TestCase):
    def test_basic_argument(self):
        container = mock.Mock()
        container.argument_model.name = 'bag'
        kwargs = {
            'container': container,
            'prop': 'ArgumentFoo'
        }
        kwargs['container'].py_name = 'bag'
        kwargs.update(FLATTEN_CONFIG['command-name']['original-argument']
                                    ['flatten']['ArgumentFoo'])
        arg = FlattenedArgument(**kwargs)

        self.assertEqual('foo', arg.name)
        self.assertEqual('', arg.documentation)
        self.assertEqual(False, arg.required)

        params = {}
        arg.add_to_params(params, 'value')
        self.assertEqual('value', params['bag']['ArgumentFoo'])

    def test_hydrate_value_argument(self):
        container = mock.Mock()
        container.argument_model.name = 'bag'
        kwargs = {
            'container': container,
            'prop': 'ArgumentBar'
        }
        kwargs['container'].py_name = 'bag'
        kwargs['container'].cli_type_name = 'list'
        kwargs.update(FLATTEN_CONFIG['command-name']['original-argument']
                                    ['flatten']['ArgumentBar'])
        arg = FlattenedArgument(**kwargs)

        self.assertEqual('bar', arg.name)
        self.assertEqual('Some help text', arg.documentation)
        self.assertEqual(True, arg.required)

        params = {}
        arg.add_to_params(params, 'value')
        self.assertEqual('VALUE', params['bag'][0]['ArgumentBar'])

    def test_hydrate_function_argument(self):
        container = mock.Mock()
        container.argument_model.name = 'bag'
        kwargs = {
            'container': container,
            'prop': 'ArgumentBaz:SomeValue'
        }
        kwargs['container'].py_name = 'bag'
        kwargs.update(FLATTEN_CONFIG['command-name']
                                    ['another-original-argument']
                                    ['flatten']['ArgumentBaz.SomeValue'])
        arg = FlattenedArgument(**kwargs)

        self.assertEqual('baz', arg.name)
        self.assertEqual('', arg.documentation)
        self.assertEqual(False, arg.required)

        params = {}
        arg.add_to_params(params, 1020)
        self.assertEqual(10.2, params['bag']['ArgumentBaz']['SomeValueAbc'])


class TestFlattenCommands(unittest.TestCase):
    def test_flatten_register(self):
        cli = mock.Mock()

        flatten = FlattenArguments('my-service', FLATTEN_CONFIG)
        flatten.register(cli)

        cli.register.assert_called_with(\
            'building-argument-table.my-service.command-name',
            flatten.flatten_args)

    def test_flatten_modify_args(self):
        # Mock operation, arguments, and members for a service
        command = mock.Mock()
        command.name = 'command-name'

        argument_model1 = mock.Mock()
        argument_model1.required_members = []

        member_foo = mock.Mock()
        member_foo.name = 'ArgumentFoo'
        member_foo.documentation = 'Original docs'
        member_foo.required_members = []

        member_bar = mock.Mock()
        member_bar.name = 'ArgumentBar'
        member_bar.documentation = 'More docs'
        member_bar.required_members = []

        argument_model1.members = {
            'ArgumentFoo': member_foo,
            'ArgumentBar': member_bar
        }

        argument_model2 = mock.Mock()
        argument_model2.required_members = []

        member_baz = mock.Mock()
        member_baz.name = 'ArgumentBaz'
        member_baz.documentation = ''
        member_baz.required_members = []

        member_some_value = mock.Mock()
        member_some_value.name = 'SomeValue'
        member_some_value.documenation = ''
        member_some_value.required_members = []

        member_baz.members = {
            'SomeValue': member_some_value
        }

        argument_model2.members = {
            'ArgumentBaz': member_baz
        }

        cli_argument1 = mock.Mock(spec=CLIArgument)
        cli_argument1.argument_model = argument_model1

        cli_argument2 = mock.Mock(spec=CLIArgument)
        cli_argument2.argument_model = argument_model2

        argument_table = {
            'original-argument': cli_argument1,
            'another-original-argument': cli_argument2
        }

        # Create the flattened argument table
        cli = mock.Mock()
        flatten = FlattenArguments('my-service', FLATTEN_CONFIG)
        flatten.flatten_args(command, argument_table)

        # Make sure new arguments and any with keep=True are there
        self.assertIn('foo', argument_table)
        self.assertIn('bar', argument_table)
        self.assertNotIn('original-argument', argument_table)
        self.assertIn('baz', argument_table)
        self.assertIn('another-original-argument', argument_table)

        # Make sure the new arguments are the class we expect
        self.assertIsInstance(argument_table['foo'], FlattenedArgument)
        self.assertIsInstance(argument_table['bar'], FlattenedArgument)
        self.assertIsInstance(argument_table['baz'], FlattenedArgument)
        self.assertNotIsInstance(argument_table['another-original-argument'],
                                 FlattenedArgument)

        # Make sure original required trait can be overridden
        self.assertEqual(False, argument_table['foo'].required)
        self.assertEqual(True, argument_table['bar'].required)

        # Make sure docs can be overridden and get the defaults
        self.assertEqual('Original docs', argument_table['foo'].documentation)
        self.assertEqual('Some help text', argument_table['bar'].documentation)
