"""Tets the functionality of the :mod:`jukeboxcore.reftrack` module"""
import pytest
import mock
from django.contrib.contenttypes.models import ContentType

from jukeboxcore.reftrack import Reftrack, RefobjInterface, ReftypeInterface, ReftrackRoot
from jukeboxcore import djadapter
from jukeboxcore.filesys import TaskFileInfo
from jukeboxcore.gui.treemodel import TreeItem, TreeModel, ListItemData
from jukeboxcore.gui.filesysitemdata import TaskFileInfoItemData
from jukeboxcore.errors import ReftrackIntegrityError


class Reference(object):
    """A dummy reference object that does nothing but just tells
    if it is currently loaded or unloaded
    """

    def __init__(self, loaded=True, content=None):
        """Initialize a new reference with the given status

        :param loaded: True if loaded, false if unloaded
        :type loaded: :class:`bool`
        :param content: a list of refobjs
        :type content: list | None
        :raises: None
        """
        if content is None:
            content = []
        self.content = content
        for refobj in content:
            if refobj.referencedby is None:
                refobj.referencedby = self
        self.loaded = loaded
        if not loaded:
            self.unload()

    def load(self, ):
        """Set loaded to True and put the content back to Refobj.instances

        :returns: None
        :rtype: None
        :raises: None
        """
        for refobj in self.content:
            Refobj.instances.append(refobj)
        self.loaded = True

    def unload(self, ):
        """Set loaded to False and remove the content from Refobj.instances

        :returns: None
        :rtype: None
        :raises: None
        """
        for refobj in self.content:
            Refobj.instances.remove(refobj)
        self.loaded = False


class Refobj(object):
    """An refobj for testing with a refobject interface

    The :class:`RefobjInterface` is abstract and we need some kind of object for testing.
    This refobj stores type, status, parent, a reference, the taskfile.
    """

    instances = []

    def __init__(self, typ, parent, reference, taskfile, referencedby, identifier=-1):
        """Initialize a new refobj

        :param typ: the type of the entity
        :type typ: str
        :param parent: the parent refobj
        :type parent: :class:`Refobj` | None
        :param reference: the reference object
        :type reference: :class:`Referencce`
        :param taskfile: the taskfile that is loaded
        :type taskfile: :class:`jukeboxcore.djadapter.models.TaskFile`
        :param referencedby: The reference that holds this refobj.
        :type referencedby: :class:`Reference` | None
        :param identifier: a identifier for the gui
        :type identifier: int
        :rtype: None
        :raises: None
        """
        Refobj.instances.append(self)
        self.typ = typ
        self.parent = parent
        self.deleted = False
        self.children = []
        if parent:
            parent.children.append(self)
        self.reference = reference
        self.taskfile = taskfile
        self.referencedby = referencedby
        self.identifier = identifier

    def get_status(self, ):
        """Return the status

        :returns: the status, loaded, unloaded, imported
        :rtype: str
        :raises: None
        """
        if not self.reference:
            return Reftrack.IMPORTED
        else:
            if self.reference.loaded:
                return Reftrack.LOADED
            else:
                return Reftrack.UNLOADED


class DummyRefobjInterface(RefobjInterface):
    """A implementation for the refobjinterface for testing

    uses :class:`Refobj` as refobjects
    """

    def __init__(self, current):
        """

        :param current: the current shot or element that is open
        :type current: Shot or Asset
        :raises: None
        """
        super(DummyRefobjInterface, self).__init__()
        self.current = current

    def exists(self, refobj):
        """Check if the given refobj is still in the scene
        or if it has been deleted/dissapeared

        :param refobj: a reference object to query
        :type refobj: refobj
        :returns: True, if it still exists
        :rtype: :class:`bool`
        :raises: None
        """
        return not refobj.deleted

    def get_parent(self, refobj):
        """Return the parent

        :param refobj: the refobj to query
        :type refobj: :class:`Refobj`
        :returns: the parent
        :rtype: :class:`Refobj`
        :raises: None
        """
        return refobj.parent

    def set_parent(self, refobj, parent):
        """Set the parent

        :param refobj: the refobj to edit
        :type refobj: :class:`Refobj`
        :param parent: the new parent
        :type parent: :class:`Refobj`
        :returns: None
        :rtype: None
        :raises: None
        """
        if refobj.parent:
            refobj.parent.children.remove(refobj)
        if parent:
            parent.children.append(refobj)
        refobj.parent = parent

    def get_children(self, refobj):
        """Return the children of the refobj

        :param refobj: the refobj to query
        :type refobj: :class:`Refobj`
        :returns: the children
        :rtype: list of refobjects
        :raises: None
        """
        return refobj.children

    def get_typ(self, refobj):
        """Return the entity type of the given refobject

        :param refobj: the refobj to query
        :type refobj: refobj
        :returns: the entity type
        :rtype: str
        :raises: None
        """
        return refobj.typ

    def set_typ(self, refobj, typ):
        """Set the type of the given refobj

        :param refobj: the refobj to query
        :type refobj: refobj
        :param typ: the entity type
        :type typ: str
        :returns: None
        :rtype: None
        :raises: None
        """
        refobj.typ = typ

    def create_refobj(self, ):
        """Create and return a new refobj

        :returns: the new refobj
        :rtype: refobj
        :raises: None
        """
        return Refobj(None, None, None, None, False,)

    def referenced_by(self, refobj):
        """Return the reference that holds the given refobj.

        :param refobj: the refobj to query
        :type refobj: refobj
        :returns: The reference
        :rtype: :class:`Reference` | None
        :raises: None
        """
        return refobj.referencedby

    def delete_refobj(self, refobj):
        """Delete the given refobj

        :param refobj: the refobj to delete
        :type refobj: refobj
        :returns: None
        :rtype: None
        :raises: None
        """
        if refobj.parent:
            refobj.parent.children.remove(refobj)
        Refobj.instances.remove(refobj)

    def get_all_refobjs(self, ):
        """Return all refobjs in the scene that are not referenced

        :returns: all refobjs in the scene
        :rtype: list
        :raises: None
        """
        return Refobj.instances

    def get_current_element(self, ):
        """Return the currenty open Shot or Asset

        :returns: the currently open element
        :rtype: :class:`jukeboxcore.djadapter.models.Asset` | :class:`jukeboxcore.djadapter.models.Shot` | None
        :raises: None
        """
        return self.current

    def set_reference(self, refobj, reference):
        """Set the reference of the given refobj to reference

        :param refobj: the refobj to update
        :type refobj: refobj
        :param reference: the value for the refobj
        :returns: None
        :rtype: None
        :raises: None
        """
        refobj.reference = reference

    def get_reference(self, refobj):
        """Return the reference that the refobj represents or None if it is imported.

        :param refobj: the refobj to query
        :type refobj: refobj
        :returns: the reference object in the scene | None
        :raises: None
        """
        return refobj.reference

    def get_status(self, refobj):
        """Return the status of the given refobj

        :param refobj: the refobj to query
        :type refobj: refobj
        :returns: the status of the given refobj
        :rtype: str
        :raises: None
        """
        return refobj.get_status()

    def get_taskfile(self, refobj):
        """Return the taskfile that is loaded and represented by the refobj

        :param refobj: the refobj to query
        :type refobj: refobj
        :returns: The taskfile that is loaded in the scene
        :rtype: :class:`jukeboxcore.djadapter.TaskFile`
        :raises: None
        """
        return refobj.taskfile

    def get_id(self, refobj):
        """Return the id of the reftrack

        An id is a integer number that will be unique between
        all reftracks of the same parent, element and type, that have a
        refobject

        :param refobj: the refobj to query
        :type refobj: refobj
        :returns: the identifier
        :rtype: int
        :raises: None
        """
        return refobj.identifier

    def set_id(self, refobj, identifier):
        """Set the identifier on the given refobj

        :param refobj: the refobj to edit
        :type refobj: refobj
        :param identifier: the refobj id. Used to identify refobjects of the same parent, element and type in the UI
        :type identifier: int
        :returns: None
        :rtype: None
        :raises: None
        """
        refobj.identifier = identifier


