#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
This module provides some classes to implements default strategies
for managers.

They will load all the resources of the manager, allow the user to make
requests and persist all the objects after.
"""

from napixd.exceptions import NotFound, Duplicate, ValidationError
from napixd.managers import Manager
from napixd.managers.base import ManagerType


class ReadOnlyDictManager(Manager):
    """
    Manager that manages a list (or a dict) of objects

    Once it's created, it manage a internal list of resources.
    This list of resources is generated by the method :meth:`load`
    that MUST be overriden.
    It is saved in the manager and used to serve the basic operations

    The list of resources MUST be an indexable object.
    The id of the resources MUST be the index of the resource
    in the internal list.

    This manager give only access to resources by read-only GET requests.


    .. attribute:: resources

        A mapping of the loaded resources.
        This is lazily evaluated.
    """

    # Methods to override
    def load(self, context):
        """
        Load: Return the list of the resources managed by this manager
        take the context that created this manager instance as argument
        """
        raise NotImplementedError('load')

    def _get_resources(self):
        if hasattr(self, '_resources'):
            return self._resources

        resources = self.load(self.context)
        try:
            self._resources = dict(resources)
        except TypeError:
            raise ValueError('load did not return a dict, but {0}',
                             type(resources).__name__)
        return self._resources

    def _set_resources(self, value):
        self._resources = value
    # resources are lazy loaded
    resources = property(_get_resources, _set_resources)

    def get_resource(self, resource_id):
        try:
            return self.resources[resource_id]
        except KeyError:
            raise NotFound(resource_id)

    def list_resource(self):
        return self.resources.keys()


class DictManager(ReadOnlyDictManager):
    """
    Manager for a dictionary of resources.

    This manager inherits of the read-only version
    and add support for modifications
    When the resources list is modified, the method :meth:`save`
    is called to persist the resources

    The id of a new resource is generated by the method :meth:`generate_new_id`.
    """

    def _save(self):
        return self.save(self.context, self.resources)

    def save(self, context, resources):
        """
        Saves the resources after they have been altered by the user's request.
        Idempotent methods (GET, HEAD) don't trigger :meth:`save`
        """
        raise NotImplementedError('save')

    def generate_id(self, resource_dict, original):
        """
        Computes the id of a resource.

        *resource_dict* is the resource as returned by :meth:`validate`.
        *original* is either `None` if the resource is created or a
        :class:`~napixd.service.wrapper.ResourceWrapper`
        with the resource being updated.

        By default, this method calls :meth:`generate_new_id` on creation requests,
        and returns :class:`~napixd.service.wrapper.ResourceWrapper.id`
        on update requests.
        """
        if original is None:
            return self.generate_new_id(resource_dict)
        return original.id

    def generate_new_id(self, resource_dict):
        """
        Generates a new identifier for the resource dict given
        It must be overriden by the sub classes
        """
        raise NotImplementedError('generate_new_id')

    # private methods and attributes

    def _set_resource(self, resource_id, resource_dict):
        """
        Set a resource inside the resources list
        """
        self.resources[resource_id] = resource_dict

    def modify_resource(self, resource, resource_dict):
        new_id = self.generate_id(resource_dict, resource)
        resource.resource.update(resource_dict)
        if new_id != resource.id:
            del self.resources[resource.id]
        self._set_resource(new_id, resource.resource)
        self._save()
        return new_id

    def create_resource(self, resource_dict):
        resource_id = self.generate_id(resource_dict, None)
        if resource_id in self.resources:
            raise Duplicate(resource_id)
        self._set_resource(resource_id, resource_dict)
        self._save()
        return resource_id

    def delete_resource(self, resource):
        try:
            del self.resources[resource.id]
        except KeyError:
            raise NotFound(resource.id)
        else:
            self._save()

    @classmethod
    def implements(cls):
        """
        If :meth:`save` is not overriden
        """
        implemented = ManagerType.implements(cls)
        if cls.save == DictManager.save:
            implemented.discard('create_resource')
            implemented.discard('delete_resource')
            implemented.discard('modify_resource')

        if cls.generate_id == DictManager.generate_id and cls.generate_new_id == DictManager.generate_new_id:
            implemented.discard('create_resource')
        return implemented


class ListManager(DictManager):
    """
    Manager that manages a list of resources
    """

    def create_resource(self, resource_dict):
        self.resources.append(resource_dict)
        self._save()
        return len(self.resources) - 1

    def list_resource(self):
        return range(0, len(self.resources))

    def validate_id(self):
        """
        Override the base method to limit id to integer values
        """
        try:
            return int(self)
        except (ValueError, TypeError):
            raise ValidationError('This resource ids are integers')


class FileManager(DictManager):
    """
    Manager that is attached to a file
    """

    def load(self, context):
        """
        Opens the file for reading, gives it to :meth:`parser`
        so that it extracs the data
        """
        filename = self.get_filename(context)
        try:
            handle = open(filename, 'r')
        except IOError:
            return {}

        with handle as fp:
            return dict(self.parse(fp))

    def save(self, context, resources):
        """
        Opens the file and then gives it to :meth:`write`
        """
        filename = self.get_filename(context)
        with open(filename, 'w') as fp:
            self.write(fp, resources)

    def get_filename(self, context):
        """
        Get the filename from the context
        """
        raise NotImplementedError('get_filename')

    def parse(self, fp):
        """
        Extract the datas from the file stream given
        """
        raise NotImplementedError('parse')

    def write(self, fp, resources):
        """
        Write the content to the file stream given
        """
        raise NotImplementedError('write')


class ReadOnlyUniqueManager(Manager):
    """
    Manager for an unique resource.

    For example ``/my/self`` in the auth server.
    The manager contains only one resource named ``self``.
    Any other request raises a :exc:`napixd.exceptions.NotFound`

    The object is not updatable, deletable.
    The manager does not support creation.
    """

    @property
    def NAME(self):
        """
        The expected `id`.

        By default it is computed from
        :meth:`napixd.managers.base.Manager.get_name`.
        """
        return self.__class__.get_name()

    def list_resource(self):
        """
        Returns a list containing only :attr:`NAME` as its the only valid id.
        """
        return [self.NAME]

    def load(self, context):
        """
        Loads the resource from the `context`.

        It musts return the only resource as a dict.
        The context is the :attr:`napixd.managers.base.Manager.context`
        """
        raise NotImplementedError('load')

    def get_resource(self, id):
        """
        Get the resource from :meth:`load`
        """
        if not id == self.NAME:
            raise NotFound('There is only one resource `{0}`'.format(self.name))
        return self.load(self.context)


class UniqueManager(ReadOnlyUniqueManager):
    """
    Like the :class:`ReadOnlyUniqueManager` but also implements
    the modifications methods.
    """
    def save(self, context, resource):
        """
        Save the modified resource.

        The *context* is the same :attr:`napixd.managers.base.Manager.context`
        passed to :meth:`ReadOnlyUniqueManager.load`.
        """
        raise NotImplementedError('save')

    def modify_resource(self, wrapper, resource_dict):
        """
        Modify the loaded resource.
        """
        resource = wrapper.resource
        resource.update(resource_dict)
        self.save(self.context, resource)
