#!/usr/bin/env python
#
# Copyright (c), 2016-2020, SISSA (International School for Advanced Studies).
# All rights reserved.
# This file is distributed under the terms of the MIT License.
# See the file 'LICENSE' in the root directory of the present
# distribution, or http://opensource.org/licenses/MIT.
#
# @author Davide Brunato <brunato@sissa.it>
#
import unittest

from xmlschema import XMLSchemaParseError, XMLSchemaModelError
from xmlschema.etree import etree_element
from xmlschema.validators import XMLSchema11
from xmlschema.testing import XsdValidatorTestCase, print_test_header


class TestXsdComplexType(XsdValidatorTestCase):

    def check_complex_restriction(self, base, restriction, expected=None, **kwargs):
        content = 'complex' if self.content_pattern.search(base) else 'simple'
        source = """
            <xs:complexType name="targetType">
                {0}
            </xs:complexType>
            <xs:complexType name="restrictedType">
                <xs:{1}Content>
                    <xs:restriction base="targetType">
                        {2}
                    </xs:restriction>
                </xs:{1}Content>
            </xs:complexType>
            """.format(base.strip(), content, restriction.strip())
        self.check_schema(source, expected, **kwargs)

    def test_element_restrictions(self):
        base = """
        <xs:sequence>
            <xs:element name="A" maxOccurs="7"/>
            <xs:element name="B" type="xs:string"/>
            <xs:element name="C" fixed="5"/>
        </xs:sequence>
        """
        self.check_complex_restriction(
            base, restriction="""
            <xs:sequence>
                <xs:element name="A" maxOccurs="6"/>
                <xs:element name="B" type="xs:NCName"/>
                <xs:element name="C" fixed="5"/>
            </xs:sequence>
            """
        )
        self.check_complex_restriction(
            base, restriction="""
            <xs:sequence>
                <xs:element name="A" maxOccurs="8"/> <!-- <<< More occurrences -->
                <xs:element name="B" type="xs:NCName"/>
                <xs:element name="C" fixed="5"/>
            </xs:sequence>
            """, expected=XMLSchemaParseError
        )
        self.check_complex_restriction(
            base, restriction="""
            <xs:sequence>
                <xs:element name="A" maxOccurs="6"/>
                <xs:element name="B" type="float"/> <!-- <<< Not a derived type -->
                <xs:element name="C" fixed="5"/>
            </xs:sequence>
            """, expected=XMLSchemaParseError
        )
        self.check_complex_restriction(
            base, restriction="""
            <xs:sequence>
                <xs:element name="A" maxOccurs="6"/>
                <xs:element name="B" type="xs:NCName"/>
                <xs:element name="C" fixed="3"/> <!-- <<< Different fixed value -->
            </xs:sequence>
            """, expected=XMLSchemaParseError
        )
        self.check_complex_restriction(
            base, restriction="""
            <xs:sequence>
                <xs:element name="A" maxOccurs="6" nillable="true"/> <!-- <<< nillable is True -->
                <xs:element name="B" type="xs:NCName"/>
                <xs:element name="C" fixed="5"/>
            </xs:sequence>
            """, expected=XMLSchemaParseError
        )

    def test_sequence_group_restriction(self):
        # Meaningless sequence group
        base = """
        <xs:sequence>
            <xs:sequence>
                <xs:element name="A"/>
                <xs:element name="B"/>
            </xs:sequence>
        </xs:sequence>
        """
        self.check_complex_restriction(
            base, restriction="""
            <xs:sequence>
              <xs:element name="A"/>
              <xs:element name="B"/>
            </xs:sequence>
            """
        )
        self.check_complex_restriction(
            base, restriction="""
            <xs:sequence>
              <xs:element name="A"/>
              <xs:element name="C"/>
            </xs:sequence>
            """, expected=XMLSchemaParseError
        )

        base = """
        <xs:sequence>
            <xs:element name="A"/>
            <xs:element name="B" minOccurs="0"/>
        </xs:sequence>
        """
        self.check_complex_restriction(base, '<xs:sequence><xs:element name="A"/></xs:sequence>')
        self.check_complex_restriction(
            base, '<xs:sequence><xs:element name="B"/></xs:sequence>', XMLSchemaParseError
        )
        self.check_complex_restriction(
            base, '<xs:sequence><xs:element name="C"/></xs:sequence>', XMLSchemaParseError
        )
        self.check_complex_restriction(
            base, '<xs:sequence><xs:element name="A"/><xs:element name="B"/></xs:sequence>'
        )
        self.check_complex_restriction(
            base, '<xs:sequence><xs:element name="A"/><xs:element name="C"/></xs:sequence>',
            XMLSchemaParseError
        )
        self.check_complex_restriction(
            base,
            '<xs:sequence><xs:element name="A" minOccurs="0"/><xs:element name="B"/></xs:sequence>',
            XMLSchemaParseError
        )
        self.check_complex_restriction(
            base,
            '<xs:sequence><xs:element name="B" minOccurs="0"/><xs:element name="A"/></xs:sequence>',
            XMLSchemaParseError
        )

    def test_all_group_restriction(self):
        base = """
        <xs:all>
            <xs:element name="A"/>
            <xs:element name="B" minOccurs="0"/>
            <xs:element name="C" minOccurs="0"/>
        </xs:all>
        """
        self.check_complex_restriction(
            base, restriction="""
            <xs:all>
              <xs:element name="A"/>
              <xs:element name="C"/>
            </xs:all>
        """)
        self.check_complex_restriction(
            base, restriction="""
            <xs:all>
              <xs:element name="C" minOccurs="0"/>
              <xs:element name="A"/>
            </xs:all>
            """, expected=XMLSchemaParseError if self.schema_class.XSD_VERSION == '1.0' else None
        )
        self.check_complex_restriction(
            base, restriction="""
            <xs:sequence>
              <xs:element name="A"/>
              <xs:element name="C"/>
            </xs:sequence>
            """)
        self.check_complex_restriction(
            base,
            '<xs:sequence><xs:element name="C" minOccurs="0"/><xs:element name="A"/></xs:sequence>'
        )
        self.check_complex_restriction(
            base, restriction="""
            <xs:sequence>
              <xs:element name="C" minOccurs="0"/>
              <xs:element name="A" minOccurs="0"/>
            </xs:sequence>
            """, expected=XMLSchemaParseError
        )
        self.check_complex_restriction(
            base, restriction="""
            <xs:sequence>
              <xs:element name="A"/>
              <xs:element name="X"/>
            </xs:sequence>
            """, expected=XMLSchemaParseError
        )

        base = """
        <xs:all>
            <xs:element name="A" minOccurs="0" maxOccurs="0"/>
        </xs:all>
        """
        self.check_complex_restriction(base, '<xs:all><xs:element name="A"/></xs:all>',
                                       XMLSchemaParseError)

    def test_choice_group_restriction(self):
        base = """
        <xs:choice maxOccurs="2">
            <xs:element name="A"/>
            <xs:element name="B"/>
            <xs:element name="C"/>
        </xs:choice>
        """
        self.check_complex_restriction(
            base, '<xs:choice><xs:element name="A"/><xs:element name="C"/></xs:choice>')
        self.check_complex_restriction(
            base,
            '<xs:choice maxOccurs="2"><xs:element name="C"/><xs:element name="A"/></xs:choice>',
            XMLSchemaParseError if self.schema_class.XSD_VERSION == '1.0' else None
        )
        self.check_complex_restriction(
            base,
            '<xs:choice maxOccurs="2"><xs:element name="A"/><xs:element name="C"/></xs:choice>',
        )

    def test_occurs_restriction(self):
        base = """
        <xs:sequence minOccurs="3" maxOccurs="10">
            <xs:element name="A"/>
        </xs:sequence>
        """
        self.check_complex_restriction(
            base, '<xs:sequence minOccurs="3" maxOccurs="7"><xs:element name="A"/></xs:sequence>')
        self.check_complex_restriction(
            base, '<xs:sequence minOccurs="4" maxOccurs="10"><xs:element name="A"/></xs:sequence>')
        self.check_complex_restriction(
            base, '<xs:sequence minOccurs="3" maxOccurs="11"><xs:element name="A"/></xs:sequence>',
            XMLSchemaParseError
        )
        self.check_complex_restriction(
            base, '<xs:sequence minOccurs="2" maxOccurs="10"><xs:element name="A"/></xs:sequence>',
            XMLSchemaParseError
        )

    def test_recursive_complex_type(self):
        schema = self.schema_class("""
            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
                <xs:element name="elemA" type="typeA"/>
                <xs:complexType name="typeA">
                    <xs:sequence>
                        <xs:element ref="elemA" minOccurs="0" maxOccurs="5"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:schema>""")
        self.assertEqual(schema.elements['elemA'].type, schema.types['typeA'])

    def test_upa_violations(self):
        self.check_schema("""
            <xs:complexType name="typeA">
                <xs:sequence>
                    <xs:sequence minOccurs="0" maxOccurs="unbounded">
                        <xs:element name="A"/>
                        <xs:element name="B"/>
                    </xs:sequence>
                    <xs:element name="A" minOccurs="0"/>
                </xs:sequence>
            </xs:complexType>""", XMLSchemaModelError)

        self.check_schema("""
            <xs:complexType name="typeA">
                <xs:sequence>
                    <xs:sequence minOccurs="0" maxOccurs="unbounded">
                        <xs:element name="B"/>
                        <xs:element name="A"/>
                    </xs:sequence>
                    <xs:element name="A" minOccurs="0"/>
                </xs:sequence>
            </xs:complexType>""")

    def test_upa_violation_with_wildcard(self):
        self.check_schema("""
        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
            targetNamespace="tns" xmlns:ns="tns" elementFormDefault="unqualified">

            <xs:complexType name="baseType">
                <xs:sequence>
                   <xs:any processContents="lax" minOccurs="0" maxOccurs="unbounded"></xs:any>
                </xs:sequence>
            </xs:complexType>

            <xs:complexType name="addressType">
                <xs:complexContent>
                    <xs:extension base="ns:baseType">
                        <xs:sequence>
                            <xs:element name="state" type="xs:string" />
                            <xs:element name="currency" type="xs:string" />
                            <xs:element name="zip" type="xs:int" />
                        </xs:sequence>
                    </xs:extension>
                </xs:complexContent>
            </xs:complexType>

        </xs:schema>
        """, XMLSchemaModelError if self.schema_class.XSD_VERSION == '1.0' else None)

    def test_content_type(self):
        # Ref: https://www.w3.org/TR/xmlschema11-1/#Complex_Type_Definition_details

        schema = self.schema_class("""
        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

            <xs:complexType name="emptyContentType1">
                <xs:attribute name="a1"/>
            </xs:complexType>

            <xs:simpleType name="emptyContentType2">
                <xs:restriction base="xs:string">
                    <xs:length value="0"/>
                </xs:restriction>
            </xs:simpleType>

            <xs:complexType name="emptyContentType3">
                <xs:simpleContent>
                    <xs:extension base="emptyContentType2">
                        <xs:attribute name="a1"/>
                    </xs:extension>
                </xs:simpleContent>
            </xs:complexType>

            <xs:simpleType name="simpleContentType1">
                <xs:restriction base="xs:string">
                    <xs:length value="1"/>
                </xs:restriction>
            </xs:simpleType>

            <xs:complexType name="simpleContentType2">
                <xs:simpleContent>
                    <xs:extension base="xs:string">
                        <xs:attribute name="a1"/>
                    </xs:extension>
                </xs:simpleContent>
            </xs:complexType>

            <xs:complexType name="elementOnlyContentType">
                <xs:sequence>
                    <xs:element name="elem1"/>
                </xs:sequence>
            </xs:complexType>

            <xs:complexType name="mixedContentType" mixed="true">
                <xs:sequence>
                    <xs:element name="elem1"/>
                </xs:sequence>
            </xs:complexType>

        </xs:schema>
        """)

        xsd_type = schema.types['emptyContentType1']
        self.assertTrue(xsd_type.is_empty())
        self.assertFalse(xsd_type.has_simple_content())
        self.assertFalse(xsd_type.is_element_only())
        self.assertFalse(xsd_type.has_mixed_content())
        self.assertEqual(xsd_type.content_type_label, 'empty')

        xsd_type = schema.types['emptyContentType2']
        self.assertTrue(xsd_type.is_empty())
        self.assertFalse(xsd_type.has_simple_content())
        self.assertFalse(xsd_type.is_element_only())
        self.assertFalse(xsd_type.has_mixed_content())
        self.assertEqual(xsd_type.content_type_label, 'empty')

        xsd_type = schema.types['emptyContentType3']
        self.assertTrue(xsd_type.is_empty())
        self.assertFalse(xsd_type.has_simple_content())
        self.assertFalse(xsd_type.is_element_only())
        self.assertFalse(xsd_type.has_mixed_content())
        self.assertEqual(xsd_type.content_type_label, 'empty')

        xsd_type = schema.types['simpleContentType1']
        self.assertFalse(xsd_type.is_empty())
        self.assertTrue(xsd_type.has_simple_content())
        self.assertFalse(xsd_type.is_element_only())
        self.assertFalse(xsd_type.has_mixed_content())
        self.assertEqual(xsd_type.content_type_label, 'simple')

        xsd_type = schema.types['simpleContentType2']
        self.assertFalse(xsd_type.is_empty())
        self.assertTrue(xsd_type.has_simple_content())
        self.assertFalse(xsd_type.is_element_only())
        self.assertFalse(xsd_type.has_mixed_content())
        self.assertEqual(xsd_type.content_type_label, 'simple')

        xsd_type = schema.types['elementOnlyContentType']
        self.assertFalse(xsd_type.is_empty())
        self.assertFalse(xsd_type.has_simple_content())
        self.assertTrue(xsd_type.is_element_only())
        self.assertFalse(xsd_type.has_mixed_content())
        self.assertEqual(xsd_type.content_type_label, 'element-only')

        xsd_type = schema.types['mixedContentType']
        self.assertFalse(xsd_type.is_empty())
        self.assertFalse(xsd_type.has_simple_content())
        self.assertFalse(xsd_type.is_element_only())
        self.assertTrue(xsd_type.has_mixed_content())
        self.assertEqual(xsd_type.content_type_label, 'mixed')

    def test_content_type_property(self):
        schema = self.schema_class("""
            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
                <xs:complexType name="type1">
                    <xs:sequence>
                        <xs:element name="elem1"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:schema>""")

        xsd_type = schema.types['type1']
        self.assertIs(xsd_type.content_type, xsd_type.content)


