import datetime
import uuid
import decimal

from marshmallow import fields, missing, Schema, validate
from marshmallow.class_registry import get_class
from marshmallow.compat import text_type, binary_type, basestring

from .validation import handle_length, handle_one_of, handle_range


__all__ = ['JSONSchema']


TYPE_MAP = {
    dict: {
        'type': 'object',
    },
    list: {
        'type': 'array',
    },
    datetime.time: {
        'type': 'string',
        'format': 'time',
    },
    datetime.timedelta: {
        # TODO explore using 'range'?
        'type': 'string',
    },
    datetime.datetime: {
        'type': 'string',
        'format': 'date-time',
    },
    datetime.date: {
        'type': 'string',
        'format': 'date',
    },
    uuid.UUID: {
        'type': 'string',
        'format': 'uuid',
    },
    text_type: {
        'type': 'string',
    },
    binary_type: {
        'type': 'string',
    },
    decimal.Decimal: {
        'type': 'number',
        'format': 'decimal',
    },
    set: {
        'type': 'array',
    },
    tuple: {
        'type': 'array',
    },
    float: {
        'type': 'number',
        'format': 'float',
    },
    int: {
        'type': 'number',
        'format': 'integer',
    },
    bool: {
        'type': 'boolean',
    },
}


FIELD_VALIDATORS = {
    validate.Length: handle_length,
    validate.OneOf: handle_one_of,
    validate.Range: handle_range,
}


class JSONSchema(Schema):
    properties = fields.Method('get_properties')
    type = fields.Constant('object')
    required = fields.Method('get_required')

    def get_properties(self, obj):
        mapping = {v: k for k, v in obj.TYPE_MAPPING.items()}
        mapping[fields.Email] = text_type
        mapping[fields.Dict] = dict
        mapping[fields.List] = list
        mapping[fields.Url] = text_type
        mapping[fields.LocalDateTime] = datetime.datetime
        properties = {}

        for field_name, field in sorted(obj.fields.items()):
            if hasattr(field, '_jsonschema_type_mapping'):
                schema = field._jsonschema_type_mapping()
            elif field.__class__ in mapping:
                pytype = mapping[field.__class__]
                schema = self.__class__._from_python_type(field, pytype)
            elif isinstance(field, fields.Nested):
                schema = self.__class__._from_nested_schema(field)
            else:
                raise ValueError('unsupported field type %s' % field)

            # Apply any and all validators that field may have
            for validator in field.validators:
                if validator.__class__ in FIELD_VALIDATORS:
                    schema = FIELD_VALIDATORS[validator.__class__](
                        schema, field, validator, obj
                    )

            properties[field.name] = schema

        return properties

    def get_required(self, obj):
        required = []

        for field_name, field in sorted(obj.fields.items()):
            if field.required:
                required.append(field.name)

        return required

    @classmethod
    def _from_python_type(cls, field, pytype):
        json_schema = {
            'title': field.attribute or field.name,
        }

        for key, val in TYPE_MAP[pytype].items():
            json_schema[key] = val

        if field.dump_only:
            json_schema['readonly'] = True

        if field.default is not missing:
            json_schema['default'] = field.default

        if field.metadata.get('metadata', {}).get('description'):
            json_schema['description'] = (
                field.metadata['metadata'].get('description')
            )

        if field.metadata.get('metadata', {}).get('title'):
            json_schema['title'] = field.metadata['metadata'].get('title')

        if isinstance(field, fields.List):
            container_pytype = None
            container = field.container
            for k, v in cls().TYPE_MAPPING.items():
                if v == container.__class__:
                    container_pytype = k
                    break
            if container_pytype:
                schema = cls()._from_python_type(container, container_pytype)
                json_schema['items'] = schema

        return json_schema

    @classmethod
    def _from_nested_schema(cls, field):
        if isinstance(field.nested, basestring):
            nested = get_class(field.nested)
        else:
            nested = field.nested
        schema = cls().dump(nested()).data

        if field.metadata.get('metadata', {}).get('description'):
            schema['description'] = (
                field.metadata['metadata'].get('description')
            )

        if field.metadata.get('metadata', {}).get('title'):
            schema['title'] = field.metadata['metadata'].get('title')

        if field.many:
            schema = {
                'type': ["array"] if field.required else ['array', 'null'],
                'items': schema,
            }

        return schema

@@ -3865,101 +3865,29 @@
     
-container_pytype = None%0A            container = field.container%0A            for k, v
+for ptyp, mtyp
  in cls
-()
 .TYP
@@ -3928,194 +3928,141 @@
  if 
-v == container.__class__:%0A                    container_pytype = k%0A                    break%0A            if container_pytype:%0A                schema = cls()._from_python_type(container, 
+isinstance(field.container, mtyp):%0A                    json_schema%5B'items'%5D = cls()._from_python_type(%0A                        field.
 cont
@@ -4070,15 +4070,14 @@
 iner
-_py
+, p
 typ
-e
 )%0A  
@@ -4094,37 +4094,17 @@
     
-json_schema%5B'items'%5D = schema
+    break
 %0A%0A  

simplify fields.List support provided by @sdayu