class AssetReftypeInterface(ReftypeInterface):
    """Implementation for the reftype interface for testing
    for the type asset
    """

    def __init__(self, refobjinter):
        """Initialize a new ReftypeInterface

        :param refobjinter: the refobject interface
        :type refobjinter: :class:`RefobjInterface`
        :raises: None
        """
        super(AssetReftypeInterface, self).__init__(refobjinter)

    def reference(self, refobj, taskfileinfo):
        """Reference the given taskfileinfo into the scene and return the created reference object

        :param refobj: the refobj
        :type refobj: :class:`Refobj`
        :param taskfileinfo: The taskfileinfo that holds the information for what to reference
        :type taskfileinfo: :class:`jukeboxcore.filesys.TaskFileInfo`
        :returns: the reference that was created and should set on the appropriate refobj
        :raises: NotImplementedError
        """
        refobj.taskfile = djadapter.taskfiles.get(task=taskfileinfo.task,
                                        version=taskfileinfo.version,
                                        releasetype=taskfileinfo.releasetype,
                                        descriptor=taskfileinfo.descriptor,
                                        typ=taskfileinfo.typ)
        # also create a new refobj. This acts like the content of the reference
        # contains another refobject
        ref = Reference()
        refobj1 = Refobj("Asset", None, None, refobj.taskfile, ref)
        ref.content.append(refobj1)
        return ref

    def load(self, refobj, reference):
        """Load the given reference

        :param refobj: the refobj
        :type refobj: :class:`Refobj`
        :param reference: the reference object. E.g. in Maya a reference node
        :returns: None
        :rtype: None
        :raises: NotImplementedError
        """
        reference.load()

    def unload(self, refobj, reference):
        """Unload the given reference

        :param refobj: the refobj
        :type refobj: :class:`Refobj`
        :param reference: the reference object. E.g. in Maya a reference node
        :returns: None
        :rtype: None
        :raises: NotImplementedError
        """
        reference.unload()

    def replace(self, refobj, reference, taskfileinfo):
        """Replace the given reference with the given taskfileinfo

        :param refobj: the refobj
        :type refobj: :class:`Refobj`
        :param reference: the reference object. E.g. in Maya a reference node
        :param taskfileinfo: the taskfileinfo that will replace the old entity
        :type taskfileinfo: :class:`jukeboxcore.filesys.TaskFileInfo`
        :returns: None
        :rtype: None
        :raises: NotImplementedError
        """
        refobj.taskfile = djadapter.taskfiles.get(task=taskfileinfo.task,
                                        version=taskfileinfo.version,
                                        releasetype=taskfileinfo.releasetype,
                                        descriptor=taskfileinfo.descriptor,
                                        typ=taskfileinfo.typ)
        for r in refobj.reference.content:
            self.get_refobjinter().delete(r)
        robj1 = Refobj("Asset", None, None, refobj.taskfile, refobj.reference)
        refobj.reference.content.append(robj1)

    def delete(self, refobj):
        """Delete the content of the given refobj

        :param refobj: the refobj that represents the content that should be deleted
        :type refobj: refobj
        :returns: None
        :rtype: None
        :raises: NotImplementedError
        """