# Copyright 2015 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.
import mock

from tests.unit.docs import BaseDocsTest
from botocore.hooks import HierarchicalEmitter
from botocore.docs.params import RequestParamsDocumenter
from botocore.docs.params import ResponseParamsDocumenter
from botocore.docs.utils import DocumentedShape


class BaseParamsDocumenterTest(BaseDocsTest):
    def setUp(self):
        super(BaseParamsDocumenterTest, self).setUp()
        self.event_emitter = HierarchicalEmitter()
        self.request_params = RequestParamsDocumenter(
            service_name='myservice', operation_name='SampleOperation',
            event_emitter=self.event_emitter)
        self.response_params = ResponseParamsDocumenter(
            service_name='myservice', operation_name='SampleOperation',
            event_emitter=self.event_emitter)


class TestDocumentDefaultValue(BaseParamsDocumenterTest):
    def setUp(self):
        super(TestDocumentDefaultValue, self).setUp()
        self.add_shape_to_params('Foo', 'String', 'This describes foo.')

    def test_request_params(self):
        self.request_params.document_params(
            self.doc_structure, self.operation_model.input_shape)
        self.assert_contains_lines_in_order([
            ':type Foo: string',
            ':param Foo: This describes foo.'
        ])

    def test_response_params(self):
        self.response_params.document_params(
            self.doc_structure, self.operation_model.input_shape)
        self.assert_contains_lines_in_order([
            '- *(dict) --*',
            '  - **Foo** *(string) --* This describes foo.'
        ])


class TestTraverseAndDocumentShape(BaseParamsDocumenterTest):
    def setUp(self):
        super(TestTraverseAndDocumentShape, self).setUp()
        self.add_shape_to_params('Foo', 'String', 'This describes foo.')
        self.event_emitter = mock.Mock()
        self.request_params = RequestParamsDocumenter(
            service_name='myservice', operation_name='SampleOperation',
            event_emitter=self.event_emitter)
        self.response_params = ResponseParamsDocumenter(
            service_name='myservice', operation_name='SampleOperation',
            event_emitter=self.event_emitter)

    def test_events_emitted_response_params(self):
        self.response_params.traverse_and_document_shape(
            section=self.doc_structure,
            shape=self.operation_model.input_shape, history=[]
        )
        self.assertEqual(
            self.event_emitter.emit.call_args_list,
            [mock.call('docs.response-params.myservice.SampleOperation.Foo',
                       section=self.doc_structure.get_section('Foo')),
             mock.call(('docs.response-params.myservice.SampleOperation'
                        '.complete-section'), section=self.doc_structure)]
        )

    def test_events_emitted_request_params(self):
        self.request_params.traverse_and_document_shape(
            section=self.doc_structure,
            shape=self.operation_model.input_shape, history=[]
        )
        self.assertEqual(
            self.event_emitter.emit.call_args_list,
            [mock.call('docs.request-params.myservice.SampleOperation.Foo',
                       section=self.doc_structure.get_section('Foo')),
             mock.call(('docs.request-params.myservice.SampleOperation'
                        '.complete-section'), section=self.doc_structure)]
        )


class TestDocumentMultipleDefaultValues(BaseParamsDocumenterTest):
    def setUp(self):
        super(TestDocumentMultipleDefaultValues, self).setUp()
        self.add_shape_to_params('Foo', 'String', 'This describes foo.')
        self.add_shape_to_params('Bar', 'String', 'This describes bar.',
                                 is_required=True)

    def test_request_params(self):
        self.request_params.document_params(
            self.doc_structure, self.operation_model.input_shape)
        self.assert_contains_lines_in_order([
            ':type Foo: string',
            ':param Foo: This describes foo.',
            ':type Bar: string',
            ':param Bar: **[REQUIRED]** This describes bar.'
        ])

    def test_response_params(self):
        self.response_params.document_params(
            self.doc_structure, self.operation_model.input_shape)
        self.assert_contains_lines_in_order([
            '- *(dict) --*',
            '  - **Foo** *(string) --* This describes foo.',
            '  - **Bar** *(string) --* This describes bar.'
        ])


class TestDocumentInclude(BaseParamsDocumenterTest):
    def setUp(self):
        super(TestDocumentInclude, self).setUp()
        self.add_shape_to_params('Foo', 'String', 'This describes foo.')
        self.include_params = [
            DocumentedShape(
                name='Baz', type_name='integer',
                documentation='This describes baz.'
            )
        ]

    def test_request_params(self):
        self.request_params.document_params(
            self.doc_structure, self.operation_model.input_shape,
            include=self.include_params
        )
        self.assert_contains_lines_in_order([
            ':type Foo: string',
            ':param Foo: This describes foo.',
            ':type Baz: int',
            ':param Baz: This describes baz.'
        ])

    def test_response_params(self):
        self.response_params.document_params(
            self.doc_structure, self.operation_model.input_shape,
            include=self.include_params
        )
        self.assert_contains_lines_in_order([
            '- *(dict) --*',
            '  - **Foo** *(string) --* This describes foo.',
            '  - **Baz** *(integer) --* This describes baz.'
        ])


