Source

utils/MoorhenMolecule.ts

import 'pako';
import { 
    guid, readTextFile, readGemmiStructure, centreOnGemmiAtoms, 
    getRandomMoleculeColour, doDownload, formatLigandSVG, getCentreAtom, parseAtomInfoLabel
 } from './utils'
import { MoorhenMoleculeRepresentation } from "./MoorhenMoleculeRepresentation"
import { MoorhenColourRule } from "./MoorhenColourRule"
import { quatToMat4 } from '../WebGLgComponents/quatToMat4.js';
import { isDarkBackground } from '../WebGLgComponents/mgWebGL'
import { hideMolecule } from '../store/moleculesSlice';
import * as vec3 from 'gl-matrix/vec3';
import * as mat3 from 'gl-matrix/mat3';
import * as quat4 from 'gl-matrix/quat';
import { moorhen } from "../types/moorhen"
import { webGL } from "../types/mgWebGL"
import { gemmi } from "../types/gemmi"
import { libcootApi } from '../types/libcoot';
import { privateer } from '../types/privateer';
import MoorhenReduxStore from "../store/MoorhenReduxStore";
import { ToolkitStore } from '@reduxjs/toolkit/dist/configureStore';

/**
 * Represents a molecule
 * @property {string} name - The name assigned to this molecule instance
 * @property {number} molNo - The imol assigned to this molecule instance
 * @property {boolean} atomsDirty - Whether the cached atoms are outdated 
 * @property {boolean} symmetryOn - Whether the symmetry is currently being displayed
 * @property {object} sequences - List of sequences present in the molecule
 * @property {object} gemmiStructure - Object representation of the cached gemmi structure for this molecule
 * @property {React.RefObject<moorhen.CommandCentre>} commandCentre - A react reference to the command centre instance
 * @property {React.RefObject<webGL.MGWebGL>} glRef - A react reference to the MGWebGL instance
 * @property {string} monomerLibraryPath - A string with the path to the monomer library, relative to the root of the app
 * @constructor
 * @param {React.RefObject<moorhen.CommandCentre>} commandCentre - A react reference to the command centre instance
 * @param {React.RefObject<webGL.MGWebGL>} glRef - A react reference to the MGWebGL instance
 * @param {ToolkitStore} [store=undefined] - A Redux store. By default Moorhen Redux store will be used
 * @param {string} [monomerLibraryPath="./baby-gru/monomers"] - A string with the path to the monomer library, relative to the root of the app
 * @example
 * import { MoorhenMolecule } from 'moorhen';
 * 
 * // Create a new molecule
 * const molecule = new MoorhenMolecule(commandCentre, glRef, monomerLibraryPath);
 * 
 * // Set some defaults
 * molecule.setBackgroundColour(glRef.current.background_colour)
 * 
 * // Load file from a URL
 * molecule.loadToCootFromURL('/uri/to/file.pdb', 'mol-1');
 * 
 * // Draw coot bond representation and centre on molecule
 * molecule.fetchIfDirtyAndDraw('CBs');
 * molecule.centreOn();
 * 
 * // Delete molecule
 * molecule.delete();
 */
export class MoorhenMolecule implements moorhen.Molecule {

    type: string;
    atomCount: number;
    commandCentre: React.RefObject<moorhen.CommandCentre>;
    glRef: React.RefObject<webGL.MGWebGL>;
    atomsDirty: boolean;
    name: string;
    molNo: number | null
    gemmiStructure: gemmi.Structure;
    sequences: moorhen.Sequence[];
    representations: moorhen.MoleculeRepresentation[];
    ligands: moorhen.LigandInfo[];
    ligandDicts: { [comp_id: string]: string };
    connectedToMaps: number[];
    excludedSelections: string[];
    excludedCids: string[];
    symmetryOn: boolean;
    biomolOn: boolean;
    symmetryRadius: number;
    symmetryMatrices: number[][][];
    gaussianSurfaceSettings: moorhen.gaussianSurfSettings;
    isDarkBackground: boolean;
    defaultBondOptions: moorhen.cootBondOptions;
    defaultM2tParams: moorhen.m2tParameters;
    defaultResidueEnvironmentOptions: moorhen.residueEnvironmentOptions;
    displayObjectsTransformation: { origin: [number, number, number], quat: any, centre: [number, number, number] }
    uniqueId: string;
    monomerLibraryPath: string;
    defaultColourRules: moorhen.ColourRule[];
    adaptativeBondsRepresentation: moorhen.MoleculeRepresentation;
    hoverRepresentation: moorhen.MoleculeRepresentation;
    unitCellRepresentation: moorhen.MoleculeRepresentation;
    environmentRepresentation: moorhen.MoleculeRepresentation;
    selectionRepresentation: moorhen.MoleculeRepresentation;
    hasGlycans: boolean;
    hasDNA: boolean;
    restraints: {maxRadius: number, cid: string}[];
    isLigand: boolean;
    coordsFormat: moorhen.coorFormats;
    cachedPrivateerValidation: privateer.ResultsEntry[];
    cachedGemmiAtoms:moorhen.AtomInfo[];
    cachedLigandSVGs: {[key: string]: string};
    moleculeDiameter: number;
    adaptativeBondsEnabled: boolean;
    store: ToolkitStore;
    headerInfo: libcootApi.headerInfoJS;
    isMRSearchModel: boolean;

    constructor(commandCentre: React.RefObject<moorhen.CommandCentre>, glRef: React.RefObject<webGL.MGWebGL>, store: ToolkitStore = MoorhenReduxStore, monomerLibraryPath = "./baby-gru/monomers") {
        this.type = 'molecule'
        this.commandCentre = commandCentre
        this.glRef = glRef
        this.store = store
        this.atomsDirty = true
        this.name = "unnamed"
        this.molNo = null
        this.coordsFormat = null
        this.gemmiStructure = null
        this.sequences = []
        this.headerInfo = null
        this.cachedGemmiAtoms = null
        this.cachedLigandSVGs = null
        this.cachedPrivateerValidation = null
        this.ligands = null
        this.ligandDicts = {}
        this.connectedToMaps = null
        this.representations = []
        this.excludedSelections = []
        this.excludedCids = []
        this.symmetryOn = false
        this.biomolOn = false
        this.symmetryRadius = 25
        this.symmetryMatrices = []
        this.isDarkBackground = false
        this.atomCount = null
        this.gaussianSurfaceSettings = {
            sigma: 4.4,
            countourLevel: 4.0,
            boxRadius: 5.0,
            gridScale: 0.7,
            bFactor: 100
        }
        this.defaultBondOptions = {
            smoothness: 1,
            width: 0.1,
            atomRadiusBondRatio: 1
        }
        this.defaultM2tParams = {
            ribbonStyleCoilThickness: 0.3,
            ribbonStyleHelixWidth: 1.2,
            ribbonStyleStrandWidth: 1.2,
            ribbonStyleArrowWidth: 1.5,
            ribbonStyleDNARNAWidth: 1.5,
            ribbonStyleAxialSampling: 6,
            cylindersStyleAngularSampling: 6,
            cylindersStyleCylinderRadius: 0.2,
            cylindersStyleBallRadius: 0.2,
            surfaceStyleProbeRadius: 1.4,
            ballsStyleRadiusMultiplier: 1,
            nucleotideRibbonStyle: 'StickBases',
            dishStyleAngularSampling: 32
        }
        this.defaultResidueEnvironmentOptions = {
            maxDist: 8,
            backgroundRepresentation: "CRs",
            focusRepresentation: "CBs",
            labelled: true,
            showHBonds: true,
            showContacts: true
        }
        this.restraints = []
        this.adaptativeBondsEnabled = false
        this.hasDNA = false
        this.hasGlycans = false
        this.isLigand = false
        this.isMRSearchModel = false
        this.displayObjectsTransformation = { origin: [0, 0, 0], quat: null, centre: [0, 0, 0] }
        this.uniqueId = guid()
        this.monomerLibraryPath = monomerLibraryPath
        this.defaultColourRules = null
        this.moleculeDiameter = null
        this.unitCellRepresentation = new MoorhenMoleculeRepresentation('unitCell', '/*/*/*/*', this.commandCentre, this.glRef)
        this.unitCellRepresentation.setParentMolecule(this)
        this.environmentRepresentation = new MoorhenMoleculeRepresentation('environment', null, this.commandCentre, this.glRef)
        this.environmentRepresentation.setParentMolecule(this)
        this.hoverRepresentation = new MoorhenMoleculeRepresentation('hover', null, this.commandCentre, this.glRef)
        this.hoverRepresentation.setParentMolecule(this)
        this.selectionRepresentation = new MoorhenMoleculeRepresentation('residueSelection', null, this.commandCentre, this.glRef)
        this.selectionRepresentation.setParentMolecule(this)
        this.adaptativeBondsRepresentation = new MoorhenMoleculeRepresentation('adaptativeBonds', null, this.commandCentre, this.glRef)
        this.adaptativeBondsRepresentation.setParentMolecule(this)
    }

    /**
     * Replace the current molecule with the model in a file
     * @param {string} fileUrl - The uri to the file with the new model
     */
    async replaceModelWithFile(fileUrl: string): Promise<void> {
        let coordData: string
        let fetchResponse: Response

        try {
            fetchResponse = await fetch(fileUrl)
        } catch (err) {
            return Promise.reject(`Unable to fetch file ${fileUrl}`)
        }

        if (fetchResponse.ok) {
            coordData = await fetchResponse.text()
        } else {
            return Promise.reject(`Error fetching data from url ${fileUrl}`)
        }

        return this.replaceModelWithCoordData(coordData)
    }

    /**
     * Replace the current molecule some file contents
     * @param {string} coordData - The coord data for new model
     */
    async replaceModelWithCoordData(coordData: string): Promise<void> {
        const cootResponse = await this.commandCentre.current.cootCommand({
            returnType: "status",
            command: 'replace_molecule_by_model_from_string',
            commandArgs: [this.molNo, coordData],
            changesMolecules: [this.molNo]
        }, true)

        if (cootResponse.data.result.status === 'Completed') {
            this.atomsDirty = true
            return this.redraw()
        }

        return Promise.reject(cootResponse.data.result.status)
    }

    /**
     * Turn on/off molecule symmetry
     */
    toggleBiomolecule() : void {
        this.biomolOn = !this.biomolOn;
        if (this.biomolOn) {
            this.symmetryOn = false;
        } else {
            this.representations.forEach(representation => {
                representation.buffers.forEach(buffer => {
                    buffer.changeColourWithSymmetry = true
                })
            })
        }
        return this.drawBiomolecule()
    }

    /**
     * Turn on/off molecule symmetry
     */
    toggleSymmetry(): Promise<void> {
        this.symmetryOn = !this.symmetryOn;
        if (this.symmetryOn) {
            this.biomolOn = false;
        }
        return this.drawSymmetry()
    }

    /**
     * Set the radius to draw symmetry mates
     * @param {number} radius - Symmetry mates with an atom within this radius will be drawn
     */
    setSymmetryRadius(radius: number): Promise<void> {
        this.symmetryRadius = radius
        return this.drawSymmetry()
    }

