# -*- coding: utf-8 -*-

import marshmallow as ma
from marshmallow.exceptions import ValidationError
from marshmallow.compat import iteritems, PY2

from .fields import BaseRelationship, Meta, _META_LOAD_FROM
from .exceptions import IncorrectTypeError
from .utils import resolve_params

TYPE = 'type'
ID = 'id'

def plain_function(f):
    """Ensure that ``callable`` is a plain function rather than an unbound method."""
    if PY2 and f:
        return f.im_func
    # Python 3 doesn't have bound/unbound methods, so don't need to do anything
    return f

class SchemaOpts(ma.SchemaOpts):

    def __init__(self, meta, *args, **kwargs):
        super(SchemaOpts, self).__init__(meta, *args, **kwargs)
        self.type_ = getattr(meta, 'type_', None)
        self.inflect = plain_function(getattr(meta, 'inflect', None))
        self.self_url = getattr(meta, 'self_url', None)
        self.self_url_kwargs = getattr(meta, 'self_url_kwargs', None)
        self.self_url_many = getattr(meta, 'self_url_many', None)

class Schema(ma.Schema):
    """Schema class that formats data according to JSON API 1.0.
    Must define the ``type_`` `class Meta` option.

    Example: ::

        from marshmallow_jsonapi import Schema, fields

        def dasherize(text):
            return text.replace('_', '-')

        class PostSchema(Schema):
            id = fields.Str(dump_only=True)  # Required
            title = fields.Str()

            author = fields.HyperlinkRelated(
                '/authors/{author_id}',
                url_kwargs={'author_id': '<author.id>'},
            )

            comments = fields.HyperlinkRelated(
                '/posts/{post_id}/comments',
                url_kwargs={'post_id': '<id>'},
                # Include resource linkage
                many=True, include_resource_linkage=True,
                type_='comments'
            )

            class Meta:
                type_ = 'posts'  # Required
                inflect = dasherize

    """
    class Meta:
        """Options object for `Schema`. Takes the same options as `marshmallow.Schema.Meta` with
        the addition of:

        * ``type_`` - required, the JSON API resource type as a string.
        * ``inflect`` - optional, an inflection function to modify attribute names.
        * ``self_url`` - optional, URL to use to `self` in links
        * ``self_url_kwargs`` - optional, replacement fields for `self_url`.
          String arguments enclosed in ``< >`` will be interpreted as attributes
          to pull from the schema data.
        * ``self_url_many`` - optional, URL to use to `self` in top-level ``links``
          when a collection of resources is returned.
        """
        pass

    def __init__(self, *args, **kwargs):
        self.include_data = kwargs.pop('include_data', ())
        super(Schema, self).__init__(*args, **kwargs)
        for field_name, field in self.fields.items():
            if field_name in self.include_data:
                if not isinstance(field, BaseRelationship):
                    raise ValueError('Can only include relationships. "{}" is a "{}"'
                                     .format(field_name, field.__class__.__name__))
                field.include_data = True
            elif isinstance(field, BaseRelationship):
                field.include_data = False

        for field_name in self.include_data:
            if field_name not in self.fields:
                raise ValueError('Unknown field "{}"'.format(field_name))

        if not self.opts.type_:
            raise ValueError('Must specify type_ class Meta option')

        if 'id' not in self.fields:
            raise ValueError('Must have an `id` field')

        if self.opts.self_url_kwargs and not self.opts.self_url:
            raise ValueError('Must specify `self_url` Meta option when '
                             '`self_url_kwargs` is specified')
        self.included_data = {}

    OPTIONS_CLASS = SchemaOpts

    @ma.post_dump(pass_many=True)
    def format_json_api_response(self, data, many):
        """Post-dump hook that formats serialized data as a top-level JSON API object.

        See: http://jsonapi.org/format/#document-top-level
        """
        ret = self.format_items(data, many)
        ret = self.wrap_response(ret, many)
        ret = self.render_included_data(ret)
        return ret

    def render_included_data(self, data):
        if not self.included_data:
            return data
        data['included'] = list(self.included_data.values())
        return data

    def unwrap_item(self, item):
        if 'type' not in item:
            raise ma.ValidationError([
                {
                    'detail': '`data` object must include `type` key.',
                    'source': {
                        'pointer': '/data'
                    }
                }
            ])
        if item['type'] != self.opts.type_:
            raise IncorrectTypeError(actual=item['type'], expected=self.opts.type_)

        payload = self.dict_class()
        if 'id' in item:
            payload['id'] = item['id']
        if 'meta' in item:
            payload[_META_LOAD_FROM] = item['meta']
        for key, value in iteritems(item.get('attributes', {})):
            payload[key] = value
        for key, value in iteritems(item.get('relationships', {})):
            payload[key] = value
        return payload

    @ma.pre_load(pass_many=True)
    def unwrap_request(self, data, many):
        if 'data' not in data:
            raise ma.ValidationError([{
                'detail': 'Object must include `data` key.',
                'source': {
                    'pointer': '/',
                },
            }])

        data = data['data']
        if many:
            return [self.unwrap_item(each) for each in data]
        return self.unwrap_item(data)

    def on_bind_field(self, field_name, field_obj):
        """Schema hook override. When binding fields, set load_from to the