<reponame>nanome-ai/nanome
from nanome._internal._structure._complex import _Complex
from nanome._internal import _PluginInstance
from nanome._internal._network import PluginNetwork
from nanome._internal._network._commands._callbacks import _Messages
from nanome.util import Matrix, Logs
from .io import ComplexIO
from . import Base


class Complex(_Complex, Base):
    """
    | Represents a Complex that contains molecules.
    """
    io = ComplexIO()

    def __init__(self):
        super(Complex, self).__init__()
        self._rendering = Complex.Rendering(self)
        self._molecular = Complex.Molecular(self)
        self._transform = Complex.Transform(self)
        self.io = ComplexIO(self)

    def add_molecule(self, molecule):
        """
        | Add a molecule to this complex

        :param molecule: Molecule to add to the chain
        :type molecule: :class:`~nanome.structure.Molecule`
        """
        molecule.index = -1
        self._add_molecule(molecule)

    def remove_molecule(self, molecule):
        """
        | Remove a molecule from this complex

        :param molecule: Molecule to remove from the chain
        :type molecule: :class:`~nanome.structure.Molecule`
        """
        molecule.index = -1
        self._remove_molecule(molecule)

    # region Generators
    @property
    def molecules(self):
        """
        | The list of molecules within this complex
        """
        for molecule in self._molecules:
            yield molecule

    @molecules.setter
    def molecules(self, molecule_list):
        self._molecules = molecule_list

    @property
    def chains(self):
        """
        | The list of chains within this complex
        """
        for molecule in self.molecules:
            for chain in molecule.chains:
                yield chain

    @property
    def residues(self):
        """
        | The list of residues within this complex
        """
        for chain in self.chains:
            for residue in chain.residues:
                yield residue

    @property
    def atoms(self):
        """
        | The list of atoms within this complex
        """
        for residue in self.residues:
            for atom in residue.atoms:
                yield atom

    @property
    def bonds(self):
        """
        | The list of bonds within this complex
        """
        for residue in self.residues:
            for bond in residue.bonds:
                yield bond
    # endregion

    # region all fields
    @property
    def boxed(self):
        """
        | Represents if this complex is boxed/bordered in Nanome.

        :type: :class:`bool`
        """
        return self._boxed

    @boxed.setter
    def boxed(self, value):
        self._boxed = value

    @property
    def locked(self):
        """
        | Represents if this complex is locked and unmovable in Nanome.

        :type: :class:`bool`
        """
        return self._locked

    @locked.setter
    def locked(self, value):
        self._locked = value
        if (value):
            self._boxed = True

    @property
    def visible(self):
        """
        | Represents if this complex is visible in Nanome.

        :type: :class:`bool`
        """
        return self._visible

    @visible.setter
    def visible(self, value):
        self._visible = value

    @property
    def computing(self):
        return self._computing

    @computing.setter
    def computing(self, value):
        self._computing = value

    @property
    def current_frame(self):
        """
        | Represents the current animation frame the complex is in.

        :type: :class:`int`
        """
        return self._current_frame

    @current_frame.setter
    def current_frame(self, value):
        value = max(0, min(value, len(self._molecules) - 1))
        self._current_frame = value

    def set_current_frame(self, value):
        self.current_frame = value

    # returns true if the complex is selected on nanome.
    def get_selected(self):
        return self._selected

    def get_all_selected(self):
        for atom in self.atoms:
            if not atom.selected:
                return False
        return True

    def set_all_selected(self, value):
        for atom in self.atoms:
            atom.selected = value

    def set_surface_needs_redraw(self):
        self._surface_dirty = True

    @property
    def box_label(self):
        """
        | Represents the label on the box surrounding the complex

        :type: :class:`str`
        """
        return self._box_label

    @box_label.setter
    def box_label(self, value):
        self._box_label = value

    @property
    def name(self):
        """
        | Represents the name of the complex

        :type: :class:`str`
        """
        return self._name

    @name.setter
    def name(self, value):
        if type(value) is not str:
            value = str(value)
        self._name = value

    @property
    def index_tag(self):
        return self._index_tag

    @index_tag.setter
    def index_tag(self, value):
        self._index_tag = value

    @property
    def split_tag(self):
        return self._split_tag

    @split_tag.setter
    def split_tag(self, value):
        self._split_tag = value

    @property
    def full_name(self):
        """
        | Represents the full name of the complex with its tags and name

        :type: :class:`str`
        """
        fullname = self._name
        has_tag = False

        if self._index_tag > 0:
            fullname = fullname + " {" + str(self._index_tag)
            has_tag = True

        if self._split_tag is not None and len(self._split_tag) > 0:
            if has_tag:
                fullname = fullname + "-" + self._split_tag
            else:
                fullname = fullname + " {" + self._split_tag
            has_tag = True

        if has_tag:
            fullname = fullname + "}"

        return fullname

    @full_name.setter
    def full_name(self, value):
        self._name = value
        self._index_tag = 0
        self._split_tag = ''

    @property
    def position(self):
        """
        | Position of the complex

        :type: :class:`~nanome.util.Vector3`
        """
        return self._position

    @position.setter
    def position(self, value):
        self._position = value

    @property
    def rotation(self):
        """
        | Rotation of the complex

        :type: :class:`~nanome.util.Quaternion`
        """
        return self._rotation

    @rotation.setter
    def rotation(self, value):
        self._rotation = value

    @property
    def remarks(self):
        """
        | remarks section of the complex file

        :type: :class: dict
        """
        return self._remarks

    @remarks.setter
    def remarks(self, value):
        self._remarks = value

    def get_workspace_to_complex_matrix(self):
        return self.get_complex_to_workspace_matrix().get_inverse()

    def get_complex_to_workspace_matrix(self):
        return Matrix.compose_transformation_matrix(self._position, self._rotation)
    # endregion

    def convert_to_conformers(self, force_conformers=None):
        return self._convert_to_conformers(force_conformers)

    def convert_to_frames(self):
        return self._convert_to_frames()

    def register_complex_updated_callback(self, callback):
        self._complex_updated_callback = callback
        _PluginInstance._hook_complex_updated(self.index, callback)
        PluginNetwork._send(_Messages.hook_complex_updated, self.index, False)

    def register_selection_changed_callback(self, callback):
        self._selection_changed_callback = callback
        _PluginInstance._hook_selection_changed(self.index, callback)
        PluginNetwork._send(_Messages.hook_selection_changed, self.index, False)

    @staticmethod
    def align_origins(target_complex, *other_complexes):
        for complex in other_complexes:
            complex.position = target_complex.position.get_copy()
            complex.rotation = target_complex.rotation.get_copy()

    # region deprecated

    @property
    @Logs.deprecated()
    def rendering(self):
        return self._rendering

    @property
    @Logs.deprecated()
    def molecular(self):
        return self._molecular

    @property
    @Logs.deprecated()
    def transform(self):
        return self._transform

    class Rendering(object):
        def __init__(self, parent):
            self.parent = parent

        @property
        def boxed(self):
            return self.parent._boxed

        @boxed.setter
        def boxed(self, value):
            self.parent.boxed = value

        @property
        def locked(self):
            return self.parent.locked

        @locked.setter
        def locked(self, value):
            self.parent.locked = value
            if (value):
                self.parent.boxed = True

        @property
        def visible(self):
            return self.parent.visible

        @visible.setter
        def visible(self, value):
            self.parent.visible = value

        @property
        def computing(self):
            return self.parent.computing

        @computing.setter
        def computing(self, value):
            self.parent.computing = value

        @property
        def current_frame(self):
            return self.parent.current_frame

        @current_frame.setter
        def current_frame(self, value):
            self.parent.current_frame = value

        # returns true if the complex is selected on nanome.
        def get_selected(self):
            return self.parent.selected

        def set_surface_needs_redraw(self):
            self.parent.surface_dirty = True

        @property
        def box_label(self):
            return self._box_label

        @box_label.setter
        def box_label(self, value):
            self._box_label = value

    class Molecular(object):
        def __init__(self, parent):
            self.parent = parent

        @property
        def name(self):
            return self.parent.name

        @name.setter
        def name(self, value):
            self.parent.name = value

        @property
        def index_tag(self):
            return self.parent.index_tag

        @index_tag.setter
        def index_tag(self, value):
            self.parent.index_tag = value

        @property
        def split_tag(self):
            return self.parent.split_tag

        @split_tag.setter
        def split_tag(self, value):
            self.parent.split_tag = value

    class Transform(object):
        def __init__(self, parent):
            self.parent = parent

        @property
        def position(self):
            return self.parent.position

        @position.setter
        def position(self, value):
            self.parent.position = value

        @property
        def rotation(self):
            return self.parent.rotation

        @rotation.setter
        def rotation(self, value):
            self.parent.rotation = value

        def get_workspace_to_complex_matrix(self):
            rotation = Matrix.from_quaternion(self.parent.rotation)
            rotation.transpose()

            translation = Matrix.identity(4)
            translation[0][3] = -self.parent.position.x
            translation[1][3] = -self.parent.position.y
            translation[2][3] = -self.parent.position.z

            transformation = rotation * translation
            return transformation

        def get_complex_to_workspace_matrix(self):
            result = self.parent.get_workspace_to_complex_matrix()
            result = result.get_inverse()
            return result

    # endregion