    /**
     * Fetch the matrices representing the biomolecule of the current model from gemmi
     */
    fetchBiomoleculeMatrix() : void {
        this.symmetryMatrices = []
        if (this.biomolOn) {
            const assemblies = this.gemmiStructure.assemblies
            const n_assembly = assemblies.size()
            if (n_assembly > 0) {
                const assembly = assemblies.get(0)
                const generators = assembly.generators
                const n_gen = generators.size()
                if (n_gen > 0) {
                    const gen = generators.get(0)
                    const operators = gen.operators
                    const n_op = operators.size()
                    for (let i_op=0; i_op < n_op; i_op++) {
                        let mat16 = []
                        const op = operators.get(i_op)
                        const transform = op.transform
                        const vec = transform.vec
                        const mat = transform.mat
                        const mat_array = mat.as_array()
                        mat16.push(mat_array[0]); mat16.push(mat_array[1]); mat16.push(mat_array[2]); mat16.push(vec.x);
                        mat16.push(mat_array[3]); mat16.push(mat_array[4]); mat16.push(mat_array[5]); mat16.push(vec.y);
                        mat16.push(mat_array[6]); mat16.push(mat_array[7]); mat16.push(mat_array[8]); mat16.push(vec.z);
                        mat16.push(0.0);          mat16.push(0.0);          mat16.push(0.0);          mat16.push(1.0);
                        this.symmetryMatrices.push(mat16);
                        transform.delete()
                        vec.delete()
                        mat.delete()
                        op.delete()
                    }
                    operators.delete()
                    gen.delete()
                }
                generators.delete()
                assembly.delete()
            }
            assemblies.delete()
        }
    }

    /**
     * Fetch the symmetry matrix for the current model from libcoot api
     */
    async fetchSymmetryMatrix(): Promise<void> {
        if (!this.symmetryOn) {
            this.symmetryMatrices = []
        } else {
            const selectionCentre: number[] = this.glRef.current.origin.map(coord => -coord)
            const response = await this.commandCentre.current.cootCommand({
                returnType: "symmetry",
                command: 'get_symmetry_with_matrices',
                commandArgs: [this.molNo, this.symmetryRadius, ...selectionCentre]
            }, false) as moorhen.WorkerResponse<{ matrix: number[][] }[]>
            this.symmetryMatrices = response.data.result.result.map(symm => symm.matrix)
        }
    }

    /**
     * Draw symmetry mates for the current molecule
     * @param {boolean} [fetchSymMatrix=true] - Indicates whether a new symmetry matrix must be fetched from libcoot api
     */
    drawBiomolecule(fetchSymMatrix: boolean = true): void {
        if (this.symmetryOn) {
            console.warn("Biomolecule will not be drawn when Crystal symmetry is being shown.")
            return
        }
        if (fetchSymMatrix) {
            this.fetchBiomoleculeMatrix()
        }
        this.representations.forEach(representation => representation.drawSymmetry())
        this.representations.forEach(representation => {
            representation.buffers.forEach(buffer => {
                buffer.changeColourWithSymmetry = false
            })
        })
        if (this.adaptativeBondsEnabled) {
            this.adaptativeBondsRepresentation.drawSymmetry()
        }
    }

    /**
     * Draw symmetry mates for the current molecule
     * @param {boolean} [fetchSymMatrix=true] - Indicates whether a new symmetry matrix must be fetched from libcoot api
     */
    async drawSymmetry(fetchSymMatrix: boolean = true): Promise<void> {
        if (this.biomolOn) {
            console.warn("Crystal symmetry will not be drawn when biomolecule is being shown.")
            return
        }
        if (fetchSymMatrix) {
            await this.fetchSymmetryMatrix()
        }
        this.representations.forEach(representation => representation.drawSymmetry())
        if (this.adaptativeBondsEnabled) {
            this.adaptativeBondsRepresentation.drawSymmetry()
        }
    }

    /**
     * Set the background colour where the molecule is being drawn. Used to detect whether the background is dark and molecule needs to be rendered using lighter colours.
     * @param {number[]} backgroundColour - The rgba indicating the background colour
     */
    setBackgroundColour(backgroundColour: [number, number, number, number]) {
        this.isDarkBackground = isDarkBackground(...backgroundColour)
    }

    /**
     * Update the cached gemmi structure for this molecule
     */
    async updateGemmiStructure(coordString: string): Promise<void> {
        if (this.gemmiStructure && !this.gemmiStructure.isDeleted()) {
            this.gemmiStructure.delete()
        }
        this.cachedGemmiAtoms = null
        this.gemmiStructure = readGemmiStructure(coordString, this.name)
        window.CCP4Module.gemmi_setup_entities(this.gemmiStructure)
        // Only override if this is mmcif
        window.CCP4Module.gemmi_add_entity_types(this.gemmiStructure, this.coordsFormat === 'mmcif')
        this.parseSequences()
        this.updateLigands()
    }

    /**
     * Get the unit cell parameters for the molecule
     * @returns An object with the unit cell parameters
     */
    getUnitCellParams(): { a: number; b: number; c: number; alpha: number; beta: number; gamma: number; } {
        if (this.gemmiStructure === null) {
            return
        }

        const structure = this.gemmiStructure.clone()
        const unitCell = this.gemmiStructure.cell

        const unitCellParams = {
            a: unitCell.a,
            b: unitCell.b,
            c: unitCell.c,
            alpha: unitCell.alpha,
            beta: unitCell.beta,
            gamma: unitCell.gamma
        }

        structure.delete()
        unitCell.delete()

        return unitCellParams
    }

    /**
     * Get the CIDs for all residues wihtin a distance threshold of a set of residues
     * @param {string} selectionCid - The CID indicating the selection of residues used for the search
     * @param {number} radius - The radius used in the search
     * @param {number} minDist - Minimum distance for the serch
     * @param {number} maxDist - Maximum distance for the search
     * @returns {Promise<string[]>} List of CIDs with the residues found within the radius of search
     */
    async getNeighborResiduesCids(selectionCid: string, maxDist: number): Promise<string[]> {
        const response = await this.commandCentre.current.cootCommand({
            returnType: "status",
            command: 'get_neighbours_cid',
            commandArgs: [this.molNo, selectionCid, maxDist]
        }, false) as moorhen.WorkerResponse<string>

        const multiCidRanges: string[] = response.data.result.result.split('||')
        return multiCidRanges
    }

    /**
     * Parse the sequences in the molecule 
     */
    parseSequences(): void {
        if (this.gemmiStructure === null) {
            return
        }

        let result: moorhen.Sequence[] = []
        const sequenceInfoVec = window.CCP4Module.get_sequence_info(this.gemmiStructure, this.name)
        const sequenceInfoVecSize = sequenceInfoVec.size()
        for (let i = 0; i < sequenceInfoVecSize; i++) {
            const sequenceInfo = sequenceInfoVec.get(i)
            const sequenceInfoSeq = sequenceInfo.sequence
            let currentSequence: moorhen.ResidueInfo[] = []
            const sequenceInfoSize = sequenceInfoSeq.size()
            for (let i = 0; i < sequenceInfoSize; i++) {
                currentSequence.push(sequenceInfoSeq.get(i))
            }
            sequenceInfoSeq.delete()    
            result.push({
                name: sequenceInfo.name,
                chain: sequenceInfo.chain,
                type: sequenceInfo.type,
                sequence: currentSequence
            })
        }
        sequenceInfoVec.delete()

        this.sequences = result
        this.hasDNA = this.sequences.some(sequence => [3, 4, 5].includes(sequence.type))
    }

    /**
     * Check if the molecule instance consists of a ligand
     * @returns {boolean} True if the molecule is a ligand
     */
    checkIsLigand(): boolean {
        let isLigand = true
        this.isLigand = window.CCP4Module.structure_is_ligand(this.gemmiStructure)
        return isLigand
    }

    /**
     * Delete this molecule instance
     * @param {boolean} [popBackImol=false] - Indicates whether the imol for this molecule instance should be popped back (useful for ephemeral molecules)
     */
    async delete(popBackImol: boolean = false): Promise<moorhen.WorkerResponse> {
        this.hoverRepresentation?.deleteBuffers()
        this.unitCellRepresentation?.deleteBuffers()
        this.environmentRepresentation?.deleteBuffers()
        this.selectionRepresentation?.deleteBuffers()
        this.adaptativeBondsRepresentation?.deleteBuffers()
        this.representations.forEach(representation => representation.deleteBuffers())
        this.glRef.current.drawScene()
        const response = await this.commandCentre.current.cootCommand({
            returnType: "status",
            command: popBackImol ? 'pop_back' : 'close_molecule',
            commandArgs: popBackImol ? [ ] : [ this.molNo ]
        }, true) as moorhen.WorkerResponse<number>
        if (this.gemmiStructure && !this.gemmiStructure.isDeleted()) {
            this.gemmiStructure.delete()
        }
        return response
    }

    /**
     * Transfer metadata stored in this molecule instance to other molecule
     * @param {morhen.Molecule} otherMolecule - The molecule where the metadata will be transferred
     * @param {boolean} [transferDicts=true] - Indicates whether ligand dictionaries should also be transferred
     */
    async transferMetaData(otherMolecule: moorhen.Molecule, transferDicts: boolean = true) {
        otherMolecule.defaultBondOptions = this.defaultBondOptions
        otherMolecule.defaultM2tParams = this.defaultM2tParams
        otherMolecule.environmentRepresentation = this.environmentRepresentation
        otherMolecule.coordsFormat = this.coordsFormat
        otherMolecule.isLigand = this.isLigand
        otherMolecule.hasGlycans = this.hasGlycans
        otherMolecule.hasDNA = this.hasDNA
        otherMolecule.isDarkBackground = this.isDarkBackground
        if (transferDicts) {
            await this.transferLigandDicts(otherMolecule)
        }
    }

    /**
     * Transfer ligand dictionaries stored in this molecule instance to other molecule
     * @param {morhen.Molecule} toMolecule - The molecule where the metadata will be transferred
     * @param {boolean} [override=false] - Override ligand dictionaries already stored under the same ligand name in the other molecule instance 
     */
    async transferLigandDicts(toMolecule: moorhen.Molecule, override: boolean = false) {
        await Promise.all(Object.keys(this.ligandDicts).map(key => {
            if (!override && Object.hasOwn(toMolecule.ligandDicts, key)) {
                return
            } else {
                toMolecule.addDict(this.ligandDicts[key])
            }
        }))
    }

    /**
     * Copy molecule into a new instance
     * @returns {moorhen.Molecule} New molecule instance
     */
    async copyMolecule(doRedraw: boolean = true): Promise<moorhen.Molecule> {
        let coordString = await this.getAtoms()
        let newMolecule = new MoorhenMolecule(this.commandCentre, this.glRef, this.store, this.monomerLibraryPath)
        newMolecule.name = `${this.name}-placeholder`

        let response = await this.commandCentre.current.cootCommand({
            returnType: "status",
            command: 'read_coords_string',
            commandArgs: [coordString, newMolecule.name]
        }, true) as moorhen.WorkerResponse<libcootApi.PairType<number, moorhen.coorFormats>>

        newMolecule.molNo = response.data.result.result.first

        await this.transferMetaData(newMolecule)
        await newMolecule.fetchDefaultColourRules()
        if (doRedraw) {
            await newMolecule.fetchIfDirtyAndDraw('CBs')
        }
        return newMolecule
    }

