"""
Classes for managing Checkpoints.
"""
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from tornado.web import HTTPError
from traitlets.config.configurable import LoggingConfigurable


class Checkpoints(LoggingConfigurable):
    """
    Base class for managing checkpoints for a ContentsManager.

    Subclasses are required to implement:

    create_checkpoint(self, contents_mgr, path)
    restore_checkpoint(self, contents_mgr, checkpoint_id, path)
    rename_checkpoint(self, checkpoint_id, old_path, new_path)
    delete_checkpoint(self, checkpoint_id, path)
    list_checkpoints(self, path)
    """

    def create_checkpoint(self, contents_mgr, path):
        """Create a checkpoint."""
        raise NotImplementedError("must be implemented in a subclass")

    def restore_checkpoint(self, contents_mgr, checkpoint_id, path):
        """Restore a checkpoint"""
        raise NotImplementedError("must be implemented in a subclass")

    def rename_checkpoint(self, checkpoint_id, old_path, new_path):
        """Rename a single checkpoint from old_path to new_path."""
        raise NotImplementedError("must be implemented in a subclass")

    def delete_checkpoint(self, checkpoint_id, path):
        """delete a checkpoint for a file"""
        raise NotImplementedError("must be implemented in a subclass")

    def list_checkpoints(self, path):
        """Return a list of checkpoints for a given file"""
        raise NotImplementedError("must be implemented in a subclass")

    def rename_all_checkpoints(self, old_path, new_path):
        """Rename all checkpoints for old_path to new_path."""
        for cp in self.list_checkpoints(old_path):
            self.rename_checkpoint(cp["id"], old_path, new_path)

    def delete_all_checkpoints(self, path):
        """Delete all checkpoints for the given path."""
        for checkpoint in self.list_checkpoints(path):
            self.delete_checkpoint(checkpoint["id"], path)


class GenericCheckpointsMixin(object):
    """
    Helper for creating Checkpoints subclasses that can be used with any
    ContentsManager.

    Provides a ContentsManager-agnostic implementation of `create_checkpoint`
    and `restore_checkpoint` in terms of the following operations:

    - create_file_checkpoint(self, content, format, path)
    - create_notebook_checkpoint(self, nb, path)
    - get_file_checkpoint(self, checkpoint_id, path)
    - get_notebook_checkpoint(self, checkpoint_id, path)

    To create a generic CheckpointManager, add this mixin to a class that
    implement the above four methods plus the remaining Checkpoints API
    methods:

    - delete_checkpoint(self, checkpoint_id, path)
    - list_checkpoints(self, path)
    - rename_checkpoint(self, checkpoint_id, old_path, new_path)
    """

    def create_checkpoint(self, contents_mgr, path):
        model = contents_mgr.get(path, content=True)
        type = model["type"]
        if type == "notebook":
            return self.create_notebook_checkpoint(
                model["content"],
                path,
            )
        elif type == "file":
            return self.create_file_checkpoint(
                model["content"],
                model["format"],
                path,
            )
        else:
            raise HTTPError(500, u"Unexpected type %s" % type)

    def restore_checkpoint(self, contents_mgr, checkpoint_id, path):
        """Restore a checkpoint."""
        type = contents_mgr.get(path, content=False)["type"]
        if type == "notebook":
            model = self.get_notebook_checkpoint(checkpoint_id, path)
        elif type == "file":
            model = self.get_file_checkpoint(checkpoint_id, path)
        else:
            raise HTTPError(500, u"Unexpected type %s" % type)
        contents_mgr.save(model, path)

    # Required Methods
    def create_file_checkpoint(self, content, format, path):
        """Create a checkpoint of the current state of a file

        Returns a checkpoint model for the new checkpoint.
        """
        raise NotImplementedError("must be implemented in a subclass")

    def create_notebook_checkpoint(self, nb, path):
        """Create a checkpoint of the current state of a file

        Returns a checkpoint model for the new checkpoint.
        """
        raise NotImplementedError("must be implemented in a subclass")

    def get_file_checkpoint(self, checkpoint_id, path):
        """Get the content of a checkpoint for a non-notebook file.

        Returns a dict of the form:
        {
            'type': 'file',
            'content': <str>,
            'format': {'text','base64'},
        }
        """
        raise NotImplementedError("must be implemented in a subclass")

    def get_notebook_checkpoint(self, checkpoint_id, path):
        """Get the content of a checkpoint for a notebook.

        Returns a dict of the form:
        {
            'type': 'notebook',
            'content': <output of nbformat.read>,
        }
        """
        raise NotImplementedError("must be implemented in a subclass")