Complex.io._setup_addon(Complex)
_Complex._create = Complex

from nanome._internal._structure._complex import _Complex
from nanome._internal import _PluginInstance
from nanome._internal._network import PluginNetwork
from nanome._internal._network._commands._callbacks import _Messages
from nanome.util import Matrix, Logs
from .io import ComplexIO
from . import Base


class Complex(_Complex, Base):
    """
    | Represents a Complex that contains molecules.
    """
    io = ComplexIO()

    def __init__(self):
        super(Complex, self).__init__()
        self._rendering = Complex.Rendering(self)
        self._molecular = Complex.Molecular(self)
        self._transform = Complex.Transform(self)
        self.io = ComplexIO(self)

    def add_molecule(self, molecule):
        """
        | Add a molecule to this complex

        :param molecule: Molecule to add to the chain
        :type molecule: :class:`~nanome.structure.Molecule`
        """
        molecule.index = -1
        self._add_molecule(molecule)

    def remove_molecule(self, molecule):
        """
        | Remove a molecule from this complex

        :param molecule: Molecule to remove from the chain
        :type molecule: :class:`~nanome.structure.Molecule`
        """
        molecule.index = -1
        self._remove_molecule(molecule)

    # region Generators
    @property
    def molecules(self):
        """
        | The list of molecules within this complex
        """
        for molecule in self._molecules:
            yield molecule

    @molecules.setter
    def molecules(self, molecule_list):
        self._molecules = molecule_list

    @property
    def chains(self):
        """
        | The list of chains within this complex
        """
        for molecule in self.molecules:
            for chain in molecule.chains:
                yield chain

    @property
    def residues(self):
        """
        | The list of residues within this complex
        """
        for chain in self.chains:
            for residue in chain.residues:
                yield residue

    @property
    def atoms(self):
        """
        | The list of atoms within this complex
        """
        for residue in self.residues:
            for atom in residue.atoms:
                yield atom

    @property
    def bonds(self):
        """
        | The list of bonds within this complex
        """
        for residue in self.residues:
            for bond in residue.bonds:
                yield bond
    # endregion

    # region all fields
    @property
    def boxed(self):
        """
        | Represents if this complex is boxed/bordered in Nanome.

        :type: :class:`bool`
        """
        return self._boxed

    @boxed.setter
    def boxed(self, value):
        self._boxed = value

    @property
    def locked(self):
        """
        | Represents if this complex is locked and unmovable in Nanome.

        :type: :class:`bool`
        """
        return self._locked

    @locked.setter
    def locked(self, value):
        self._locked = value
        if (value):
            self._boxed = True

    @property
    def visible(self):
        """
        | Represents if this complex is visible in Nanome.

        :type: :class:`bool`
        """
        return self._visible

    @visible.setter
    def visible(self, value):
        self._visible = value

    @property
    def computing(self):
        return self._computing

    @computing.setter
    def computing(self, value):
        self._computing = value

    @property
    def current_frame(self):
        """
        | Represents the current animation frame the complex is in.

        :type: :class:`int`
        """
        return self._current_frame

    @current_frame.setter
    def current_frame(self, value):
        value = max(0, min(value, len(self._molecules) - 1))
        self._current_frame = value

    def set_current_frame(self, value):
        self.current_frame = value

    # returns true if the complex is selected on nanome.
    def get_selected(self):
        return self._selected

    def get_all_selected(self):
        for atom in self.atoms:
            if not atom.selected:
                return False
        return True

    def set_all_selected(self, value):
        for atom in self.atoms:
            atom.selected = value

    def set_surface_needs_redraw(self):
        self._surface_dirty = True

    @property
    def box_label(self):
        """
        | Represents the label on the box surrounding the complex

        :type: :class:`str`
        """
        return self._box_label

    @box_label.setter
    def box_label(self, value):
        self._box_label = value

    @property
    def name(self):
        """
        | Represents the name of the complex

        :type: :class:`str`
        """
        return self._name

    @name.setter
    def name(self, value):
        if type(value) is not str:
            value = str(value)
        self._name = value

    @property
    def index_tag(self):
        return self._index_tag

    @index_tag.setter
    def index_tag(self, value):
        self._index_tag = value

    @property
    def split_tag(self):
        return self._split_tag

    @split_tag.setter
    def split_tag(self, value):
        self._split_tag = value

    @property
    def full_name(self):
        """
        | Represents the full name of the complex with its tags and name

        :type: :class:`str`
        """
        fullname = self._name
        has_tag = False

        if self._index_tag > 0:
            fullname = fullname + " {" + str(self._index_tag)
            has_tag = True

        if self._split_tag is not None and len(self._split_tag) > 0:
            if has_tag:
                fullname = fullname + "-" + self._split_tag
            else:
                fullname = fullname + " {" + self._split_tag
            has_tag = True

        if has_tag:
            fullname = fullname + "}"

        return fullname

    @full_name.setter
    def full_name(self, value):
        self._name = value
        self._index_tag = 0
        self._split_tag = ''

    @property
    def position(self):
        """
        | Position of the complex

        :type: :class:`~nanome.util.Vector3`
        """
        return self._position

    @position.setter
    def position(self, value):
        self._position = value

    @property
    def rotation(self):
        """
        | Rotation of the complex

        :type: :class:`~nanome.util.Quaternion`
        """
        return self._rotation

    @rotation.setter
    def rotation(self, value):
        self._rotation = value

    @property
    def remarks(self):
        """
        | remarks section of the complex file

        :type: :class: dict
        """
        return self._remarks

    @remarks.setter
    def remarks(self, value):
        self._remarks = value

    def get_workspace_to_complex_matrix(self):
        return self.get_complex_to_workspace_matrix().get_inverse()

    def get_complex_to_workspace_matrix(self):
        return Matrix.compose_transformation_matrix(self._position, self._rotation)
    # endregion

    def convert_to_conformers(self, force_conformers=None):
        return self._convert_to_conformers(force_conformers)

    def convert_to_frames(self):
        return self._convert_to_frames()

    def register_complex_updated_callback(self, callback):
        self._complex_updated_callback = callback
        _PluginInstance._hook_complex_updated(self.index, callback)
        PluginNetwork._send(_Messages.hook_complex_updated, self.index, False)

    def register_selection_changed_callback(self, callback):
        self._selection_changed_callback = callback
        _PluginInstance._hook_selection_changed(self.index, callback)
        PluginNetwork._send(_Messages.hook_selection_changed, self.index, False)

    @staticmethod
    def align_origins(target_complex, *other_complexes):
        for complex in other_complexes:
            complex.position = target_complex.position.get_copy()
            complex.rotation = target_complex.rotation.get_copy()

    # region deprecated

    @property
    @Logs.deprecated()
    def rendering(self):
        return self._rendering

    @property
    @Logs.deprecated()
    def molecular(self):
        return self._molecular

    @property
    @Logs.deprecated()
    def transform(self):
        return self._transform

    class Rendering(object):
        def __init__(self, parent):
            self.parent = parent

        @property
        def boxed(self):
            return self.parent._boxed

        @boxed.setter
        def boxed(self, value):
            self.parent.boxed = value

        @property
        def locked(self):
            return self.parent.locked

        @locked.setter
        def locked(self, value):
            self.parent.locked = value
            if (value):
                self.parent.boxed = True

        @property
        def visible(self):
            return self.parent.visible

        @visible.setter
        def visible(self, value):
            self.parent.visible = value

        @property
        def computing(self):
            return self.parent.computing

        @computing.setter
        def computing(self, value):
            self.parent.computing = value

        @property
        def current_frame(self):
            return self.parent.current_frame

        @current_frame.setter
        def current_frame(self, value):
            self.parent.current_frame = value

        # returns true if the complex is selected on nanome.
        def get_selected(self):
            return self.parent.selected

        def set_surface_needs_redraw(self):
            self.parent.surface_dirty = True

        @property
        def box_label(self):
            return self._box_label

        @box_label.setter
        def box_label(self, value):
            self._box_label = value

    class Molecular(object):
        def __init__(self, parent):
            self.parent = parent

        @property
        def name(self):
            return self.parent.name

        @name.setter
        def name(self, value):
            self.parent.name = value

        @property
        def index_tag(self):
            return self.parent.index_tag

        @index_tag.setter
        def index_tag(self, value):
            self.parent.index_tag = value

        @property
        def split_tag(self):
            return self.parent.split_tag

        @split_tag.setter
        def split_tag(self, value):
            self.parent.split_tag = value

    class Transform(object):
        def __init__(self, parent):
            self.parent = parent

        @property
        def position(self):
            return self.parent.position

        @position.setter
        def position(self, value):
            self.parent.position = value

        @property
        def rotation(self):
            return self.parent.rotation

        @rotation.setter
        def rotation(self, value):
            self.parent.rotation = value

        def get_workspace_to_complex_matrix(self):
            rotation = Matrix.from_quaternion(self.parent.rotation)
            rotation.transpose()

            translation = Matrix.identity(4)
            translation[0][3] = -self.parent.position.x
            translation[1][3] = -self.parent.position.y
            translation[2][3] = -self.parent.position.z

            transformation = rotation * translation
            return transformation

        def get_complex_to_workspace_matrix(self):
            result = self.parent.get_workspace_to_complex_matrix()
            result = result.get_inverse()
            return result

    # endregion