    /**
     * Copy a fragment of the current model into a new molecule using a selection CID
     * @param {string} cid - The CID selection indicating the residues that will be copied into the new fragment
     * @param {boolean} [doRecentre=true] - Indicates whether the view should re-centre on the new copied fragment
     * @param {boolean} [style="CBs"] - Indicates the style used to draw the copied fragment (only takes effect if doRecentre=true)
     * @returns {Promise<moorhen.Molecule>}  New molecule instance
     */
    async copyFragmentUsingCid(cid: string, doRecentre: boolean = true, style: moorhen.RepresentationStyles = 'CBs'): Promise<moorhen.Molecule> {
        const response = await this.commandCentre.current.cootCommand({
            returnType: "status",
            command: "copy_fragment_using_cid",
            commandArgs: [this.molNo, cid],
        }, true) as moorhen.WorkerResponse<number>
        const newMolecule = new MoorhenMolecule(this.commandCentre, this.glRef, this.store, this.monomerLibraryPath)
        newMolecule.name = `${this.name} fragment`
        newMolecule.molNo = response.data.result.result
        await this.transferMetaData(newMolecule)
        await newMolecule.fetchDefaultColourRules()
        if (doRecentre) {
            newMolecule.setAtomsDirty(true)
            await newMolecule.fetchIfDirtyAndDraw(style)
            await newMolecule.centreOn('/*/*/*/*', true, true)
        }
        return newMolecule
    }

    /**
     * Copy a fragment of the current model into a new molecule for refinement
     * @param {string[]} cid - The CID selection indicating the residues that will be copied into the new fragment
     * @param {moorhen.Map} refinementMap - The map instance used in the refinement
     * @param {boolean} redraw - Indicate if the molecules should be redrawn
     * @param {boolean} redrawFragmentFirst - Indicate if the fragment should be redrawn first
     * @returns {moorhen.Molecule} A new molecule instance that can be used for refinement
     */
    async copyFragmentForRefinement(cid: string[], refinementMap: moorhen.Map, redraw: boolean = true, redrawFragmentFirst: boolean = true): Promise<moorhen.Molecule> {
        const newMolecule = new MoorhenMolecule(this.commandCentre, this.glRef, this.store, this.monomerLibraryPath)
        const copyResult = await this.commandCentre.current.cootCommand({
            returnType: 'int',
            command: 'copy_fragment_for_refinement_using_cid',
            commandArgs: [this.molNo, cid.join('||')]
        }, false)
        
        if (copyResult.data.result.result !== -1) {
            newMolecule.molNo = copyResult.data.result.result
            await this.commandCentre.current.cootCommand({
                returnType: 'status',
                command: 'init_refinement_of_molecule_as_fragment_based_on_reference',
                commandArgs: [newMolecule.molNo, this.molNo, refinementMap.molNo]
            }, false)
            await this.transferLigandDicts(newMolecule)
            newMolecule.setAtomsDirty(true)
            if (redraw) {
                const drawMissingLoops = this.store.getState().sceneSettings.drawMissingLoops
                if (drawMissingLoops) {
                    await this.commandCentre.current.cootCommand({
                        command: "set_draw_missing_residue_loops",
                        returnType:'status',
                        commandArgs: [ false ],
                    }, false)
                }
                await Promise.all(cid.map(cid => {
                    return this.hideCid(cid, false)
                }))
                if (redrawFragmentFirst) { 
                    await newMolecule.fetchIfDirtyAndDraw('CBs')
                    await this.redraw()
                } else {
                    await this.redraw()
                    await newMolecule.fetchIfDirtyAndDraw('CBs')
                }
            }
        } else {
            console.warn(`Unable to copy fragment for refinement using cid ${cid} for molecule ${this.molNo}`)
        }
        
        return newMolecule    
    }

    /**
     * Merge a fragment that was used for refinement into the current molecule
     * @param {string[]} cid - The CID selection used to create the fragment
     * @param {moorhen.Molecule} fragmentMolecule - The fragment molecule
     * @param {boolean} [acceptTransform=true] - Indicates whether the transformation should be accepted
     * @param {boolean} [refineAfterMerge=false] - Indicates whether another cycle of refinement should be run after merging the fragment
     */
    async mergeFragmentFromRefinement(cid: string, fragmentMolecule: moorhen.Molecule, acceptTransform: boolean = true, refineAfterMerge: boolean = false) {
        const drawMissingLoops = this.store.getState().sceneSettings.drawMissingLoops
        if (drawMissingLoops) {
            await this.commandCentre.current.cootCommand({
                command: "set_draw_missing_residue_loops",
                returnType:'status',
                commandArgs: [ true ],
            }, false)
        }

        await this.commandCentre.current.cootCommand({
            returnType: 'status',
            command: 'clear_refinement',
            commandArgs: [this.molNo],
        }, false)
        
        if (acceptTransform) {
            await this.commandCentre.current.cootCommand({
                returnType: 'status',
                command: 'replace_fragment',
                commandArgs: [this.molNo, fragmentMolecule.molNo, cid],
                changesMolecules: [this.molNo]
            }, !refineAfterMerge)
            if (refineAfterMerge) {
                await this.refineResiduesUsingAtomCid(cid, 'LITERAL', 4000, false)
            }
            this.setAtomsDirty(true)
        }

        await this.unhideAll()
        await fragmentMolecule.delete(true)
    }

    /**
     * Load a new molecule from a file URL
     * @param {string} url - The url to the path with the data for the new molecule
     * @param {string} molName - The new molecule name
     * @param {object} [options] - Options passed to fetch API
     * @returns {Promise<moorhen.Molecule>} The new molecule
     */
    async loadToCootFromURL(url: RequestInfo | URL, molName: string, options?: RequestInit): Promise<moorhen.Molecule> {
        const response = await fetch(url, options)
        try {
            if (response.ok) {
                const coordData = await response.text()
                return this.loadToCootFromString(coordData, molName)
            } else {
                return Promise.reject(`Error fetching data from url ${url}`)
            }

        } catch (err) {
            return Promise.reject(err)
        }
    }

    /**
     * Load a new molecule from the contents of a file
     * @param {File} source - The input file
     * @returns {Promise<moorhen.Molecule>} The new molecule
     */
    async loadToCootFromFile(source: File): Promise<moorhen.Molecule> {
        try {
            const coordData = await readTextFile(source);
            return await this.loadToCootFromString(coordData, source.name);
        } catch (err) {
            return await Promise.reject(err);
        }
    }

    /**
     * Guess the coordinate format from the file contents
     * @param {string} coordDataString - The file contents
     * @returns {string} - The file format
     */
    static guessCoordFormat(coordDataString: string): moorhen.coorFormats {
        let result: moorhen.coorFormats = 'pdb'
        try {
            const format = window.CCP4Module.guess_coord_data_format(coordDataString)
            if (format === 0) {
                // result = 'unknown'
            } else if (format === 1) {
                // result = 'detect'
            } else if (format === 2) {
                result = 'pdb'
            } else if (format === 3) {
                result = 'mmcif'
            } else if (format === 4) {
                // result = 'mmjson'
            } else if (format === 5) {
                // result = 'chemComp'
            }
        } catch (err) {
            console.warn(err)
            console.log('Unable to guess format of coords using gemmi... Defaulting to PDB format')
        }
        return result
    }

    /**
     * Load a new molecule from a string
     * @param {string} coordData - The molecule data
     * @param {string} name - The new molecule name
     * @returns {Promise<moorhen.Molecule>} The new molecule
     */
    async loadToCootFromString(coordData: ArrayBuffer | string, name: string): Promise<moorhen.Molecule> {
        const pdbRegex = /.pdb$/;
        const entRegex = /.ent$/;
        const cifRegex = /.cif$/;
        const mmcifRegex = /.mmcif$/;

        if (this.gemmiStructure && !this.gemmiStructure.isDeleted()) {
            this.gemmiStructure.delete()
        }

        this.name = name.replace(pdbRegex, "").replace(entRegex, "").replace(cifRegex, "").replace(mmcifRegex, "");

        try {
            this.updateGemmiStructure(coordData as string)
            this.atomsDirty = false
            const response = await this.commandCentre.current.cootCommand({
                returnType: "status",
                command: 'read_coords_string',
                commandArgs: [coordData, this.name],
            }, true) as moorhen.WorkerResponse<libcootApi.PairType<number, moorhen.coorFormats>>
            this.molNo = response.data.result.result.first
            this.coordsFormat = response.data.result.result.second
            await Promise.all([
                this.getNumberOfAtoms(),
                this.loadMissingMonomers(),
                this.checkHasGlycans(),
                this.fetchDefaultColourRules(),
                this.getMoleculeDiameter()
            ])
            return this
        } catch (err) {
            console.log('Error in loadToCootFromString', err)
        }
    }

    /**
     * Load a the missing dictionary for a monomer. First attempts to load it from the monomer library, if it fails it will load from the EBI.
     * @param {string} newTlc - Three letter code for the monomer
     * @param {number} attachToMolecule - Molecule number for which the dicitonary will be associated
     */
    async loadMissingMonomer(newTlc: string, attachToMolecule: number): Promise<string> {
        let response: Response = await fetch(`${this.monomerLibraryPath}/${newTlc.toLowerCase()[0]}/${newTlc.toUpperCase()}.cif`)
        const fileContent = await response.text()
        let dictContent: string

        if (!fileContent.includes('data_')) {
            console.log(`Unable to fetch ligand dictionary ${newTlc} from local monomer library...`)
            try {
                const url = `https://raw.githubusercontent.com/MonomerLibrary/monomers/master/${newTlc.toLowerCase()[0]}/${newTlc.toUpperCase()}.cif`
                response = await fetch(url)
                if (response.ok) {
                    dictContent = await response.text()
                } else {
                    console.log(`Unable to fetch ligand dictionary ${newTlc} from remote monomer library...`)
                    const url = `https://www.ebi.ac.uk/pdbe/static/files/pdbechem_v2/${newTlc.toUpperCase()}.cif`
                    response = await fetch(url)
                    if (response.ok) {
                        dictContent = await response.text()
                    } else {
                        console.log(`Unable to fetch ligand dictionary ${newTlc} from PDBe chem...`)
                    }
                }
            } catch (err) {
                console.log(err)
                console.log(`Unable to fetch ligand dictionary ${newTlc}`)
            }
        } else {
            dictContent = fileContent
        }

        if (dictContent) {
            await this.commandCentre.current.cootCommand({
                returnType: "status",
                command: 'read_dictionary_string',
                commandArgs: [dictContent, attachToMolecule],
            }, false)
        }

        return dictContent
    }

    /**
     * Attempt to load dictionaries for all missing monomers present in the molecule
     * @returns {Promise<moorhen.Molecule>} This molecule instance
     */
    async loadMissingMonomers(): Promise<void> {
        const response = await this.commandCentre.current.cootCommand({
            returnType: "string_array",
            command: 'get_residue_names_with_no_dictionary',
            commandArgs: [this.molNo],
        }, false) as moorhen.WorkerResponse<string[]>

        if (response.data.result.status === 'Completed') {
            try {
                const ligandDicts = await Promise.all(
                    response.data.result.result.map(newTlc => {
                        return this.loadMissingMonomer(newTlc, -999999)
                    })
                )
                ligandDicts.forEach(ligandDict => this.cacheLigandDict(ligandDict))
            } catch (err) {
                console.log(err)
                console.warn('Error in loadMissingMonomers...')
            }
        } else {
            console.log('Error in loadMissingMonomers...');
        }
    }

    /**
     * Set the cached molecule atoms as "dirty". This means new bond representations need to be fetched 
     * next time the molecule is redrawn.
     * @param {boolean} state - Indicate whether the current atom representation is dirty
     */
    setAtomsDirty(state: boolean): void {
        this.atomsDirty = state
    }

    /**
     * Get a string with the PDB file contents of the molecule in its current state
     * @param {string} [format='pdb'] - File format will match the one of the original file unless specified here
     * @returns {string}  A string representation file contents
     */
    async getAtoms(format?: moorhen.coorFormats): Promise<string> {
        let cootCommand = 'molecule_to_PDB_string'
        if (format) {
            cootCommand = format === 'mmcif' ? 'molecule_to_mmCIF_string' : 'molecule_to_PDB_string'
        } else if (this.coordsFormat) {
            cootCommand = this.coordsFormat === 'mmcif' ? 'molecule_to_mmCIF_string' : 'molecule_to_PDB_string'
        }
        const response = await this.commandCentre.current.cootCommand({
            returnType: "string",
            command: cootCommand,
            commandArgs: [this.molNo],
        }, false) as moorhen.WorkerResponse<string>
        return response.data.result.result
    }

