from Atomic.backends._docker import DockerBackend
from Atomic.backends._docker_errors import NoDockerDaemon
from Atomic.backends._ostree import OSTreeBackend
from Atomic.util import write_out, get_atomic_config

ATOMIC_CONFIG = get_atomic_config()
default_storage = ATOMIC_CONFIG.get('default_storage', "docker")

class BackendUtils(object):
    """
    Given an image, returns the back end that owns that image
    """

    BACKENDS = [DockerBackend, OSTreeBackend]

    @property
    def available_backends(self):
        return self._set_available_backends()

    def _set_available_backends(self):
        bes = []
        for x in self.BACKENDS:
            try:
                be = x()
                if be.available:
                    bes.append(x)
            except NoDockerDaemon:
                pass
        if len(bes) < 1:
            raise ValueError("No backends are enabled for Atomic.")
        return bes

    def dump_backends(self):
        backends = ''
        for i in self.available_backends:
            be = i()
            backends += "{}: Active, ".format(be.backend)
        write_out("Backends({})\n".format(backends))

    def get_backend_from_string(self, str_backend, init=True):
        for _backend in self.BACKENDS:
            backend = _backend
            backend_obj = _backend()
            if backend_obj.backend == str_backend:
                if init:
                    return backend_obj
                return backend
        raise ValueError("Unable to associate string '{}' with backend".format(str_backend))

    def _get_backend(self, backend):
        return self.get_backend_from_string(backend, init=False)

    def _get_backend_index_from_string(self, str_backend):
        return [x().backend for x in self.BACKENDS].index(str_backend)

    @staticmethod
    def backend_has_image(backend, img):
        return True if backend.has_image(img) else False

    @staticmethod
    def backend_has_container(backend, container):
        return True if backend.has_container(container) else False

    def get_backend_and_image_obj(self, img, str_preferred_backend=None, required=False):
        """
        Given an image name (str) and optionally a str reference to a backend,
        this method looks for the image firstly on the preferred backend and
        then on the alternate backends.  It returns a backend object and an
        image object.
        :param img: name of image to look for
        :param str_preferred_backend: i.e. 'docker'
        :return: backend object and image object
        """
        backends = list(self.available_backends)

        if str_preferred_backend and self._get_backend(str_preferred_backend) not in self.available_backends and required:
            raise ValueError("The '{}' backend appears unavailable/inactive".format(str_preferred_backend))
        # Check preferred backend first
        if str_preferred_backend and self._get_backend(str_preferred_backend) in self.available_backends:
            be = self.get_backend_from_string(str_preferred_backend)
            img_obj = be.has_image(img)
            if img_obj:
                return be, img_obj
            if required:
                raise ValueError("Unable to find {} in the {} backend".format(img, str_preferred_backend))
            # Didnt find in preferred, need to remove it from the list now
            del backends[self._get_backend_index_from_string(str_preferred_backend)]

        # Did not find it in the preferred backend, keep looking
        img_in_backends = []
        for backend in backends:
            be = backend()
            img_obj = be.has_image(img)
            if img_obj:
                img_in_backends.append((be, img_obj))

        if len(img_in_backends) == 1:
            return img_in_backends[0]
        if len(img_in_backends) == 0:
            raise ValueError("Unable to find '{}' in the following backends: {}".format(img, ", ".join([x().backend for x in self.available_backends])))
        raise ValueError("Found {} in multiple storage backends: {}".
                         format(img, ', '.join([x.backend for x, _ in img_in_backends])))

    def get_backend_and_container_obj(self, container_name, str_preferred_backend=None, required=False):
        """
        Given a container name (str) and optionally a str reference to a backend,
        this method looks for the container firstly on the preferred backend and
        then on the alternate backends.  It returns a backend object and a container
        object.
        :param container_name: name of image to look for
        :param str_preferred_backend: i.e. 'docker'
        :return: backend object and container object
        """

        if str_preferred_backend and self._get_backend(str_preferred_backend) not in self.available_backends and required:
            raise ValueError("The '{}' backend appears unavailable/inactive".format(str_preferred_backend))

        backends = list(self.available_backends)
        # Check preferred backend first
        if str_preferred_backend and self._get_backend(str_preferred_backend) in self.available_backends:
            be = self.get_backend_from_string(str_preferred_backend)
            con_obj = be.has_container(container_name)
            if con_obj:
                return be, con_obj
            if required:
                raise ValueError("Unable to find {} in the {} backend".format(container_name, str_preferred_backend))
            # Didnt find in preferred, need to remove it from the list now
            del backends[self._get_backend_index_from_string(str_preferred_backend)]

        container_in_backends = []
        for backend in backends:
            be = backend()
            con_obj = be.has_container(container_name)
            if con_obj:
                container_in_backends.append((be, con_obj))
        if len(container_in_backends) == 1:
            return container_in_backends[0]
        if len(container_in_backends) == 0:
            raise ValueError("Unable to find backend associated with container '{}'".format(container_name))
        raise ValueError("Found {} in multiple storage backends: {}".
                         format(container_name, ', '.join([x.backend for x in container_in_backends])))

    def get_images(self, get_all=False):
        backends = self.available_backends
        img_objs = []
        for backend in backends:
            be = backend()
            img_objs += be.get_images(get_all=get_all)
        return img_objs

    def get_containers(self):
        backends = self.available_backends
        con_objs = []
        for backend in backends:
            be = backend()
            con_objs += be.get_containers()
        return con_objs

    def get_container_obj_by_image_name(self, image_name, str_preferred_backend):
        pass

    @staticmethod
    def message_backend_change(previous, new):
        write_out("\nNote: Switching from the '{}' backend to the '{}' backend based on the 'atomic.type' label in the "
                  "image.  You can use --storage to override this behaviour.\n".format(previous, new))