Complex.io._setup_addon(Complex)
_Complex._create = Complex
| Represents a Complex that contains molecules. | Add a molecule to this complex          :param molecule: Molecule to add to the chain         :type molecule: :class:`~nanome.structure.Molecule` | Remove a molecule from this complex          :param molecule: Molecule to remove from the chain         :type molecule: :class:`~nanome.structure.Molecule` # region Generators | The list of molecules within this complex | The list of chains within this complex | The list of residues within this complex | The list of atoms within this complex | The list of bonds within this complex # endregion # region all fields | Represents if this complex is boxed/bordered in Nanome.          :type: :class:`bool` | Represents if this complex is locked and unmovable in Nanome.          :type: :class:`bool` | Represents if this complex is visible in Nanome.          :type: :class:`bool` | Represents the current animation frame the complex is in.          :type: :class:`int` # returns true if the complex is selected on nanome. | Represents the label on the box surrounding the complex          :type: :class:`str` | Represents the name of the complex          :type: :class:`str` | Represents the full name of the complex with its tags and name          :type: :class:`str` | Position of the complex          :type: :class:`~nanome.util.Vector3` | Rotation of the complex          :type: :class:`~nanome.util.Quaternion` | remarks section of the complex file          :type: :class: dict # endregion # region deprecated # returns true if the complex is selected on nanome. # endregion