    /**
     * Download the PDB file contents of the molecule in its current state
     * @param {string} [format='pdb'] - File format will match the one of the original file unless specified here
     */
    async downloadAtoms(format?: moorhen.coorFormats, fileName?: string) {
        const coordsString = await this.getAtoms(format)
        doDownload([coordsString], `${fileName ?? this.name}.${format ? format : this.coordsFormat ? this.coordsFormat : 'pdb'}`)
    }

    /**
     * Check if the current molecule has glycans
     * @returns {Promise<boolean>} - True if the current molecule has glycans
     */
    async checkHasGlycans(): Promise<boolean> {
        this.cachedPrivateerValidation = null
        const result = await this.commandCentre.current.cootCommand({
            returnType: 'boolean',
            command: 'model_has_glycans',
            commandArgs: [this.molNo],
        }, false) as moorhen.WorkerResponse<boolean>
        this.hasGlycans = result.data.result.result
        return this.hasGlycans
    }

    /**
     * Get the diameter of this molecule
     * @returns {number} The molecule diameter
     */
    async getMoleculeDiameter(): Promise<number> {
        const diameter = await this.commandCentre.current.cootCommand({
            returnType: 'int',
            command: 'get_molecule_diameter',
            commandArgs: [ this.molNo ],
        }, false) as moorhen.WorkerResponse<number>
        
        this.moleculeDiameter = diameter.data.result.result
        
        return diameter.data.result.result
    }

    /**
     * Update the cached atoms with the latest information from the libcoot api
     */
    async updateAtoms() {
        if (this.gemmiStructure && !this.gemmiStructure.isDeleted()) {
            this.gemmiStructure.delete()
        }
        const [_hasGlycans, coordString, _diameter] = await Promise.all([
            this.checkHasGlycans(),
            this.getAtoms(),
            this.getMoleculeDiameter()
        ])
        try {
            this.updateGemmiStructure(coordString)
        }
        catch (err) {
            console.log(err)
            console.warn('Issue parsing coordinates into Gemmi structure', coordString)
        }
        this.atomsDirty = false
}

    /**
     * Draw the molecule with a particular style. If the molecule atoms are marked as "dirty" then fetch new atoms.
     * @param {string} style - The style that will be drawn
     */
    async fetchIfDirtyAndDraw(style: moorhen.RepresentationStyles): Promise<void> {
        if (this.atomsDirty) {
            await this.updateAtoms()
        }

        const cid = "/*/*/*/*"
        const representation = this.representations.find(item => item.style === style && item.cid === cid)
        if (representation) {
            await this.redrawRepresentation(representation.uniqueId)
        } else {
            await this.addRepresentation(style, cid)
        }
    }

    /**
     * Centre the view and align it with the axis of a particular residue
     * @param {string} selectionCid - CID selection for the residue to centre the view on
     * @param {boolean} [alignWithCB=false] - Indicates whether to align with the CB atom for better view of the side chain (when present in the residue)
     * @param {number} [zoomLevel=0.3] - Indicates the zoom level to use
     */
    async centreAndAlignViewOn(selectionCid: string, alignWithCB: boolean = false, zoomLevel: number = 0.3): Promise<void> {

        if (this.atomsDirty) {
            await this.updateAtoms()
        }

        let selectionAtomsAlign: moorhen.AtomInfo[] = []
        let selectionAtomsCentre: moorhen.AtomInfo[] = []
        if (selectionCid) {
            selectionAtomsAlign = await this.gemmiAtomsForCid(selectionCid + "*")
            selectionAtomsCentre = await this.gemmiAtomsForCid(selectionCid + "CA")

        } else {
            selectionAtomsAlign = await this.gemmiAtomsForCid('/*/*/*/*')
            selectionAtomsCentre = await this.gemmiAtomsForCid('/*/*/*/*')
        }

        let CA: number[]
        let CB: number[]
        let C: number[]
        let O: number[]

        selectionAtomsAlign.forEach(atom => {
            if (atom.name === "CA") {
                CA = [atom.x, atom.y, atom.z]
            } else if (atom.name === "CB") {
                CB = [atom.x, atom.y, atom.z]
            } else if (atom.name === "C") {
                C = [atom.x, atom.y, atom.z]
            } else if (atom.name === "O") {
                O = [atom.x, atom.y, atom.z]
            }
        })

        let newQuat = null

        if (C && CA && O) {
            let right = vec3.create()
            vec3.set(right, C[0] - CA[0], C[1] - CA[1], C[2] - CA[2])
            let rightNorm = vec3.create()
            vec3.normalize(rightNorm, right);

            let upInit = vec3.create()
            if (CB && alignWithCB) {
                vec3.set(upInit, CB[0] - C[0], CB[1] - C[1], CB[2] - C[2])
            } else {
                vec3.set(upInit, O[0] - C[0], O[1] - C[1], O[2] - C[2])
            }
            let upInitNorm = vec3.create()
            vec3.normalize(upInitNorm, upInit);

            let forward = vec3.create()
            vec3.cross(forward, right, upInitNorm)
            let forwardNorm = vec3.create()
            vec3.normalize(forwardNorm, forward);

            let up = vec3.create()
            vec3.cross(up, forwardNorm, rightNorm)
            let upNorm = vec3.create()
            vec3.normalize(upNorm, up);

            newQuat = quat4.create()
            let mat = mat3.create()
            const [right_x, right_y, right_z] = [rightNorm[0], rightNorm[1], rightNorm[2]]
            const [up_x, up_y, up_z] = [upNorm[0], upNorm[1], upNorm[2]]
            const [formaward_x, formaward_y, formaward_z] = [forwardNorm[0], forwardNorm[1], forwardNorm[2]]
            mat3.set(mat, right_x, right_y, right_z, up_x, up_y, up_z, formaward_x, formaward_y, formaward_z)
            quat4.fromMat3(newQuat, mat)
        }

        let selectionCentre = centreOnGemmiAtoms(selectionAtomsCentre)
        if (newQuat) {
            this.glRef.current.setOriginOrientationAndZoomAnimated(selectionCentre, newQuat, zoomLevel);
        } else {
            await this.centreOn(selectionCid, true, true)
        }
    }

    /**
     * Centre the view on a particular residue
     * @param {string} selectionCid - CID selection for the residue to centre the view on
     * @param {boolean} [animate=true] - Indicates whether the change will be animated
     */
    async centreOn(selectionCid: string = '/*/*/*/*', animate: boolean = true, setZoom: boolean = true): Promise<void> {
        if (this.atomsDirty) {
            await this.updateAtoms()
        }

        const selectionAtoms = await this.gemmiAtomsForCid(selectionCid)

        if (selectionAtoms.length === 0) {
            console.log('Unable to select any atoms, skip centering...')
            return
        }

        let zoomLevel: number
        if (selectionCid === '/*/*/*/*' || selectionCid === '//') {
            if (this.moleculeDiameter === null) {
                this.moleculeDiameter = await this.getMoleculeDiameter()
            }
            zoomLevel = this.moleculeDiameter / 40
        } else {
            zoomLevel = 0.4
        }

        let selectionCentre = centreOnGemmiAtoms(selectionAtoms)
        if (animate && setZoom) {
            this.glRef.current.setOriginAndZoomAnimated(selectionCentre, zoomLevel)
        } else if (animate) {
            this.glRef.current.setOriginAnimated(selectionCentre)
        } else if (setZoom) {
            this.glRef.current.setOrigin(selectionCentre)
            this.glRef.current.setZoom(zoomLevel)
        } else {
            this.glRef.current.setOrigin(selectionCentre)
        }
    }

    /**
     * Draw molecule from a given mesh
     * @param {string} style - Indicate the style to be drawn
     * @param {any[]} meshObjects - The mesh obects that will be drawn
     * @param {string} [cid] - The new buffer CID selection
     */
    async drawWithStyleFromMesh(style: moorhen.RepresentationStyles, meshObjects: any[], cid: string = "/*/*/*/*", fetchAtomBuffers: boolean = false): Promise<void> {
        let representation = this.representations.find(item => item.style === style && item.cid === cid)
        if (!representation) {
            representation = new MoorhenMoleculeRepresentation(style, cid, this.commandCentre, this.glRef)
            representation.setParentMolecule(this)
            this.representations.push(representation)
        }

        representation.deleteBuffers()
        representation.buildBuffers(meshObjects)
        if (fetchAtomBuffers) {
            let bufferAtoms = await this.gemmiAtomsForCid(cid)
            if(bufferAtoms.length > 0) {
                representation.setAtomBuffers(bufferAtoms)
            }    
        }
        representation.show()
    }

    addColourRule(ruleType: string, cid: string, color: string, args: (string | number)[], isMultiColourRule: boolean = false, applyColourToNonCarbonAtoms: boolean = false, label?: string) {
        const newColourRule = new MoorhenColourRule(ruleType, cid, color, this.commandCentre, isMultiColourRule, applyColourToNonCarbonAtoms)
        newColourRule.setParentMolecule(this)
        newColourRule.setArgs(args)
        if (label) {
            newColourRule.setLabel(label)
        }
        this.defaultColourRules.push(newColourRule)
    }

    /**
     * Add a representation to the molecule
     * @param {string} style - The style of the new representation
     * @param {string} cid - The CID selection for the residues included in the new representation
     * @param {boolean} [isCustom=false] - Indicates if the representation is considered "custom"
     * @param {moorhen.ColourRule[]} [colourRules=undefined] - A list of colour rules that will be applied to the new representation
     * @param {moorhen.cootBondOptions} [bondOptions=undefined] - An object that describes bond width, atom/bond ratio and other bond settings.
     * @param {moorhen.m2tParameters} [m2tParams=undefined] - An object that describes ribbon width, nucleotide style and other ribbon settings.
     */
    async addRepresentation(style: moorhen.RepresentationStyles, cid: string = '/*/*/*/*', isCustom: boolean = false, colourRules?: moorhen.ColourRule[], bondOptions?: moorhen.cootBondOptions, m2tParams?: moorhen.m2tParameters, residueEnvOptions?: moorhen.residueEnvironmentOptions) {
        if (!this.defaultColourRules) {
            await this.fetchDefaultColourRules()
        }
        const representation = new MoorhenMoleculeRepresentation(style, cid, this.commandCentre, this.glRef)
        representation.isCustom = isCustom
        representation.setParentMolecule(this)
        representation.setColourRules(colourRules)
        representation.setBondOptions(bondOptions)
        representation.setM2tParams(m2tParams)
        representation.setResidueEnvOptions(residueEnvOptions)
        await representation.draw()
        this.representations.push(representation)
        await this.drawSymmetry(false)
        this.drawBiomolecule(false)
        return representation
    }

    /**
     * Redraw a molecule representation
     * @param {string} id - Unique identifier for the representation of interest
     */
    async redrawRepresentation(id: string) {
        const representation = this.representations.find(representation => representation.uniqueId === id)
        await representation.redraw()
        await this.drawSymmetry(false)
    }

