class ChimeraVisualizer:
    """Provides visualization for Chimera."""

    def __init__(self, plcomplex, chimera_module, tid):
        self.chimera = chimera_module
        self.tid = tid
        self.uid = plcomplex.uid
        self.plipname = 'PLIP-%i' % self.tid
        self.hetid, self.chain, self.pos = self.uid.split(':')
        self.pos = int(self.pos)
        self.colorbyname = self.chimera.colorTable.getColorByName
        self.rc = self.chimera.runCommand
        self.getPseudoBondGroup = self.chimera.misc.getPseudoBondGroup

        if plcomplex is not None:
            self.plcomplex = plcomplex
            self.protname = plcomplex.pdbid  # Name of protein with binding site
            self.ligname = plcomplex.hetid  # Name of ligand
            self.metal_ids = plcomplex.metal_ids
            self.water_ids = []
            self.bs_res_ids = []
            self.models = self.chimera.openModels

            for md in self.models.list():
                if md.name == self.plipname:
                    self.model = md

            self.atoms = self.atom_by_serialnumber()

    def set_initial_representations(self):
        """Set the initial representations"""
        self.update_model_dict()
        self.rc("background solid white")
        self.rc("setattr g display 0")  # Hide all pseudobonds
        self.rc("~display #%i & :/isHet & ~:%s" % (self.model_dict[self.plipname], self.hetid))

    def update_model_dict(self):
        """Updates the model dictionary"""
        dct = {}
        models = self.chimera.openModels
        for md in models.list():
            dct[md.name] = md.id
        self.model_dict = dct

    def atom_by_serialnumber(self):
        """Provides a dictionary mapping serial numbers to their atom objects."""
        atm_by_snum = {}
        for atom in self.model.atoms:
            atm_by_snum[atom.serialNumber] = atom
        return atm_by_snum

    def show_hydrophobic(self):
        """Visualizes hydrophobic contacts."""
        grp = self.getPseudoBondGroup("Hydrophobic Interactions-%i" % self.tid, associateWith=[self.model])
        grp.lineType = self.chimera.Dash
        grp.lineWidth = 3
        grp.color = self.colorbyname('gray')
        for i in self.plcomplex.hydrophobic_contacts.pairs_ids:
            self.bs_res_ids.append(i[0])

    def show_hbonds(self):
        """Visualizes hydrogen bonds."""
        grp = self.getPseudoBondGroup("Hydrogen Bonds-%i" % self.tid, associateWith=[self.model])
        grp.lineWidth = 3
        for i in self.plcomplex.hbonds.ldon_id:
            b = grp.newPseudoBond(self.atoms[i[0]], self.atoms[i[1]])
            b.color = self.colorbyname('blue')
            self.bs_res_ids.append(i[0])
        for i in self.plcomplex.hbonds.pdon_id:
            b = grp.newPseudoBond(self.atoms[i[0]], self.atoms[i[1]])
            b.color = self.colorbyname('blue')
            self.bs_res_ids.append(i[1])

    def show_halogen(self):
        """Visualizes halogen bonds."""
        grp = self.getPseudoBondGroup("HalogenBonds-%i" % self.tid, associateWith=[self.model])
        grp.lineWidth = 3
        for i in self.plcomplex.halogen_bonds:
            b = grp.newPseudoBond(self.atoms[i[0]], self.atoms[i[1]])
            b.color = self.colorbyname('turquoise')

            self.bs_res_ids.append(i.acc_id)

    def show_stacking(self):
        """Visualizes pi-stacking interactions."""
        grp = self.getPseudoBondGroup("pi-Stacking-%i" % self.tid, associateWith=[self.model])
        grp.lineWidth = 3
        grp.lineType = self.chimera.Dash
        for i, stack in enumerate(self.plcomplex.pistacking):

            m = self.model
            r = m.newResidue("pseudoatoms", " ", 1, " ")
            centroid_prot = m.newAtom("CENTROID", self.chimera.Element("CENTROID"))
            x, y, z = stack.proteinring_center
            centroid_prot.setCoord(self.chimera.Coord(x, y, z))
            r.addAtom(centroid_prot)

            centroid_lig = m.newAtom("CENTROID", self.chimera.Element("CENTROID"))
            x, y, z = stack.ligandring_center
            centroid_lig.setCoord(self.chimera.Coord(x, y, z))
            r.addAtom(centroid_lig)

            b = grp.newPseudoBond(centroid_lig, centroid_prot)
            b.color = self.colorbyname('forest green')

            self.bs_res_ids += stack.proteinring_atoms

    def show_cationpi(self):
        """Visualizes cation-pi interactions"""
        grp = self.getPseudoBondGroup("Cation-Pi-%i" % self.tid, associateWith=[self.model])
        grp.lineWidth = 3
        grp.lineType = self.chimera.Dash
        for i, cat in enumerate(self.plcomplex.pication):

            m = self.model
            r = m.newResidue("pseudoatoms", " ", 1, " ")
            chargecenter = m.newAtom("CHARGE", self.chimera.Element("CHARGE"))
            x, y, z = cat.charge_center
            chargecenter.setCoord(self.chimera.Coord(x, y, z))
            r.addAtom(chargecenter)

            centroid = m.newAtom("CENTROID", self.chimera.Element("CENTROID"))
            x, y, z = cat.ring_center
            centroid.setCoord(self.chimera.Coord(x, y, z))
            r.addAtom(centroid)

            b = grp.newPseudoBond(centroid, chargecenter)
            b.color = self.colorbyname('orange')

            if cat.protcharged:
                self.bs_res_ids += cat.charge_atoms
            else:
                self.bs_res_ids += cat.ring_atoms

    def show_sbridges(self):
        """Visualizes salt bridges."""
        # Salt Bridges
        grp = self.getPseudoBondGroup("Salt Bridges-%i" % self.tid, associateWith=[self.model])
        grp.lineWidth = 3
        grp.lineType = self.chimera.Dash
        for i, sbridge in enumerate(self.plcomplex.saltbridges):

            m = self.model
            r = m.newResidue("pseudoatoms", " ", 1, " ")
            chargecenter1 = m.newAtom("CHARGE", self.chimera.Element("CHARGE"))
            x, y, z = sbridge.positive_center
            chargecenter1.setCoord(self.chimera.Coord(x, y, z))
            r.addAtom(chargecenter1)

            chargecenter2 = m.newAtom("CHARGE", self.chimera.Element("CHARGE"))
            x, y, z = sbridge.negative_center
            chargecenter2.setCoord(self.chimera.Coord(x, y, z))
            r.addAtom(chargecenter2)

            b = grp.newPseudoBond(chargecenter1, chargecenter2)
            b.color = self.colorbyname('yellow')

            if sbridge.protispos:
                self.bs_res_ids += sbridge.positive_atoms
            else:
                self.bs_res_ids += sbridge.negative_atoms

    def show_wbridges(self):
        """Visualizes water bridges"""
        grp = self.getPseudoBondGroup("Water Bridges-%i" % self.tid, associateWith=[self.model])
        grp.lineWidth = 3
        for i, wbridge in enumerate(self.plcomplex.waterbridges):
            c = grp.newPseudoBond(self.atoms[wbridge.water_id], self.atoms[wbridge.acc_id])
            c.color = self.colorbyname('cornflower blue')
            self.water_ids.append(wbridge.water_id)
            b = grp.newPseudoBond(self.atoms[wbridge.don_id], self.atoms[wbridge.water_id])
            b.color = self.colorbyname('cornflower blue')
            self.water_ids.append(wbridge.water_id)
            if wbridge.protisdon:
                self.bs_res_ids.append(wbridge.don_id)
            else:
                self.bs_res_ids.append(wbridge.acc_id)

    def show_metal(self):
        """Visualizes metal coordination."""
        grp = self.getPseudoBondGroup("Metal Coordination-%i" % self.tid, associateWith=[self.model])
        grp.lineWidth = 3
        for i, metal in enumerate(self.plcomplex.metal_complexes):
            c = grp.newPseudoBond(self.atoms[metal.metal_id], self.atoms[metal.target_id])
            c.color = self.colorbyname('magenta')

            if metal.location == 'water':
                self.water_ids.append(metal.target_id)

            if metal.location.startswith('protein'):
                self.bs_res_ids.append(metal.target_id)

    def cleanup(self):
        """Clean up the visualization."""

        if not len(self.water_ids) == 0:
            # Hide all non-interacting water molecules
            water_selection = []
            for wid in self.water_ids:
                water_selection.append('serialNumber=%i' % wid)
            self.rc("~display :HOH")
            self.rc("display :@/%s" % " or ".join(water_selection))

        # Show all interacting binding site residues
        self.rc("~display #%i & ~:/isHet" % self.model_dict[self.plipname])
        self.rc("display :%s" % ",".join([str(self.atoms[bsid].residue.id) for bsid in self.bs_res_ids]))
        self.rc("color lightblue :HOH")

    def zoom_to_ligand(self):
        """Centers the view on the ligand and its binding site residues."""
        self.rc("center #%i & :%s" % (self.model_dict[self.plipname], self.hetid))

    def refinements(self):
        """Details for the visualization."""
        self.rc("setattr a color gray @CENTROID")
        self.rc("setattr a radius 0.3 @CENTROID")
        self.rc("represent sphere @CENTROID")
        self.rc("setattr a color orange @CHARGE")
        self.rc("setattr a radius 0.4 @CHARGE")
        self.rc("represent sphere @CHARGE")
        self.rc("display :pseudoatoms")