class TestXsd11ComplexType(TestXsdComplexType):

    schema_class = XMLSchema11

    def test_complex_type_assertion(self):
        schema = self.check_schema("""
            <xs:complexType name="intRange">
              <xs:attribute name="min" type="xs:int"/>
              <xs:attribute name="max" type="xs:int"/>
              <xs:assert test="@min le @max"/>
            </xs:complexType>""")

        xsd_type = schema.types['intRange']
        xsd_type.decode(etree_element('a', attrib={'min': '10', 'max': '19'}))
        self.assertTrue(xsd_type.is_valid(etree_element('a', attrib={'min': '10', 'max': '19'})))
        self.assertTrue(xsd_type.is_valid(etree_element('a', attrib={'min': '19', 'max': '19'})))
        self.assertFalse(xsd_type.is_valid(etree_element('a', attrib={'min': '25', 'max': '19'})))
        self.assertTrue(xsd_type.is_valid(etree_element('a', attrib={'min': '25', 'max': '100'})))

    def test_sequence_extension(self):
        schema = self.schema_class("""
            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
              <xs:complexType name="base">
                <xs:openContent mode="suffix">
                  <xs:any namespace="tns1" processContents="lax"/>
                </xs:openContent>
                <xs:sequence>
                  <xs:element name="a" maxOccurs="unbounded"/>
                  <xs:element name="b" minOccurs="0"/>
                  <xs:element name="c" minOccurs="0"/>
                </xs:sequence>
              </xs:complexType>

              <xs:complexType name="ext">
                <xs:complexContent>
                  <xs:extension base="base">
                    <xs:sequence>
                      <xs:element name="d" minOccurs="0"/>
                    </xs:sequence>
                  </xs:extension>
                </xs:complexContent>
              </xs:complexType>
            </xs:schema>""")

        base_group = schema.types['base'].content
        self.assertEqual(base_group.model, 'sequence')
        self.assertEqual(base_group[0].name, 'a')
        self.assertEqual(base_group[1].name, 'b')
        self.assertEqual(base_group[2].name, 'c')
        self.assertEqual(len(base_group), 3)

        ext_group = schema.types['ext'].content
        self.assertEqual(ext_group.model, 'sequence')
        self.assertEqual(len(ext_group), 2)
        self.assertEqual(ext_group[0].model, 'sequence')
        self.assertEqual(ext_group[1].model, 'sequence')
        self.assertEqual(ext_group[0][0].name, 'a')
        self.assertEqual(ext_group[0][1].name, 'b')
        self.assertEqual(ext_group[0][2].name, 'c')
        self.assertEqual(len(ext_group[0]), 3)
        self.assertEqual(ext_group[1][0].name, 'd')
        self.assertEqual(len(ext_group[1]), 1)


if __name__ == '__main__':
    print_test_header()
    unittest.main()