    /**
     * Show the representation for the molecule 
     * @param {string} style - The representation style to show
     * @param {string} [cid=undefined] - The CID selection for the representation
     */
    show(style: moorhen.RepresentationStyles, cid?: string): void {
        let representation: moorhen.MoleculeRepresentation
        try {
            if (style === 'ligands') {
                representation = this.representations.find(item => item.style === style)
            } else {
                if (!cid) cid = '/*/*/*/*'
                representation = this.representations.find(item => item.style === style && item.cid === cid)
            }
            if (representation) {
                representation.show()
            } else {
                this.addRepresentation(style, cid)
            }
        } catch (err) {
            console.log(err)
        }
    }

    /**
     * Hide a type of representation for the molecule 
     * @param {string} style - The representation style to hide
     * @param {string} [cid=undefined] - The CID selection for the representation
     */
    hide(style: moorhen.RepresentationStyles, cid?: string) {
        let representation: moorhen.MoleculeRepresentation
        try {
            if (style === 'ligands') {
                representation = this.representations.find(item => item.style === style)
            } else {
                if (!cid) cid = '/*/*/*/*'
                representation = this.representations.find(item => item.style === style && item.cid === cid)
            }
            if (representation) {
                representation.hide()
            }
        } catch (err) {
            console.log(err)
        }
    }

    /**
     * Clears the representation buffers for a particular style
     * @param {string} style - The style to clear
     */
    clearBuffersOfStyle(style: string) {
        if (style === 'hover') {
            this.hoverRepresentation?.deleteBuffers()
        } else if (style === 'unitCell') {
            this.unitCellRepresentation?.deleteBuffers()
        } else if (style === 'environment') {
            this.environmentRepresentation?.deleteBuffers()
        } else if (style === 'residueSelection') {
            this.selectionRepresentation?.deleteBuffers()
        } else if (style === 'adaptativeBonds') {
            this.adaptativeBondsRepresentation?.deleteBuffers()
        } else {
            this.representations.forEach(representation => representation.style === style ? representation.deleteBuffers() : null)
            this.representations = this.representations.filter(representation => representation.style !== style)
        }
    }

    /**
     * Remove a representation for this molecule instance
     * @param {string} representationId - The unique identifier for the representation
     */
    removeRepresentation(representationId: string) {
        this.representations.forEach(representation => representation.uniqueId === representationId ? representation.deleteBuffers() : null)
        this.representations = this.representations.filter(representation => representation.uniqueId !== representationId)
    }

    /**
     * Check whether a particular buffer is included within the representation buffer for this molecule
     * @param bufferIn - The buffer with the ID to search for
     * @returns {boolean} True if the buffer is included in this molecule
     */
    buffersInclude(bufferIn: { id: string; }): boolean {
        try {
            for (let representation of this.representations) {
                if (Array.isArray(representation.buffers)) {
                    for (let buffer of representation.buffers) {
                        if (bufferIn.id === buffer.id) {
                            return true
                        }
                    }
                }   
            }
            if (this.adaptativeBondsEnabled) {
                if (Array.isArray(this.adaptativeBondsRepresentation.buffers)) {
                    for (let buffer of this.adaptativeBondsRepresentation.buffers) {
                        if (bufferIn.id === buffer.id) {
                            return true
                        }
                    }
                }
            }
        }
        catch (e) {
            console.log(e);
            return false
        }
        return false
    }

    /**
     * Draw the unit cell of this molecule
     */
    async drawUnitCell() {
        if (this.unitCellRepresentation && this.unitCellRepresentation.buffers?.length > 0) {
            this.unitCellRepresentation.show()
        } else {
            await this.unitCellRepresentation.draw()
        }
    }

    /**
     * Draw a hover effect over a selected residue
     * @param {string} selectionString - The CID selection for the residue that will be highlighted
     */
    async drawHover(selectionString: string): Promise<void> {
        if (typeof selectionString === 'string') {
            this.hoverRepresentation.cid = selectionString
            await this.hoverRepresentation.redraw()
            this.hoverRepresentation.drawSymmetry()
            this.hoverRepresentation.buffers[0].changeColourWithSymmetry = false
        }
    }

    /**
     * Highlight residues in a selected CID range
     * @param {string} selectionString - The CID selection for the residues that will be highlighted
     */
    async drawResidueSelection(selectionString: string): Promise<void> {
        if (typeof selectionString === 'string') {
            this.selectionRepresentation.cid = selectionString
            await this.selectionRepresentation.redraw()
        }
    }

    /**
     * Get the active atom for this molecule
     * @returns {string} The active atom CID
     */
    async getActiveAtom(): Promise<string> {
        const [_molecule, activeAtomCid] = await getCentreAtom([this], this.commandCentre, this.glRef)
        return activeAtomCid
    }

    /**
     * Value setter for MoorhenMolecule.adaptativeBondsEnabled
     * @param {boolean} newValue - The new value
     */
    async setDrawAdaptativeBonds(newValue: boolean) {
        this.adaptativeBondsEnabled = newValue
        if (newValue) {
            const activeAtomCid = await this.getActiveAtom()
            if (activeAtomCid === this.adaptativeBondsRepresentation.cid) {
                this.adaptativeBondsRepresentation.show()
            }
            await this.redrawAdaptativeBonds(activeAtomCid)
        } else {
            this.adaptativeBondsRepresentation.hide()
        }
    }

    /**
     * Draw bonds for a given glRef origin
     * @param {string} selectionString - The CID selection for the residues that will be highlighted
     */
    async redrawAdaptativeBonds(selectionCid?: string): Promise<void> {
        if (!this.adaptativeBondsEnabled) {
            this.adaptativeBondsRepresentation?.deleteBuffers()
            this.adaptativeBondsEnabled = false
            return
        } else if (!selectionCid && !this.adaptativeBondsRepresentation.cid) {
            console.warn(`No selection string provided when drawing origin bonds`)
            this.adaptativeBondsEnabled = false
            return
        } else if (selectionCid) {
            this.adaptativeBondsRepresentation.cid = selectionCid
        } 

        this.adaptativeBondsEnabled = true
        await this.adaptativeBondsRepresentation.redraw()
    }

    /**
     * Draw enviroment distances for a given residue
     * @param {string} selectionCid - The CID selection to draw the environment
     */
    async drawEnvironment(selectionCid: string): Promise<void> {
        if (typeof selectionCid === 'string') {
            this.environmentRepresentation.cid = selectionCid
            await this.environmentRepresentation.redraw()
        }
    }

    /**
     * Redraw the molecule representations
     */
    async redraw(): Promise<void> {
        if (this.atomsDirty) {
            try {
                await this.updateAtoms()
            } catch (err) {
                console.log(err)
                return
            }
        }

        for (const representation of this.representations) {
            if (representation.visible) {
                await this.redrawRepresentation(representation.uniqueId)
            } else {
                representation.deleteBuffers()
            }
        }

        await this.redrawAdaptativeBonds()
        await this.drawSymmetry(false)
    }

    /**
     * Move residues by applying a series of cached matrix transformations
     * @param {string} selectionCid - The CID selection for the set of residues that will be moved
     * @returns {moorhen.AtomInfo[][]} New atom information for the moved residues
     */
    transformedCachedAtomsAsMovedAtoms(selectionCid: string = '/*/*/*/*'): moorhen.AtomInfo[][] {
        let movedResidues: moorhen.AtomInfo[][] = [];

        const selection = new window.CCP4Module.Selection(selectionCid)
        const models = this.gemmiStructure.models
        const modelsSize = this.gemmiStructure.models.size()
        for (let modelIndex = 0; modelIndex < modelsSize; modelIndex++) {
            const model = models.get(modelIndex)
            if (!selection.matches_model(model)) {
                model.delete()
                continue
            }
            const chains = model.chains
            const chainsSize = chains.size()
            for (let chainIndex = 0; chainIndex < chainsSize; chainIndex++) {
                const chain = chains.get(chainIndex)
                if (!selection.matches_chain(chain)) {
                    chain.delete()
                    continue
                }
                const residues = chain.residues
                const residuesSize = residues.size()
                for (let residueIndex = 0; residueIndex < residuesSize; residueIndex++) {
                    const residue = residues.get(residueIndex)
                    if (!selection.matches_residue(residue)) {
                        residue.delete()
                        continue
                    }
                    const residueSeqId = residue.seqid
                    let movedAtoms: moorhen.AtomInfo[] = []
                    const atoms = residue.atoms
                    const atomsSize = atoms.size()
                    for (let atomIndex = 0; atomIndex < atomsSize; atomIndex++) {
                        const atom = atoms.get(atomIndex)
                        if (!selection.matches_atom(atom)) {
                            atom.delete()
                            continue
                        }
                        const atomElement = atom.element
                        const gemmiAtomPos = atom.pos
                        const atomAltLoc = atom.altloc
                        const atomSerial = atom.serial
                        const atomHasAltLoc = atom.has_altloc()
                        const atomElementString: string = window.CCP4Module.getElementNameAsString(atomElement)
                        const atomName = atomElementString.length === 2 ? (atom.name).padEnd(4, " ") : (" " + atom.name).padEnd(4, " ")
                        const diff = this.displayObjectsTransformation.centre
                        let x = gemmiAtomPos.x + this.glRef.current.origin[0] - diff[0]
                        let y = gemmiAtomPos.y + this.glRef.current.origin[1] - diff[1]
                        let z = gemmiAtomPos.z + this.glRef.current.origin[2] - diff[2]
                        const origin = this.displayObjectsTransformation.origin
                        const quat = this.displayObjectsTransformation.quat
                        if (quat) {
                            const theMatrix = quatToMat4(quat)
                            theMatrix[12] = origin[0]
                            theMatrix[13] = origin[1]
                            theMatrix[14] = origin[2]
                            // And then transform ...
                            const atomPos = vec3.create()
                            const transPos = vec3.create()
                            vec3.set(atomPos, x, y, z)
                            vec3.transformMat4(transPos, atomPos, theMatrix);
                            movedAtoms.push({
                                mol_name: model.name,
                                chain_id: chain.name,
                                res_no: residueSeqId.str(),
                                res_name: residue.name,
                                name: atomName,
                                element: atomElementString,
                                tempFactor: atom.b_iso,
                                charge: atom.charge,
                                x: transPos[0] - this.glRef.current.origin[0] + diff[0],
                                y: transPos[1] - this.glRef.current.origin[1] + diff[1],
                                z: transPos[2] - this.glRef.current.origin[2] + diff[2],
                                serial: atomSerial,
                                has_altloc: atomHasAltLoc,
                                alt_loc: atomHasAltLoc ? String.fromCharCode(atomAltLoc) : '',
                            })
                        }
                        atom.delete()
                        atomElement.delete()
                        gemmiAtomPos.delete()
                    }
                    movedResidues.push(movedAtoms)
                    residue.delete()
                    residueSeqId.delete()
                    atoms.delete()
                }
                chain.delete()
                residues.delete()
            }
            model.delete()
            chains.delete()
        }
        models.delete()
        selection.delete()

        return movedResidues
    }

    /**
     * Update the molecule with a set of moved residues
     * @param {moorhen.AtomInfo[][]} movedResidues - Set of moved residues
     */
    async updateWithMovedAtoms(movedResidues: moorhen.AtomInfo[][]): Promise<void> {
        await this.commandCentre.current.cootCommand({
            returnType: "status",
            command: "shim_new_positions_for_residue_atoms",
            commandArgs: [this.molNo, movedResidues],
            changesMolecules: [this.molNo]
        }, true)
        this.displayObjectsTransformation.origin = [0, 0, 0]
        this.displayObjectsTransformation.quat = null
        this.setAtomsDirty(true)
        await this.redraw()
    }

    /**
     * Apply cached transformation matrix to molecule
     */
    applyTransform() {
        const movedResidues = this.transformedCachedAtomsAsMovedAtoms()
        return this.updateWithMovedAtoms(movedResidues)
    }