class TestDocumentExclude(BaseParamsDocumenterTest):
    def setUp(self):
        super(TestDocumentExclude, self).setUp()
        self.add_shape_to_params('Foo', 'String', 'This describes foo.')
        self.add_shape_to_params('Bar', 'String', 'This describes bar.',
                                 is_required=True)
        self.exclude_params = ['Foo']

    def test_request_params(self):
        self.request_params.document_params(
            self.doc_structure, self.operation_model.input_shape,
            exclude=self.exclude_params)
        self.assert_contains_lines_in_order([
            ':type Bar: string',
            ':param Bar: **[REQUIRED]** This describes bar.'
        ])
        self.assert_not_contains_lines([
            ':type Foo: string',
            ':param Foo: This describes foo.'
        ])

    def test_response_params(self):
        self.response_params.document_params(
            self.doc_structure, self.operation_model.input_shape,
            exclude=self.exclude_params)
        self.assert_contains_lines_in_order([
            '- *(dict) --*',
            '  - **Bar** *(string) --* This describes bar.'
        ])
        self.assert_not_contains_line(
            '  - **Foo** *(string) --* This describes foo.')


class TestDocumentList(BaseParamsDocumenterTest):
    def setUp(self):
        super(TestDocumentList, self).setUp()
        self.add_shape(
            {'List': {
                'type': 'list',
                'member': {'shape': 'String',
                           'documentation': 'A string element'}}})
        self.add_shape_to_params(
            'Foo', 'List',
            'This describes the list. Each element of this list is a string.')

    def test_request_params(self):
        self.request_params.document_params(
            self.doc_structure, self.operation_model.input_shape)
        self.assert_contains_lines_in_order([
            ':type Foo: list',
            ':param Foo: This describes the list.',
            '  - *(string) --* A string element'
        ])

    def test_response_params(self):
        self.response_params.document_params(
            self.doc_structure, self.operation_model.input_shape)
        self.assert_contains_lines_in_order([
            '- *(dict) --*',
            ('  - **Foo** *(list) --* This describes the list. '
             'Each element of this list is a string.'),
            '    - *(string) --* A string element'
        ])


class TestDocumentMap(BaseParamsDocumenterTest):
    def setUp(self):
        super(TestDocumentMap, self).setUp()
        self.add_shape(
            {'Map': {
                'type': 'map',
                'key': {'shape': 'String'},
                'value': {'shape': 'String'}}})
        self.add_shape_to_params('Foo', 'Map', 'This describes the map.')

    def test_request_params(self):
        self.request_params.document_params(
            self.doc_structure, self.operation_model.input_shape)
        self.assert_contains_lines_in_order([
            ':type Foo: dict',
            ':param Foo: This describes the map.',
            '  - *(string) --*',
            '    - *(string) --*'
        ])

    def test_response_params(self):
        self.response_params.document_params(
            self.doc_structure, self.operation_model.input_shape)
        self.assert_contains_lines_in_order([
            '- *(dict) --*',
            '  - **Foo** *(dict) --* This describes the map.',
            '    - *(string) --*',
            '      - *(string) --*'
        ])


class TestDocumentStructure(BaseParamsDocumenterTest):
    def setUp(self):
        super(TestDocumentStructure, self).setUp()
        self.add_shape(
            {'Structure': {
                'type': 'structure',
                'members': {
                    'Member': {'shape': 'String',
                               'documentation': 'This is its member.'}}}})
        self.add_shape_to_params(
            'Foo', 'Structure', 'This describes the structure.')

    def test_request_params(self):
        self.request_params.document_params(
            self.doc_structure, self.operation_model.input_shape)
        self.assert_contains_lines_in_order([
            ':type Foo: dict',
            ':param Foo: This describes the structure.',
            '  - **Member** *(string) --* This is its member.'
        ])

    def test_response_params(self):
        self.response_params.document_params(
            self.doc_structure, self.operation_model.input_shape)
        self.assert_contains_lines_in_order([
            '- *(dict) --*',
            '  - **Foo** *(dict) --* This describes the structure.',
            '    - **Member** *(string) --* This is its member.'
        ])


class TestDocumentRecursiveShape(BaseParamsDocumenterTest):
    def setUp(self):
        super(TestDocumentRecursiveShape, self).setUp()
        self.add_shape(
            {'Structure': {
                'type': 'structure',
                'members': {
                    'Foo': {
                        'shape': 'Structure',
                        'documentation': 'This is a recursive structure.'}}}})
        self.add_shape_to_params(
            'Foo', 'Structure', 'This describes the structure.')

    def test_request_params(self):
        self.request_params.document_params(
            self.doc_structure, self.operation_model.input_shape)
        self.assert_contains_lines_in_order([
            ':type Foo: dict',
            ':param Foo: This describes the structure.',
            '  - **Foo** *(dict) --* This is a recursive structure.'
        ])

    def test_response_params(self):
        self.response_params.document_params(
            self.doc_structure, self.operation_model.input_shape)
        self.assert_contains_lines_in_order([
            '- *(dict) --*',
            '  - **Foo** *(dict) --* This is a recursive structure.',
        ])