class AsyncCheckpoints(Checkpoints):
    """
    Base class for managing checkpoints for a ContentsManager asynchronously.
    """

    async def create_checkpoint(self, contents_mgr, path):
        """Create a checkpoint."""
        raise NotImplementedError("must be implemented in a subclass")

    async def restore_checkpoint(self, contents_mgr, checkpoint_id, path):
        """Restore a checkpoint"""
        raise NotImplementedError("must be implemented in a subclass")

    async def rename_checkpoint(self, checkpoint_id, old_path, new_path):
        """Rename a single checkpoint from old_path to new_path."""
        raise NotImplementedError("must be implemented in a subclass")

    async def delete_checkpoint(self, checkpoint_id, path):
        """delete a checkpoint for a file"""
        raise NotImplementedError("must be implemented in a subclass")

    async def list_checkpoints(self, path):
        """Return a list of checkpoints for a given file"""
        raise NotImplementedError("must be implemented in a subclass")

    async def rename_all_checkpoints(self, old_path, new_path):
        """Rename all checkpoints for old_path to new_path."""
        for cp in await self.list_checkpoints(old_path):
            await self.rename_checkpoint(cp["id"], old_path, new_path)

    async def delete_all_checkpoints(self, path):
        """Delete all checkpoints for the given path."""
        for checkpoint in await self.list_checkpoints(path):
            await self.delete_checkpoint(checkpoint["id"], path)


class AsyncGenericCheckpointsMixin(GenericCheckpointsMixin):
    """
    Helper for creating Asynchronous Checkpoints subclasses that can be used with any
    ContentsManager.
    """

    async def create_checkpoint(self, contents_mgr, path):
        model = await contents_mgr.get(path, content=True)
        type = model["type"]
        if type == "notebook":
            return await self.create_notebook_checkpoint(
                model["content"],
                path,
            )
        elif type == "file":
            return await self.create_file_checkpoint(
                model["content"],
                model["format"],
                path,
            )
        else:
            raise HTTPError(500, u"Unexpected type %s" % type)

    async def restore_checkpoint(self, contents_mgr, checkpoint_id, path):
        """Restore a checkpoint."""
        type = await contents_mgr.get(path, content=False)["type"]
        if type == "notebook":
            model = await self.get_notebook_checkpoint(checkpoint_id, path)
        elif type == "file":
            model = await self.get_file_checkpoint(checkpoint_id, path)
        else:
            raise HTTPError(500, u"Unexpected type %s" % type)
        await contents_mgr.save(model, path)

    # Required Methods
    async def create_file_checkpoint(self, content, format, path):
        """Create a checkpoint of the current state of a file

        Returns a checkpoint model for the new checkpoint.
        """
        raise NotImplementedError("must be implemented in a subclass")

    async def create_notebook_checkpoint(self, nb, path):
        """Create a checkpoint of the current state of a file

        Returns a checkpoint model for the new checkpoint.
        """
        raise NotImplementedError("must be implemented in a subclass")

    async def get_file_checkpoint(self, checkpoint_id, path):
        """Get the content of a checkpoint for a non-notebook file.

        Returns a dict of the form:
        {
            'type': 'file',
            'content': <str>,
            'format': {'text','base64'},
        }
        """
        raise NotImplementedError("must be implemented in a subclass")

    async def get_notebook_checkpoint(self, checkpoint_id, path):
        """Get the content of a checkpoint for a notebook.

        Returns a dict of the form:
        {
            'type': 'notebook',
            'content': <output of nbformat.read>,
        }
        """
        raise NotImplementedError("must be implemented in a subclass")