    /**
     * Get a list with the names of the chains in the current model
     * @returns {string[]} - A list of chain names in the current structure
     */
    getChainNames(): string[] {
        let result: string[] = []
        const models = this.gemmiStructure.models
        const modelsSize = models.size()
        for (let modelIndex = 0; modelIndex < modelsSize; modelIndex++) {
            const model = models.get(modelIndex)
            const chains = model.chains
            const chainsSize = chains.size()
            for (let chainIndex = 0; chainIndex < chainsSize; chainIndex++) {
                const chain = chains.get(chainIndex)
                result.push(chain.name)
                chain.delete()
            }
            model.delete()
            chains.delete()
        }
        models.delete()
        return result
    }

    /**
     * Merge a set of molecules in this molecule instance
     * @param {moorhen.Molecule} otherMolecules - A list of other molecules to merge into this instance
     * @param {boolean} [doHide=false] - Indicates whether the source molecules should be hidden when finish
     */
    async mergeMolecules(otherMolecules: moorhen.Molecule[], doHide: boolean = false, doRedraw: boolean = true): Promise<void> {
        try {
            const prevChainNames = this.getChainNames()
            await this.commandCentre.current.cootCommand({
                command: 'merge_molecules',
                commandArgs: [this.molNo, `${otherMolecules.map(molecule => molecule.molNo).join(':')}`],
                returnType: "merge_molecules_return",
                changesMolecules: [this.molNo]
            }, true)

            await Promise.all(otherMolecules.map(molecule => {
                if (doHide) {
                    this.store.dispatch(hideMolecule(molecule))
                }
                return molecule.transferLigandDicts(this, false)
            }))
            
            await this.updateAtoms()
            const currentChains = this.getChainNames()
            const newChains = currentChains.filter(chainName => !prevChainNames.includes(chainName))
            newChains.forEach(chainName => {
                const selectedColour = getRandomMoleculeColour()
                this.addColourRule('chain', `//${chainName}`, selectedColour, [`//${chainName}`, selectedColour])
            })

            if (doRedraw) {
                await this.redraw()
            }
        } catch (err) {
            console.log(err)
        }
    }

    /**
     * Add a ligand of a given type to this molecule isntance
     * @param {string} resType - Three letter code for the ligand of interest
     * @param {number} [fromMolNo=-999999] - Indicate the molecule number to which the ligand dictionary was associated (use -999999 for "any")
     */
    async addLigandOfType(resType: string, fromMolNo: number = -999999): Promise<moorhen.WorkerResponse> {
        const getMonomer = () => {
            return this.commandCentre.current.cootCommand({
                returnType: 'status',
                command: 'get_monomer_and_position_at',
                commandArgs: [resType.toUpperCase(), fromMolNo,
                ...this.glRef.current.origin.map(coord => -coord)
                ]
            }, true) as Promise<moorhen.WorkerResponse<number>>
        }

        let result = await getMonomer()

        if (result.data.result.result === -1) {
            await this.loadMissingMonomer(resType.toUpperCase(), fromMolNo)
            result = await getMonomer()
        }
        if (result.data.result.status === "Completed" && result.data.result.result !== -1) {
            const newMolecule = new MoorhenMolecule(this.commandCentre, this.glRef, this.store, this.monomerLibraryPath)
            newMolecule.setAtomsDirty(true)
            newMolecule.molNo = result.data.result.result
            newMolecule.name = resType.toUpperCase()
            newMolecule.defaultBondOptions = this.defaultBondOptions
            newMolecule.defaultM2tParams = this.defaultM2tParams
            newMolecule.defaultResidueEnvironmentOptions = this.defaultResidueEnvironmentOptions
            await this.mergeMolecules([newMolecule], true)
            return newMolecule.delete()
        } else {
            console.log('Error getting monomer... Missing dictionary?')
        }
    }

    /**
     * Get dictionary for a ligand associated with this molecule
     * @param {string} comp_id - Three letter code for the ligand of interest
     * @returns {string} The ligand dictionary
     */
    getDict(comp_id: string): string {
        if (Object.hasOwn(this.ligandDicts, comp_id)) {
            return this.ligandDicts[comp_id]
        }
        console.log(`Cannot find ligand dict with comp_id ${comp_id}`)
    }

    /**
     * Internal function used to store a ligand dictionary in the cache for this molecule instance
     * @param {string} fileContent - The dictionary contents
     */
    cacheLigandDict(fileContent: string): void {
        if (!fileContent) {
            console.warn('File contents for dictionary not found, doing nothing...')
            return
        }

        let possibleIndentedLines = fileContent.split("\n")
        let unindentedLines: string[] = []
        let comp_id = 'list'
        let rx = /data_comp_(.*)/;

        for (let line of possibleIndentedLines) {
            let trimmedLine = line.trim()
            let arr = rx.exec(trimmedLine)
            if (arr !== null) {
                //Had we encountered a previous compound ?  If so, add it into the energy lib
                if (comp_id !== 'list') {
                    const reassembledCif = unindentedLines.join("\n")
                    this.ligandDicts[comp_id] = reassembledCif
                    unindentedLines = []
                }
                comp_id = arr[1]
            }
            unindentedLines.push(line.trim())
        }

        if (comp_id !== 'list') {
            const reassembledCif = unindentedLines.join("\n")
            this.ligandDicts[comp_id] = reassembledCif
        }
    }

    /**
     * Associate ligand dictionary with this molecule instance
     * @param {string} fileContent - The dictionary contents
     */
    async addDict(fileContent: string): Promise<void> {
        if (!fileContent) {
            console.warn('File contents for dictionary not found, doing nothing...')
            return
        }

        await this.commandCentre.current.cootCommand({
            returnType: "status",
            command: 'read_dictionary_string',
            commandArgs: [fileContent, this.molNo]
        }, false)

        this.cacheLigandDict(fileContent)
    }

    /**
     * Undo last action performed on this molecule
     */
    async undo(): Promise<void> {
        await this.commandCentre.current.cootCommand({
            returnType: "status",
            command: "undo",
            commandArgs: [this.molNo],
            changesMolecules: [this.molNo]
        }, true)
        this.setAtomsDirty(true)
        return this.redraw()
    }

    /**
     * Redo last action performed on this molecule
     */
    async redo(): Promise<void> {
        await this.commandCentre.current.cootCommand({
            returnType: "status",
            command: "redo",
            commandArgs: [this.molNo],
            changesMolecules: [this.molNo]
        }, true)
        this.setAtomsDirty(true)
        return this.redraw()
    }

    /**
     * Update the ligand dictionaries for this molecule instance
     */
    async updateLigands(): Promise<void> {
        this.cachedLigandSVGs = null
        let ligandList: moorhen.LigandInfo[] = []
        const ligandInfoVec = window.CCP4Module.get_ligand_info_for_structure(this.gemmiStructure)
        const ligandInfoVecSize = ligandInfoVec.size()
        for (let i = 0; i < ligandInfoVecSize; i++) {
            const ligandInfo = ligandInfoVec.get(i)
            ligandList.push({...ligandInfo})
        }
        ligandInfoVec.delete()

        this.ligands = ligandList
        this.checkIsLigand()
    }

    /**
     * Get atom information for a given CID selection
     * @param {string} cid - The CID selection
     * @returns {Promise<moorhen.AtomInfo[]>} JS objects containing atom information
     */
    async gemmiAtomsForCid(cid: string, omitExcludedCids: boolean = false): Promise<moorhen.AtomInfo[]> {
        if (this.atomsDirty || this.gemmiStructure.isDeleted()) {
            await this.updateAtoms()
        }

        if (this.cachedGemmiAtoms && (cid === '/*/*/*/*' || cid === '//')) {
            return this.cachedGemmiAtoms
        }

        let result: moorhen.AtomInfo[] = []
        const atomInfoVec = window.CCP4Module.get_atom_info_for_selection(this.gemmiStructure, cid, omitExcludedCids ? this.excludedSelections.join("||") : "")
        const atomInfoVecSize = atomInfoVec.size()
        for (let i = 0; i < atomInfoVecSize; i++) {
            const atomInfo = atomInfoVec.get(i)
            result.push(atomInfo)
        }
        atomInfoVec.delete()

        if (cid === '/*/*/*/*' || cid === '//') {
            this.cachedGemmiAtoms = result
        }

        return result
    }

    /**
     * Determine whether this molecule instance has any visible buffers
     * @param {string[]} excludeStyles - A list of representation styles that should be excluded from this check
     * @returns {boolean} True if the molecule has any visible buffers
     */
    isVisible(excludeStyles: string[] = ['hover', 'unitCell', 'originNeighbours', 'selection', 'transformation', 'contact_dots', 'chemical_features', 'VdWSurface']): boolean {
        const state = this.store.getState()
        const isVisible = state.molecules.visibleMolecules.some(molNo => molNo === this.molNo)
        const hasVisibleBuffers = this.representations
            .filter(item => !excludeStyles.includes(item.style))
            .some(item => item.visible)
        return (hasVisibleBuffers || this.adaptativeBondsEnabled) && isVisible
    }

    /**
     * Set the default colour rules for this molecule from libcoot API
     */
    async fetchDefaultColourRules() {
        if (this.defaultColourRules) {
            console.log('Default colour rules already set, doing nothing...')
            return
        }

        const response = await this.commandCentre.current.cootCommand({
            message: 'coot_command',
            command: "get_colour_rules",
            returnType: 'colour_rules',
            commandArgs: [this.molNo],
        }, false) as moorhen.WorkerResponse<libcootApi.PairType<string, string>[]>
        
        this.defaultColourRules = []
        for (let rule of response.data.result.result) {
            this.addColourRule('chain',  rule.first, rule.second, [rule.first, rule.second])
        }
    }

    /**
     * Hide representations for a given CID selection
     * @param {string} cid - The CID selection
     * @param {boolean} [redraw=true] - Indicates if the molecule should be redrawn
     */
    async hideCid(cid: string, redraw: boolean = true): Promise<void> {
        if (cid.includes('||')) {
            await Promise.all(cid.split('||').map(i => {
                this.commandCentre.current.cootCommand({
                    message: 'coot_command',
                    command: "add_to_non_drawn_bonds",
                    returnType: 'status',
                    commandArgs: [this.molNo, i],
                }, false)
            }))
        } else {
            await this.commandCentre.current.cootCommand({
                message: 'coot_command',
                command: "add_to_non_drawn_bonds",
                returnType: 'status',
                commandArgs: [this.molNo, cid],
            }, false)    
        }

        // We want a list with the CID ranges (e.g. //A/1-10)
        if (cid.includes("||")) {
            this.excludedSelections.push(...cid.split("||"))
        } else {
            this.excludedSelections.push(cid)
        }

        // We also want a list with individual residue CIDs
        const cidVect = window.CCP4Module.parse_multi_cids(this.gemmiStructure, cid)
        const cidVectSize = cidVect.size()
        for (let i = 0; i < cidVectSize; i++) {
            const cid = cidVect.get(i)
            this.excludedCids.push(cid)
        }
        cidVect.delete()

        // Redraw to apply changes
        if (redraw) {
            await this.redraw()
        }
    }

    /**
     * Unhide all the molecule representations
     * @param {boolean} [redraw=true] - Indicates if the molecule should be redrawn
     */
    async unhideAll(redraw: boolean = true) {
        await this.commandCentre.current.cootCommand({
            message: 'coot_command',
            command: "clear_non_drawn_bonds",
            returnType: 'status',
            commandArgs: [this.molNo],
        }, false)
        this.excludedSelections = []
        this.excludedCids = []
        if (redraw) {
            await this.redraw()
        }
    }

    /**
     * Run rigid body fitting
     * @param {string} cidsString - Residue CID selection 
     * @param {number} mapNo - Map number that should be used
     */
    async rigidBodyFit(cidsString: string, mapNo: number, redraw: boolean = true): Promise<void> {
        await this.commandCentre.current.cootCommand({
            command: "rigid_body_fit",
            returnType: 'status',
            commandArgs: [this.molNo, cidsString, mapNo],
            changesMolecules: [this.molNo]
        }, true)
                
        this.setAtomsDirty(true)
        if (redraw) {
            await this.redraw()
        }
    }

    /**
     * Generate self restraints
     * @param {string} [cid="//"] The CID for local restraints
     * @param {number} [maxRadius=4.2] The maximum radius for the restraints
     */
    async generateSelfRestraints(cid: string = "//", maxRadius: number = 4.2): Promise<void> {
        await this.commandCentre.current.cootCommand({
            command: "generate_local_self_restraints",
            returnType: 'status',
            commandArgs: [this.molNo, maxRadius, cid],
        }, false)
        this.restraints.push({ maxRadius, cid })
    }

    /**
     * Clear all additional restraints
     */
    clearExtraRestraints(): Promise<moorhen.WorkerResponse> {
        this.restraints = []
        return this.commandCentre.current.cootCommand({
            command: "clear_extra_restraints",
            returnType: 'status',
            commandArgs: [this.molNo],
        }, false)
    }

    /**
     * Refine a set of residues
     * @param {string} cid - The CID selection with the atoms that should be refined
     * @param {string} mode - Refinement mode (SINGLE, TRIPLE ...etc.)
     * @param {number} [ncyc=4000] - Number of refinement cycles
     * @param {boolean} [redraw=true] - Indicates if the molecule should be redrawn
     */
    async refineResiduesUsingAtomCid(cid: string, mode: string, ncyc: number = 4000, redraw: boolean = true): Promise<void> {
        await this.commandCentre.current.cootCommand({
            command: "refine_residues_using_atom_cid",
            returnType: 'status',
            commandArgs: [this.molNo, cid, mode, ncyc],
            changesMolecules: [this.molNo]
        }, true)
        
        this.setAtomsDirty(true)
        if (redraw) {
            await this.redraw()
        }
    }

    /**
     * Refine a residue range
     * @param {string} chainId - The chain ID for the residue range
     * @param {string} start - First residue number in the range
     * @param {string} stop - Last residue number in the range
     * @param {number} [ncyc=4000] - Number of refinement cycles
     * @param {boolean} [redraw=true] - Indicates if the molecule should be redrawn
     */
    async refineResidueRange(chainId: string, start: number, stop: number, ncyc: number = 4000, redraw: boolean = true): Promise<void> {
        await this.commandCentre.current.cootCommand({
            returnType: "status",
            command: 'refine_residue_range',
            commandArgs: [this.molNo, chainId, start, stop, ncyc],
            changesMolecules: [this.molNo]
        }, true)

        this.setAtomsDirty(true)
        if (redraw) {
            await this.redraw()
        }
    }

    /**
     * Refine a residue CID with animation
     * @param {string[]} cid - The CID selection used to create the fragment
     * @param {moorhen.Map} activeMap - The map instance used in the refinement
     * @param {number} dist - The maximum distance used to get neighboring residues for the refinement. Use -1 for literal CID instead of neighbours.
     * @param {boolean} redraw - Indicate if the molecules should be redrawn
     * @param {boolean} redrawFragmentFirst - Indicate if the fragment should be redrawn first
     */
    async refineResiduesUsingAtomCidAnimated(cid: string, activeMap: moorhen.Map, dist: number = 6, redraw: boolean = true, redrawFragmentFirst: boolean = true) {
        let cidList: string[]
        if (dist <= 0) {
            cidList = [cid]
        } else {
            cidList = await this.getNeighborResiduesCids(cid, dist)
        }

        const newMolecule = await this.copyFragmentForRefinement(cidList, activeMap, redraw, redrawFragmentFirst)
        await newMolecule.animateRefine(50, 30, 50)
        await this.mergeFragmentFromRefinement(cidList.join('||'), newMolecule, true, true)        
    }

    /**
     * Refine a molecule with animation effect
     * @param {number} n_cyc - The total number of refinement cycles for each iteration
     * @param {number} n_iteration - The number of iterations
     * @param {number} [final_n_cyc=100] - Number of refinement cycles in the last iteration
     */
    async animateRefine(n_cyc: number, n_iteration: number, final_n_cyc: number = 100) {
        for (let i = 0; i <= n_iteration; i++) {
            const result = await this.commandCentre.current.cootCommand({
                returnType: 'status_instanced_mesh_pair',
                command: 'refine',
                commandArgs: [this.molNo, i !== n_iteration ? n_cyc : final_n_cyc]
            }, false) as moorhen.WorkerResponse<{ status: number; mesh: libcootApi.InstancedMeshJS[]; }>
    
            if (result.data.result.result.status !== -2) {
                return
            }
    
            if (i !== n_iteration) {
                await this.drawWithStyleFromMesh('CBs', [result.data.result.result.mesh])
            }
        }
        this.setAtomsDirty(true)
        await this.fetchIfDirtyAndDraw('CBs')
    }
    
    /**
     * Delete residues in a given CID
     * @param {string} cid - The CID to delete
     * @param {boolean} [redraw=true] - Indicates if the molecule should be redrawn
     * @returns {object} - A pair where first is the return status and second is the atom count of the molecule after deletion
     */
    async deleteCid(cid: string, redraw: boolean = true): Promise<libcootApi.PairType<number, number>> {
        const result = await this.commandCentre.current.cootCommand({
            returnType: "status",
            command: "delete_using_cid",
            commandArgs: [this.molNo, cid, "LITERAL"],
            changesMolecules: [this.molNo]
        }, true) as moorhen.WorkerResponse<libcootApi.PairType<number, number>>
        
        this.setAtomsDirty(true)
        if (redraw) {
            await this.redraw()
        }
        
        return result.data.result.result
    }

    /**
     * Use SSM to superpose this molecule (as the moving structure) with another molecule isntance
     * @param {string} movChainId - Chain ID for the moving structure
     * @param {string} refMolNo - Molecule number for the reference structure
     * @param {string} refChainId - Chain ID for the reference structure
     * @param {boolean} [redraw=true] - Indicates if the molecule should be redrawn
     */
    async SSMSuperpose(movChainId: string, refMolNo: number, refChainId: string, redraw: boolean = true): Promise<void> {
        this.commandCentre.current.cootCommand({
            command: "SSM_superpose",
            returnType: 'superpose_results',
            commandArgs: [refMolNo, refChainId, this.molNo, movChainId],
            changesMolecules: [this.molNo]
        }, true)
        
        this.setAtomsDirty(true)
        if (redraw) {
            await this.redraw()
            await this.centreOn('/*/*/*/*', true)
        }
    }

    /**
     * Use LSQKB to superpose this molecule (as the moving structure) with another molecule isntance
     * @param {string} refMolNo - Molecule number for the reference structure
     * @param {moorhen.lskqbResidueRangeMatch[]} residueMatches - A list of objects describing the residue matches for LSQKB
     * @param {number} [matchType=1] - The match type for LSQKB: 0 - all | 1 - main | 2 - CAs 
     * @param {boolean} [redraw=true] - Indicates if the molecule should be redrawn
     */
    async lsqkbSuperpose(refMolNo: number, residueMatches: moorhen.lskqbResidueRangeMatch[], matchType: number = 1, redraw: boolean = true): Promise<void> {
        await this.commandCentre.current.cootCommand({
            command: 'clear_lsq_matches',
            commandArgs: [ ],
            returnType: 'status'
        }, false)
        
        await Promise.all(residueMatches.map(item => {
            return this.commandCentre.current.cootCommand({
                command: 'add_lsq_superpose_match',
                commandArgs: [item.refChainId, ...item.refResidueRange, item.movChainId, ...item.movResidueRange, matchType],
                returnType: 'status'
            }, false)
        }))
        
        await this.commandCentre.current.cootCommand({
            command: 'lsq_superpose',
            commandArgs: [refMolNo, this.molNo],
            returnType: 'status'
        }, false)
        
        this.setAtomsDirty(true)
        if (redraw) {
            await this.redraw()
            await this.centreOn('/*/*/*/*', true)
        }
    }

    /**
     * A function to fit a given ligand
     * @param {number} mapMolNo - The map iMol that will be used for ligand fitting
     * @param {number} ligandMolNo - The ligand iMol that will be fitted
     * @param {boolean} [fitRightHere=true] - Indicates if the ligand should be fitted at the current origin
     * @param {boolean} [redraw=false] - Indicates if the fitted ligands should be drawn
     * @param {boolean} [useConformers=false] - Indicates if there is need to test multiple conformers
     * @param {number} [conformerCount=0] - Conformer count
     * @returns {Promise<moorhen.Molecule[]>} - A list of fitted ligands
     */
    async fitLigand(mapMolNo: number, ligandMolNo: number, fitRightHere: boolean = true, redraw: boolean = false, useConformers: boolean = false, conformerCount: number = 0): Promise<moorhen.Molecule[]> {
        let newMolecules: moorhen.Molecule[] = []
        const command = fitRightHere ? 'fit_ligand_right_here' : 'fit_ligand'
        const returnType = fitRightHere ? 'int_array' : 'fit_ligand_info_array'
        
        const commandArgs = fitRightHere ? [
            this.molNo, mapMolNo, ligandMolNo,
            ...this.glRef.current.origin.map(coord => -coord),
            1., useConformers, conformerCount
        ] : [
            this.molNo, mapMolNo, ligandMolNo,
            1., useConformers, conformerCount
        ]

        const result = await this.commandCentre.current.cootCommand({
            returnType: returnType,
            command: command,
            commandArgs: commandArgs,
            changesMolecules: [this.molNo]
        }, true) as moorhen.WorkerResponse<(number[] | libcootApi.fitLigandInfo[])>
        
        if (result.data.result.status === "Completed") {
            newMolecules = await Promise.all(
                result.data.result.result.map(async (fitLigandResult: (number | libcootApi.fitLigandInfo), idx: number) => {
                    const newMolecule = new MoorhenMolecule(this.commandCentre, this.glRef, this.store, this.monomerLibraryPath)
                    newMolecule.molNo = fitRightHere ? fitLigandResult as number : (fitLigandResult as libcootApi.fitLigandInfo).imol 
                    newMolecule.name = `Fit. lig. #${idx + 1}`
                    newMolecule.isDarkBackground = this.isDarkBackground
                    newMolecule.defaultBondOptions = this.defaultBondOptions
                    if (redraw) {
                        await newMolecule.fetchIfDirtyAndDraw('CBs')
                    }
                    return newMolecule
                })
            )
        } else {
            console.warn('Something went wrong when finding ligands...')
        }
        
        return newMolecules
    }

    /**
     * Get information about the residue B-factors
     * @returns {object[]} An array of objects indicating the residue CID and B-factor
     */
    getResidueBFactors() {
        let result: { cid: string; bFactor: number; normalised_bFactor: number }[] = []
        const resBfactorInfoVec = window.CCP4Module.get_structure_bfactors(this.gemmiStructure)
        const resBfactorInfoVecSize = resBfactorInfoVec.size()
        for (let i = 0; i < resBfactorInfoVecSize; i++) {
            const resInfo = resBfactorInfoVec.get(i)
            result.push({...resInfo})
        }
        resBfactorInfoVec.delete()
        
        return result
    }

    /**
     * Get chain IDs that are related by NCS or molecular symmetry 
     * @returns {string[][]} An array of arrays where chain IDs are grouped together
     */
    async getNcsRelatedChains(): Promise<string[][]> {
        const result = await this.commandCentre.current.cootCommand({
            returnType: 'string_array_array',
            command: 'get_ncs_related_chains',
            commandArgs: [this.molNo]
        }, false) as moorhen.WorkerResponse<string[][]>
        return result.data.result.result
    }

    /**
     * A function to get the number of atoms in the current molecule
     * @returns {Promise<number>} The number of atoms in the molecule
     */
    async getNumberOfAtoms(): Promise<number> {
        const result = await this.commandCentre.current.cootCommand({
            returnType: 'int',
            command: 'get_number_of_atoms',
            commandArgs: [this.molNo],
        }, false) as moorhen.WorkerResponse<number>
        this.atomCount = result.data.result.result
        return result.data.result.result
    }

    /**
     * Move the molecule to a new position
     * @param {number} x - Coordinate X 
     * @param {number} y - Coordinate Y 
     * @param {number} z - Coordinate Z 
     */
    async moveMoleculeHere(x: number, y: number, z: number): Promise<void> {
        await this.commandCentre.current.cootCommand({
            returnType: 'int',
            command: 'move_molecule_to_new_centre',
            commandArgs: [this.molNo, x, y, z],
        }, false) as moorhen.WorkerResponse<number>
        this.setAtomsDirty(true)
        await this.redraw()
    }

    /**
     * Parse a CID selection into a residue selection object
     * @param {string} cid - The CID selection
     * @returns {object} An object for the residue selection
     */
    async parseCidIntoSelection(cid: string): Promise<moorhen.ResidueSelection> {

        const selectionAtoms = await this.gemmiAtomsForCid(cid)
        if (!selectionAtoms || selectionAtoms.length === 0) {
            console.warn(`Specified CID resulted in no residue selection: ${cid}`)
            return 
        }

        return {
            molecule: this,
            first: parseAtomInfoLabel(selectionAtoms[0]),
            second: parseAtomInfoLabel(selectionAtoms[selectionAtoms.length - 1]),
            isMultiCid: cid.includes('||'),
            cid: cid.includes('||') ? cid.split('||') : cid,
            label: cid
        }
    }

    /**
     * Test whether an atom selection is valid
     * @param {string} cid - The CID selection
     * @returns {boolean} Whether the selection is valid
     */
    async isValidSelection(cid: string): Promise<boolean> {
        try {
            const selectedAtoms = await this.gemmiAtomsForCid(cid)
            if (!selectedAtoms || selectedAtoms.length === 0) {
                return false
            }
        } catch (error) {
            console.warn(error)
            return false
        }
        return true
    }

    /**
     * Get the CIDs of residues not included in the input CID
     * @param {string} cid - The input CID selection
     * @returns {string[]} An array of CIDs for the residue ranges not included in the input CID
     */
    getNonSelectedCids(cid: string): string[] {
        let result: string[] = []
        const nonSelectedCidVec = window.CCP4Module.get_non_selected_cids(this.gemmiStructure, cid)
        const nonSelectedCidVecSize = nonSelectedCidVec.size()
        for (let i = 0; i < nonSelectedCidVecSize; i++) {
            const iCid = nonSelectedCidVec.get(i)
            result.push(iCid)
        }
        nonSelectedCidVec.delete()
        return result
    }

    /**
     * Get the secondary structure information for the residues in the current molecule
     * @param {number} modelNumber - The model number to extract secondary structure information from
     * @returns {object[]} An array of objects containing the secondary structure information for each residue
     */
    async getSecondaryStructInfo(modelNumber: number = 1): Promise<libcootApi.ResidueSpecJS[]> {
        const secondaryStructInfoVec = await this.commandCentre.current.cootCommand({
            returnType: 'residue_specs',
            command: 'GetSecondaryStructure',
            commandArgs: [ this.molNo, modelNumber ],
        }, false) as moorhen.WorkerResponse<libcootApi.ResidueSpecJS[]>
        
        return secondaryStructInfoVec.data.result.result
    }

    /**
     * Export the current molecule as a gltf file in binary format
     * @param {string} representationId - The id of the representation to export
     * @returns {ArrayBuffer} - The contents of the gltf file in binary format
     */
    async exportAsGltf(representationId: string): Promise<ArrayBuffer> {
        const selectedRepresentation = this.representations.find(item => item.uniqueId === representationId)
        if (selectedRepresentation) {
            const fileContents = await selectedRepresentation.exportAsGltf()
            return fileContents
        } else {
            console.warn(`Could not find representation with id ${representationId}`)
        }
    }

    /**
     * Get results of privateer validation for this molecule instance
     * @param {boolean} useCache - Whether to use the cached results or not
     * @returns {Promise<privateer.ResultsEntry[]>} A list of results from privateer validation
     */
    async getPrivateerValidation(useCache: boolean = false): Promise<privateer.ResultsEntry[]> {
        if (useCache && this.cachedPrivateerValidation && !this.atomsDirty) {
            return this.cachedPrivateerValidation
        }
        
        const result = await this.commandCentre.current.cootCommand({
            command: 'privateer_validate',
            commandArgs: [this.molNo],
            returnType: 'privateer_results'
        }, false) as moorhen.WorkerResponse<privateer.ResultsEntry[]>
        
        if (useCache) {
            this.cachedPrivateerValidation = result.data.result.result
        }

        return result.data.result.result
    }

    /**
     * Get SVG descriptions for the ligands in this molecule instance
     * @param {string} resName - The name of the ligand to get SVG descriptions for
     * @param {boolean} useCache - Whether to use the cached results or not
     * @returns {Promise<string[]>} A list of SVG descriptions for the ligands in this molecule instance
     */
    async getLigandSVG(resName: string, useCache: boolean = false): Promise<string> {
        if (useCache && this.cachedLigandSVGs && !this.atomsDirty && resName in this.cachedLigandSVGs) {
            return this.cachedLigandSVGs[resName]
        }

        const state = this.store.getState()
        const isDark = state.sceneSettings.isDark

        const result = await this.commandCentre.current.cootCommand({
                returnType: "string",
                command: 'get_svg_for_residue_type',
                commandArgs: [this.molNo, resName, false, isDark],
            }, false) as moorhen.WorkerResponse<string>

        const ligandSVG = formatLigandSVG(result.data.result.result)

        if (useCache && ligandSVG !== `No dictionary for ${resName}`) {
            this.cachedLigandSVGs = { ...this.cachedLigandSVGs, [resName]: ligandSVG }
        }

        return ligandSVG
    }

    /**
     * Change the ID of a given chain
     * @param {string} oldId - The old chain ID
     * @param {string} newId - The new chain ID
     * @param {number} startResNo - The start residue number
     * @param {number} endResNo - The end residue number
     * @returns {number} - Status code -1 on a conflict, 1 on good, 0 on did nothing
     */
    async changeChainId(oldId: string, newId: string, redraw: boolean = false, startResNo?: number, endResNo?: number): Promise<number> {
        const status = await this.commandCentre.current.cootCommand({
            returnType: 'pair_int_str',
            command: 'change_chain_id',
            commandArgs: [ this.molNo, oldId, newId, (startResNo !== undefined && endResNo !== undefined), startResNo ? startResNo : 0, endResNo ? endResNo : 0 ],
        }, false) as moorhen.WorkerResponse<{first: number; second: string}>
        
        if (status.data.result.result.first === 1) {
            this.setAtomsDirty(true)
            // If the chain is new, then we need to create a random colour rule for it...
            const selectedColour = getRandomMoleculeColour()
            this.addColourRule('chain', `//${newId}`, selectedColour, [`//${newId}`, selectedColour])
            if (redraw) {
                await this.redraw()
            }
        } else {
            console.warn(status.data.result.result.second)
            console.warn(`change_chain_id returned status ${status.data.result.result.first}`)
        }
        return status.data.result.result.first
    }

    /**
     * Split a molecule with multiple models into separate molecules (one for each model)
     * @param {boolean} [draw=false] - Indicates whether the new molecules should be drawn
     * @returns {moorhen.Molecule[]} - A list with the new molecules
     */
    async splitMultiModels(draw: boolean = false): Promise<moorhen.Molecule[]> {
        const result = await this.commandCentre.current.cootCommand({
            returnType: 'int_array',
            command: 'split_multi_model_molecule',
            commandArgs: [ this.molNo ],
        }, false) as moorhen.WorkerResponse<number[]>
        
        if (result.data.result.status === 'Completed') {
            if (draw) {
                this.store.dispatch(hideMolecule(this))
            }
            return await Promise.all(
                result.data.result.result.map(async (molNo, index) => {
                    const newMolecule = new MoorhenMolecule(this.commandCentre, this.glRef, this.store, this.monomerLibraryPath)
                    newMolecule.name = `${this.name}-${index+1}`
                    newMolecule.molNo = molNo
                    await this.transferMetaData(newMolecule)
                    newMolecule.setAtomsDirty(true)
                    await newMolecule.fetchDefaultColourRules()
                    if (draw) {
                        await newMolecule.fetchIfDirtyAndDraw('CBs')
                    }
                    return newMolecule
                })
            )
        } 
        else {
            console.warn(result.data.consoleMessage)
        }
    }

    /**
     * Minimize the energy of a given set of residues (usually a ligand)
     * @param cid - The CID for the input residues
     * @param ncyc - The number of cycles 
     * @param nIterations - The number of iterations
     * @param useRamaRestraints - Indicates whether ramachandran restraints should be used
     * @param ramaWeight - Indicates the weight assigned to ramachandran restraints
     * @param useTorsionRestraints - Indicates whether torsion restraints should be used
     * @param torsionWeight - Indicates the weight assigned to torsion restraints
     */
    async minimizeEnergyUsingCidAnimated(cid: string, ncyc: number, nIterations: number, useRamaRestraints: boolean, ramaWeight: number, useTorsionRestraints: boolean, torsionWeight: number) {
        const commandArgs = [
            this.molNo,
            cid,
            ncyc,
            useRamaRestraints,
            ramaWeight,
            useTorsionRestraints,
            torsionWeight,
            true
        ]
        for (let i = 0; i < nIterations; i++) {
            const result = await this.commandCentre.current.cootCommand({
                command: 'minimize_energy',
                commandArgs: commandArgs,
                returnType: 'status_instanced_mesh_pair',
            }, false) as moorhen.WorkerResponse<{status: number; mesh: libcootApi.InstancedMeshJS}>
    
            if (result.data.result.result.status !== -2) {
                break                
            } else {
                await this.drawWithStyleFromMesh('CBs', [result.data.result.result.mesh])
            }
        }
        this.setAtomsDirty(true)
        await this.redraw()
    }

    /**
     * Fetch header information for this molecule instance
     * @param {boolean} useCache - Whether to use the cached results or not
     * @returns {Promise<libcootApi.headerInfoJS>} Object containing header information
     */
    async fetchHeaderInfo(useCache: boolean = true): Promise<libcootApi.headerInfoJS> {
        if (useCache && this.headerInfo !== null) {
            return this.headerInfo
        }

        const headerInfo = await this.commandCentre.current.cootCommand({
            command: 'get_header_info',
            commandArgs: [ this.molNo ],
            returnType: 'header_info_t',
        }, false) as moorhen.WorkerResponse<libcootApi.headerInfoJS>

        if (useCache) {
            this.headerInfo = headerInfo.data.result.result
        }

        return headerInfo.data.result.result
    }
}