##
# File: GromacsPTParserListener.py
# Date: 02-Jun-2022
#
# Updates:
# Generated from GromacsPTParser.g4 by ANTLR 4.10.1
""" ParserLister class for GROMACS PT files.
    @author: Masashi Yokochi
"""
import sys
import collections

from antlr4 import ParseTreeListener
from rmsd.calculate_rmsd import NAMES_ELEMENT  # noqa: F401 pylint: disable=no-name-in-module, import-error, unused-import

try:
    from wwpdb.utils.align.alignlib import PairwiseAlign  # pylint: disable=no-name-in-module
    from wwpdb.utils.nmr.mr.GromacsPTParser import GromacsPTParser
    from wwpdb.utils.nmr.mr.ParserListenerUtil import (checkCoordinates,
                                                       translateToStdAtomName,
                                                       translateToStdResName,
                                                       REPRESENTATIVE_MODEL_ID)
    from wwpdb.utils.nmr.ChemCompUtil import ChemCompUtil
    from wwpdb.utils.nmr.BMRBChemShiftStat import BMRBChemShiftStat
    from wwpdb.utils.nmr.NEFTranslator.NEFTranslator import NEFTranslator
    from wwpdb.utils.nmr.AlignUtil import (monDict3,
                                           letterToDigit, indexToLetter,
                                           alignPolymerSequence,
                                           assignPolymerSequence,
                                           trimSequenceAlignment,
                                           retrieveAtomIdentFromMRMap)
except ImportError:
    from nmr.align.alignlib import PairwiseAlign  # pylint: disable=no-name-in-module
    from nmr.mr.GromacsPTParser import GromacsPTParser
    from nmr.mr.ParserListenerUtil import (checkCoordinates,
                                           translateToStdAtomName,
                                           translateToStdResName,
                                           REPRESENTATIVE_MODEL_ID)
    from nmr.ChemCompUtil import ChemCompUtil
    from nmr.BMRBChemShiftStat import BMRBChemShiftStat
    from nmr.NEFTranslator.NEFTranslator import NEFTranslator
    from nmr.AlignUtil import (monDict3,
                               letterToDigit, indexToLetter,
                               alignPolymerSequence,
                               assignPolymerSequence,
                               trimSequenceAlignment,
                               retrieveAtomIdentFromMRMap)


# This class defines a complete listener for a parse tree produced by GromacsPTParser.
class GromacsPTParserListener(ParseTreeListener):

    # atom name mapping of public MR file between the archive coordinates and submitted ones
    __mrAtomNameMapping = None

    # CCD accessing utility
    __ccU = None

    # BMRB chemical shift statistics
    __csStat = None

    # NEFTranslator
    __nefT = None

    # Pairwise align
    __pA = None

    # coordinates information generated by ParserListenerUtil.checkCoordinates()
    __polySeqModel = None
    __nonPolyModel = None

    __hasPolySeqModel = False

    # polymer sequence of GROMACS parameter/topology file
    __polySeqPrmTop = None

    __seqAlign = None
    __chainAssign = None

    # system
    __system = None

    # atoms
    __atoms = []

    # molecules
    __molecules = []

    # collection of number selection
    numberSelection = []

    # GROMACS atom number dictionary
    __atomNumberDict = None

    # __cur_column_len = None
    __cur_word_len = None

    warningMessage = ''

    def __init__(self, verbose=True, log=sys.stdout,
                 representativeModelId=REPRESENTATIVE_MODEL_ID,
                 mrAtomNameMapping=None,
                 cR=None, cC=None, ccU=None, csStat=None, nefT=None):

        self.__mrAtomNameMapping = None if mrAtomNameMapping is None or len(mrAtomNameMapping) == 0 else mrAtomNameMapping

        if cR is not None:
            ret = checkCoordinates(verbose, log, representativeModelId, cR, cC, testTag=False)
            self.__polySeqModel = ret['polymer_sequence']
            self.__nonPolyModel = ret['non_polymer']

        self.__hasPolySeqModel = self.__polySeqModel is not None and len(self.__polySeqModel) > 0

        # CCD accessing utility
        self.__ccU = ChemCompUtil(verbose, log) if ccU is None else ccU

        # BMRB chemical shift statistics
        self.__csStat = BMRBChemShiftStat(verbose, log, self.__ccU) if csStat is None else csStat

        # NEFTranslator
        self.__nefT = NEFTranslator(verbose, log, self.__ccU, self.__csStat) if nefT is None else nefT

        # Pairwise align
        if self.__hasPolySeqModel:
            self.__pA = PairwiseAlign()
            self.__pA.setVerbose(verbose)

        self.defaultStatements = 0
        self.moleculetypeStatements = 0
        self.atomtypesStatements = 0
        self.pairtypesStatements = 0
        self.bondtypesStatements = 0
        self.angletypesStatements = 0
        self.dihedraltypesStatements = 0
        self.constrainttypesStatements = 0
        self.nonbond_paramsStatements = 0
        self.atomsStatements = 0
        self.bondsStatements = 0
        self.pairsStatements = 0
        self.pairs_nbStatements = 0
        self.anglesStatements = 0
        self.dihedralsStatements = 0
        self.exclusionsStatements = 0
        self.constraintsStatements = 0
        self.settlesStatements = 0
        self.virtual_sites1Statements = 0
        self.virtual_sites2Statements = 0
        self.virtual_sites3Statements = 0
        self.virtual_sites4Statements = 0
        self.virtual_sitesnStatements = 0
        self.systemStatements = 0
        self.moleculesStatements = 0

    # Enter a parse tree produced by GromacsPTParser#gromacs_pt.
    def enterGromacs_pt(self, ctx: GromacsPTParser.Gromacs_ptContext):  # pylint: disable=unused-argument
        self.__atomNumberDict = {}
        self.__polySeqPrmTop = []

    # Exit a parse tree produced by GromacsPTParser#gromacs_pt.
    def exitGromacs_pt(self, ctx: GromacsPTParser.Gromacs_ptContext):  # pylint: disable=unused-argument
        if not self.__hasPolySeqModel:
            if len(self.warningMessage) == 0:
                self.warningMessage = None
            else:
                self.warningMessage = self.warningMessage[0:-1]
                self.warningMessage = '\n'.join(set(self.warningMessage.split('\n')))
            return

        if len(self.__atoms) == 0:
            return

        chainIndex = letterToDigit(self.__polySeqModel[0]['chain_id']) - 1  # set tentative chain_id from label_asym_id, which will be assigned to coordinate auth_asym_id
        chainId = indexToLetter(chainIndex)

        terminus = [atom['auth_atom_id'].endswith('T') for atom in self.__atoms]

        atomTotal = len(self.__atoms)
        if terminus[0]:
            terminus[0] = False
        for i in range(0, atomTotal - 1):
            j = i + 1
            if terminus[i] and terminus[j]:
                terminus[i] = False
        if terminus[-1]:
            terminus[-1] = False

        seqIdList = []
        compIdList = []

        NON_METAL_ELEMENTS = ('H', 'C', 'N', 'O', 'P', 'S')

        prevAtomName = ''
        prevSeqId = None
        offset = 0
        for atom in self.__atoms:
            atomNum = atom['atom_number']
            atomName = atom['auth_atom_id']
            atomType = atom['atom_type']
            _seqId = atom['auth_seq_id']
            compId = atom['auth_comp_id']
            if compId not in monDict3 and self.__mrAtomNameMapping is not None:
                _, compId, atomName = retrieveAtomIdentFromMRMap(self.__mrAtomNameMapping, _seqId, compId, atomName)
            # the second condition indicates metal ions
            if terminus[atomNum - 2]\
               or (compId == atomName and compId.title() in NAMES_ELEMENT)\
               or (len(prevAtomName) > 0 and prevAtomName[0] not in NON_METAL_ELEMENTS and prevSeqId != _seqId):
                self.__polySeqPrmTop.append({'chain_id': chainId,
                                             'seq_id': seqIdList,
                                             'auth_comp_id': compIdList})
                seqIdList = []
                compIdList = []
                chainIndex += 1
                chainId = indexToLetter(chainIndex)
                offset = 1 - _seqId
            seqId = _seqId + offset
            if seqId not in seqIdList:
                seqIdList.append(seqId)
                compIdList.append(compId)
            self.__atomNumberDict[atomNum] = {'chain_id': chainId,
                                              'seq_id': seqId,
                                              'auth_comp_id': compId,
                                              'auth_atom_id': atomName,
                                              'atom_type': atomType}
            prevAtomName = atomName
            prevSeqId = _seqId

        self.__polySeqPrmTop.append({'chain_id': chainId,
                                     'seq_id': seqIdList,
                                     'auth_comp_id': compIdList})

        for ps in self.__polySeqPrmTop:
            chainId = ps['chain_id']
            compIdList = []
            for seqId, authCompId in zip(ps['seq_id'], ps['auth_comp_id']):
                authAtomIds = [atomNum['auth_atom_id'] for atomNum in self.__atomNumberDict.values()
                               if atomNum['chain_id'] == chainId
                               and atomNum['seq_id'] == seqId
                               and atomNum['auth_atom_id'][0] != 'H']
                authCompId = translateToStdResName(authCompId)
                if self.__ccU.updateChemCompDict(authCompId):
                    chemCompAtomIds = [cca[self.__ccU.ccaAtomId] for cca in self.__ccU.lastAtomList]
                    valid = True
                    for _atomId in authAtomIds:
                        if _atomId not in chemCompAtomIds:
                            valid = False
                            break
                        if not valid:
                            break
                    if valid:
                        compIdList.append(authCompId)
                        for atomNum in self.__atomNumberDict.values():
                            if atomNum['chain_id'] == chainId and atomNum['seq_id'] == seqId:
                                atomNum['comp_id'] = authCompId
                                atomNum['atom_id'] = atomNum['auth_atom_id']
                                if 'atom_type' in atomNum:
                                    del atomNum['atom_type']
                    else:
                        compId = self.__csStat.getSimilarCompIdFromAtomIds([atomNum['auth_atom_id']
                                                                            for atomNum in self.__atomNumberDict.values()
                                                                            if atomNum['chain_id'] == chainId
                                                                            and atomNum['seq_id'] == seqId])
                        if compId is not None:
                            compIdList.append(compId + '?')  # decide when coordinate is available
                            chemCompAtomIds = None
                            if self.__ccU.updateChemCompDict(compId):
                                chemCompAtomIds = [cca[self.__ccU.ccaAtomId] for cca in self.__ccU.lastAtomList]
                            for atomNum in self.__atomNumberDict.values():
                                if atomNum['chain_id'] == chainId and atomNum['seq_id'] == seqId:
                                    atomNum['comp_id'] = compId
                                    if chemCompAtomIds is not None and atomNum['auth_atom_id'] in chemCompAtomIds:
                                        atomNum['atom_id'] = atomNum['auth_atom_id']
                                        if 'atom_type' in atomNum:
                                            del atomNum['atom_type']
                        else:
                            compIdList.append('.')
                            unknownAtomIds = [_atomId for _atomId in authAtomIds if _atomId not in chemCompAtomIds]
                            self.warningMessage += f"[Unknown atom name] "\
                                f"{unknownAtomIds} are unknown atom names for {authCompId} residue.\n"
                            compIdList.append(f"? {authCompId} {unknownAtomIds}")
                else:
                    compId = self.__csStat.getSimilarCompIdFromAtomIds([atomNum['auth_atom_id']
                                                                        for atomNum in self.__atomNumberDict.values()
                                                                        if atomNum['chain_id'] == chainId
                                                                        and atomNum['seq_id'] == seqId])
                    if compId is not None:
                        compIdList.append(compId + '?')  # decide when coordinate is available
                        chemCompAtomIds = None
                        if self.__ccU.updateChemCompDict(compId):
                            chemCompAtomIds = [cca[self.__ccU.ccaAtomId] for cca in self.__ccU.lastAtomList]
                        for atomNum in self.__atomNumberDict.values():
                            if atomNum['chain_id'] == chainId and atomNum['seq_id'] == seqId:
                                atomNum['comp_id'] = compId
                                if chemCompAtomIds is not None and atomNum['auth_atom_id'] in chemCompAtomIds:
                                    atomNum['atom_id'] = atomNum['auth_atom_id']
                                    if 'atom_type' in atomNum:
                                        del atomNum['atom_type']
                    else:
                        compIdList.append('.')
                        """ deferred to assignNonPolymer()
                        self.warningMessage += f"[Unknown residue name] "\
                            f"{authCompId!r} is unknown residue name.\n"
                        """

            ps['comp_id'] = compIdList

        for atomNum in self.__atomNumberDict.values():
            if 'comp_id' in atomNum and atomNum['comp_id'] != atomNum['auth_comp_id']\
               and 'atom_id' not in atomNum:
                if self.__ccU.updateChemCompDict(atomNum['comp_id']):
                    chemCompAtomIds = [cca[self.__ccU.ccaAtomId] for cca in self.__ccU.lastAtomList]

                    atomId = translateToStdAtomName(atomNum['auth_atom_id'], atomNum['comp_id'], chemCompAtomIds)

                    if atomId is not None and atomId in chemCompAtomIds:
                        atomNum['atom_id'] = atomId
                        if 'atom_type' in atomNum:
                            del atomNum['atom_type']
                    elif atomNum['comp_id'] != atomNum['auth_comp_id']:
                        authCompId = translateToStdResName(atomNum['auth_comp_id'])
                        if self.__ccU.updateChemCompDict(authCompId):
                            chemCompAtomIds = [cca[self.__ccU.ccaAtomId] for cca in self.__ccU.lastAtomList]

                            atomId = translateToStdAtomName(atomNum['auth_atom_id'], authCompId, chemCompAtomIds)

                            if atomId is not None and atomId in chemCompAtomIds:
                                atomNum['atom_id'] = atomId
                                if 'atom_type' in atomNum:
                                    del atomNum['atom_type']
            else:
                authCompId = translateToStdResName(atomNum['auth_comp_id'])
                if self.__ccU.updateChemCompDict(authCompId):
                    atomIds = self.__nefT.get_valid_star_atom_in_xplor(authCompId, atomNum['auth_atom_id'])[0]
                    if len(atomIds) == 1:
                        atomNum['atom_id'] = atomIds[0]
                        if 'atom_type' in atomNum:
                            del atomNum['atom_type']

        file_type = 'nm-aux-gro'

        self.__seqAlign, compIdMapping = alignPolymerSequence(self.__pA, self.__polySeqModel, self.__polySeqPrmTop)

        for cmap in compIdMapping:
            for atomNum in self.__atomNumberDict.values():
                if atomNum['chain_id'] == cmap['chain_id'] and atomNum['seq_id'] == cmap['seq_id']:
                    atomNum['comp_id'] = cmap['comp_id']
                    atomNum['auth_comp_id'] = cmap['auth_comp_id']
                    atomNum['atom_id'] = atomNum['auth_atom_id']
                    if 'atom_type' in atomNum:
                        del atomNum['atom_type']

        for atomNum in self.__atomNumberDict.values():
            if 'atom_id' not in atomNum:
                if 'comp_id' not in atomNum or atomNum['comp_id'] == atomNum['auth_comp_id']:
                    authCompId = translateToStdResName(atomNum['auth_comp_id'])
                    if self.__ccU.updateChemCompDict(authCompId):
                        self.warningMessage += f"[Unknown atom name] "\
                            f"{atomNum['auth_atom_id']!r} is not recognized as the atom name of {atomNum['auth_comp_id']!r} residue.\n"
                else:
                    authCompId = translateToStdResName(atomNum['auth_comp_id'])
                    if self.__ccU.updateChemCompDict(authCompId):
                        atomNum['atom_id'] = atomNum['auth_atom_id']
                        self.warningMessage += f"[Unknown atom name] "\
                            f"{atomNum['auth_atom_id']!r} is not recognized as the atom name of {atomNum['comp_id']!r} residue "\
                            f"(the original residue label is {atomNum['auth_comp_id']!r}).\n"

        self.__chainAssign, message = assignPolymerSequence(self.__pA, self.__ccU, file_type, self.__polySeqModel, self.__polySeqPrmTop, self.__seqAlign)

        if len(message) > 0:
            self.warningMessage += message

        if self.__chainAssign is not None:

            if len(self.__polySeqModel) == len(self.__polySeqPrmTop):

                chain_mapping = {}

                for ca in self.__chainAssign:
                    ref_chain_id = ca['ref_chain_id']
                    test_chain_id = ca['test_chain_id']

                    if ref_chain_id != test_chain_id:
                        chain_mapping[test_chain_id] = ref_chain_id

                if len(chain_mapping) == len(self.__polySeqModel):

                    for ps in self.__polySeqPrmTop:
                        if ps['chain_id'] in chain_mapping:
                            ps['chain_id'] = chain_mapping[ps['chain_id']]

                    for atomNum in self.__atomNumberDict.values():
                        if atomNum['chain_id'] in chain_mapping:
                            atomNum['chain_id'] = chain_mapping[atomNum['chain_id']]

                    self.__seqAlign, _ = alignPolymerSequence(self.__pA, self.__polySeqModel, self.__polySeqPrmTop)
                    self.__chainAssign, _ = assignPolymerSequence(self.__pA, self.__ccU, file_type, self.__polySeqModel, self.__polySeqPrmTop, self.__seqAlign)

            trimSequenceAlignment(self.__seqAlign, self.__chainAssign)

            if self.__nonPolyModel is not None:

                # metal ion
                if any(ps for ps in self.__polySeqPrmTop
                       if len(ps['seq_id']) == 1 and ps['comp_id'][0].title() in NAMES_ELEMENT):
                    self.assignMetalIon()

                # other non-polymer
                nonPolyIndices = [idx for idx, ps in enumerate(self.__polySeqPrmTop)
                                  if not any(ca for ca in self.__chainAssign
                                             if ca['test_chain_id'] == ps['chain_id'])
                                  and len(set(ps['comp_id'])) == 1 and ps['comp_id'][0] == '.']

                if len(nonPolyIndices) > 0:
                    self.assignNonPolymer(nonPolyIndices)

                    for idx in sorted(nonPolyIndices, reverse=True):
                        del self.__polySeqPrmTop[idx]

        if len(self.warningMessage) == 0:
            self.warningMessage = None
        else:
            self.warningMessage = self.warningMessage[0:-1]
            self.warningMessage = '\n'.join(set(self.warningMessage.split('\n')))

    def assignMetalIon(self):
        if self.__nonPolyModel is None:
            return

        metals = collections.Counter(s2['comp_id'][0] for s2 in self.__polySeqPrmTop
                                     if len(s2['seq_id']) == 1 and s2['comp_id'][0].title() in NAMES_ELEMENT).most_common()

        for metal in metals:
            compId = metal[0]

            atomNums = [atomNum for atomNum in self.__atomNumberDict.values()
                        if atomNum['auth_comp_id'] == compId and atomNum['auth_atom_id'] == compId]

            nonPolys = [nonPoly for nonPoly in self.__nonPolyModel
                        if nonPoly['comp_id'][0] == compId]

            for atomNum, nonPoly in zip(atomNums, nonPolys):
                atomNum['chain_id'] = nonPoly['auth_chain_id']
                atomNum['seq_id'] = nonPoly['auth_seq_id'][0]

    def assignNonPolymer(self, nonPolyIndices):
        if self.__nonPolyModel is None:
            return

        authCompIds = []

        for idx, ps in enumerate(self.__polySeqPrmTop):
            if idx not in nonPolyIndices:
                continue
            for authCompId, compId in zip(ps['auth_comp_id'], ps['comp_id']):
                if compId != '.':
                    continue
                authCompIds.append(authCompId)

        nonPolyCompIds = collections.Counter(authCompIds).most_common()

        compIds = []
        for nonPoly in self.__nonPolyModel:
            compId = nonPoly['comp_id'][0]
            if compId.title() in NAMES_ELEMENT:
                continue
            compIds.append(compId)

        refCompIds = collections.Counter(compIds).most_common()

        comp_id_mapping = {}

        for authCompId in nonPolyCompIds:
            refCompId = next((compId[0] for compId in refCompIds if compId[1] == authCompId[1] and compId[1] not in comp_id_mapping.values()), None)
            if refCompId is None:
                self.warningMessage += f"[Unknown residue name] "\
                    f"{authCompId[0]!r} is unknown residue name.\n"
                continue
            comp_id_mapping[authCompId[0]] = refCompId

        for authCompId, compId in comp_id_mapping.items():
            chemCompAtomIds = None
            if self.__ccU.updateChemCompDict(compId):
                chemCompAtomIds = [cca[self.__ccU.ccaAtomId] for cca in self.__ccU.lastAtomList]

            authSeqKeys = []

            for idx, ps in enumerate(self.__polySeqPrmTop):
                if idx not in nonPolyIndices:
                    continue
                _chainId = ps['chain_id']
                for _authCompId, _compId, _seqId in zip(ps['auth_comp_id'], ps['comp_id'], ps['seq_id']):
                    if _authCompId != authCompId or _compId != '.':
                        continue
                    authSeqKeys.append((_chainId, _seqId))

            nonPolys = [nonPoly for nonPoly in self.__nonPolyModel
                        if nonPoly['comp_id'][0] == compId]

            reported_auth_atom_id = []

            for authSeqKey, nonPoly in zip(authSeqKeys, nonPolys):
                atomNums = [atomNum for atomNum in self.__atomNumberDict.values()
                            if atomNum['chain_id'] == authSeqKey[0] and atomNum['seq_id'] == authSeqKey[1]]

                for atomNum in atomNums:
                    atomNum['chain_id'] = nonPoly['auth_chain_id']
                    atomNum['seq_id'] = nonPoly['auth_seq_id'][0]
                    atomNum['comp_id'] = compId
                    authAtomId = atomNum['auth_atom_id']
                    if chemCompAtomIds is not None and authAtomId in chemCompAtomIds:
                        atomNum['atom_id'] = authAtomId
                    else:
                        if authAtomId not in reported_auth_atom_id:
                            atomNum['atom_id'] = atomNum['auth_atom_id']
                            self.warningMessage += f"[Unknown atom name] "\
                                f"{authAtomId!r} is not recognized as the atom name of {compId!r} residue "\
                                f"(the original residue label is {authCompId!r}).\n"
                            reported_auth_atom_id.append(authAtomId)

    # Enter a parse tree produced by GromacsPTParser#default_statement.
    def enterDefault_statement(self, ctx: GromacsPTParser.Default_statementContext):  # pylint: disable=unused-argument
        self.defaultStatements += 1

    # Exit a parse tree produced by GromacsPTParser#default_statement.
    def exitDefault_statement(self, ctx: GromacsPTParser.Default_statementContext):
        if ctx.Integer(0):
            return
        self.defaultStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#moleculetype_statement.
    def enterMoleculetype_statement(self, ctx: GromacsPTParser.Moleculetype_statementContext):  # pylint: disable=unused-argument
        self.moleculetypeStatements += 1

    # Exit a parse tree produced by GromacsPTParser#moleculetype_statement.
    def exitMoleculetype_statement(self, ctx: GromacsPTParser.Moleculetype_statementContext):
        if ctx.moleculetype(0):
            return
        self.moleculetypeStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#moleculetype.
    def enterMoleculetype(self, ctx: GromacsPTParser.MoleculetypeContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#moleculetype.
    def exitMoleculetype(self, ctx: GromacsPTParser.MoleculetypeContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#atomtypes_statement.
    def enterAtomtypes_statement(self, ctx: GromacsPTParser.Atomtypes_statementContext):  # pylint: disable=unused-argument
        self.atomtypesStatements += 1

    # Exit a parse tree produced by GromacsPTParser#atomtypes_statement.
    def exitAtomtypes_statement(self, ctx: GromacsPTParser.Atomtypes_statementContext):
        if ctx.atomtypes(0):
            return
        self.atomtypesStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#atomtypes.
    def enterAtomtypes(self, ctx: GromacsPTParser.AtomtypesContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#atomtypes.
    def exitAtomtypes(self, ctx: GromacsPTParser.AtomtypesContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#pairtypes_statement.
    def enterPairtypes_statement(self, ctx: GromacsPTParser.Pairtypes_statementContext):  # pylint: disable=unused-argument
        self.pairtypesStatements += 1

    # Exit a parse tree produced by GromacsPTParser#pairtypes_statement.
    def exitPairtypes_statement(self, ctx: GromacsPTParser.Pairtypes_statementContext):
        if ctx.pairtypes(0):
            return
        self.pairtypesStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#pairtypes.
    def enterPairtypes(self, ctx: GromacsPTParser.PairtypesContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#pairtypes.
    def exitPairtypes(self, ctx: GromacsPTParser.PairtypesContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#bondtypes_statement.
    def enterBondtypes_statement(self, ctx: GromacsPTParser.Bondtypes_statementContext):  # pylint: disable=unused-argument
        self.bondtypesStatements += 1

    # Exit a parse tree produced by GromacsPTParser#bondtypes_statement.
    def exitBondtypes_statement(self, ctx: GromacsPTParser.Bondtypes_statementContext):
        if ctx.bondtypes(0):
            return
        self.bondtypesStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#bondtypes.
    def enterBondtypes(self, ctx: GromacsPTParser.BondtypesContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#bondtypes.
    def exitBondtypes(self, ctx: GromacsPTParser.BondtypesContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#angletypes_statement.
    def enterAngletypes_statement(self, ctx: GromacsPTParser.Angletypes_statementContext):  # pylint: disable=unused-argument
        self.angletypesStatements += 1

    # Exit a parse tree produced by GromacsPTParser#angletypes_statement.
    def exitAngletypes_statement(self, ctx: GromacsPTParser.Angletypes_statementContext):
        if ctx.angletypes(0):
            return
        self.angletypesStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#angletypes.
    def enterAngletypes(self, ctx: GromacsPTParser.AngletypesContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#angletypes.
    def exitAngletypes(self, ctx: GromacsPTParser.AngletypesContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#dihedraltypes_statement.
    def enterDihedraltypes_statement(self, ctx: GromacsPTParser.Dihedraltypes_statementContext):  # pylint: disable=unused-argument
        self.dihedraltypesStatements += 1

    # Exit a parse tree produced by GromacsPTParser#dihedraltypes_statement.
    def exitDihedraltypes_statement(self, ctx: GromacsPTParser.Dihedraltypes_statementContext):
        if ctx.dihedraltypes(0):
            return
        self.dihedraltypesStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#dihedraltypes.
    def enterDihedraltypes(self, ctx: GromacsPTParser.DihedraltypesContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#dihedraltypes.
    def exitDihedraltypes(self, ctx: GromacsPTParser.DihedraltypesContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#constrainttypes_statement.
    def enterConstrainttypes_statement(self, ctx: GromacsPTParser.Constrainttypes_statementContext):  # pylint: disable=unused-argument
        self.constrainttypesStatements += 1

    # Exit a parse tree produced by GromacsPTParser#constrainttypes_statement.
    def exitConstrainttypes_statement(self, ctx: GromacsPTParser.Constrainttypes_statementContext):
        if ctx.constrainttypes(0):
            return
        self.constrainttypesStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#constrainttypes.
    def enterConstrainttypes(self, ctx: GromacsPTParser.ConstrainttypesContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#constrainttypes.
    def exitConstrainttypes(self, ctx: GromacsPTParser.ConstrainttypesContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#nonbonded_params_statement.
    def enterNonbonded_params_statement(self, ctx: GromacsPTParser.Nonbonded_params_statementContext):  # pylint: disable=unused-argument
        self.nonbond_paramsStatements += 1

    # Exit a parse tree produced by GromacsPTParser#nonbonded_params_statement.
    def exitNonbonded_params_statement(self, ctx: GromacsPTParser.Nonbonded_params_statementContext):
        if ctx.nonbonded_params(0):
            return
        self.nonbond_paramsStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#nonbonded_params.
    def enterNonbonded_params(self, ctx: GromacsPTParser.Nonbonded_paramsContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#nonbonded_params.
    def exitNonbonded_params(self, ctx: GromacsPTParser.Nonbonded_paramsContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#atoms_statement.
    def enterAtoms_statement(self, ctx: GromacsPTParser.Atoms_statementContext):  # pylint: disable=unused-argument
        self.atomsStatements += 1

    # Exit a parse tree produced by GromacsPTParser#atoms_statement.
    def exitAtoms_statement(self, ctx: GromacsPTParser.Atoms_statementContext):  # pylint: disable=unused-argument
        if ctx.atoms(0):
            return
        self.atomsStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#atoms.
    def enterAtoms(self, ctx: GromacsPTParser.AtomsContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#atoms.
    def exitAtoms(self, ctx: GromacsPTParser.AtomsContext):

        try:

            nr = int(str(ctx.Integer(0)))
            seqId = int(str(ctx.Integer(1)))
            # cgnr = int(str(ctx.Integer(2)))

            type = str(ctx.Simple_name(0))
            compId = str(ctx.Simple_name(1))
            atomId = str(ctx.Simple_name(2))

            atom = {'atom_number': nr,
                    'auth_seq_id': seqId,
                    'auth_comp_id': compId,
                    'auth_atom_id': atomId,
                    'atom_type': type}

            if atom not in self.__atoms:
                self.__atoms.append(atom)

        except ValueError:
            pass
        finally:
            self.numberSelection.clear()

    # Enter a parse tree produced by GromacsPTParser#bonds_statement.
    def enterBonds_statement(self, ctx: GromacsPTParser.Bonds_statementContext):  # pylint: disable=unused-argument
        self.bondsStatements += 1

    # Exit a parse tree produced by GromacsPTParser#bonds_statement.
    def exitBonds_statement(self, ctx: GromacsPTParser.Bonds_statementContext):
        if ctx.bonds(0):
            return
        self.bondsStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#bonds.
    def enterBonds(self, ctx: GromacsPTParser.BondsContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#bonds.
    def exitBonds(self, ctx: GromacsPTParser.BondsContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#pairs_statement.
    def enterPairs_statement(self, ctx: GromacsPTParser.Pairs_statementContext):  # pylint: disable=unused-argument
        self.pairsStatements += 1

    # Exit a parse tree produced by GromacsPTParser#pairs_statement.
    def exitPairs_statement(self, ctx: GromacsPTParser.Pairs_statementContext):
        if ctx.pairs(0):
            return
        self.pairsStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#pairs.
    def enterPairs(self, ctx: GromacsPTParser.PairsContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#pairs.
    def exitPairs(self, ctx: GromacsPTParser.PairsContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#pairs_nb_statement.
    def enterPairs_nb_statement(self, ctx: GromacsPTParser.Pairs_nb_statementContext):  # pylint: disable=unused-argument
        self.pairs_nbStatements += 1

    # Exit a parse tree produced by GromacsPTParser#pairs_nb_statement.
    def exitPairs_nb_statement(self, ctx: GromacsPTParser.Pairs_nb_statementContext):
        if ctx.pairs_nb(0):
            return
        self.pairs_nbStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#pairs_nb.
    def enterPairs_nb(self, ctx: GromacsPTParser.Pairs_nbContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#pairs_nb.
    def exitPairs_nb(self, ctx: GromacsPTParser.Pairs_nbContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#angles_statement.
    def enterAngles_statement(self, ctx: GromacsPTParser.Angles_statementContext):  # pylint: disable=unused-argument
        self.anglesStatements += 1

    # Exit a parse tree produced by GromacsPTParser#angles_statement.
    def exitAngles_statement(self, ctx: GromacsPTParser.Angles_statementContext):
        if ctx.angles(0):
            return
        self.anglesStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#angles.
    def enterAngles(self, ctx: GromacsPTParser.AnglesContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#angles.
    def exitAngles(self, ctx: GromacsPTParser.AnglesContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#dihedrals_statement.
    def enterDihedrals_statement(self, ctx: GromacsPTParser.Dihedrals_statementContext):  # pylint: disable=unused-argument
        self.dihedralsStatements += 1

    # Exit a parse tree produced by GromacsPTParser#dihedrals_statement.
    def exitDihedrals_statement(self, ctx: GromacsPTParser.Dihedrals_statementContext):
        if ctx.dihedrals(0):
            return
        self.dihedralsStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#dihedrals.
    def enterDihedrals(self, ctx: GromacsPTParser.DihedralsContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#dihedrals.
    def exitDihedrals(self, ctx: GromacsPTParser.DihedralsContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#exclusions_statement.
    def enterExclusions_statement(self, ctx: GromacsPTParser.Exclusions_statementContext):  # pylint: disable=unused-argument
        self.exclusionsStatements += 1

    # Exit a parse tree produced by GromacsPTParser#exclusions_statement.
    def exitExclusions_statement(self, ctx: GromacsPTParser.Exclusions_statementContext):
        if ctx.exclusions(0):
            return
        self.exclusionsStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#exclusions.
    def enterExclusions(self, ctx: GromacsPTParser.ExclusionsContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#exclusions.
    def exitExclusions(self, ctx: GromacsPTParser.ExclusionsContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#constraints_statement.
    def enterConstraints_statement(self, ctx: GromacsPTParser.Constraints_statementContext):  # pylint: disable=unused-argument
        self.constraintsStatements += 1

    # Exit a parse tree produced by GromacsPTParser#constraints_statement.
    def exitConstraints_statement(self, ctx: GromacsPTParser.Constraints_statementContext):
        if ctx.constraints(0):
            return
        self.constraintsStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#constraints.
    def enterConstraints(self, ctx: GromacsPTParser.ConstraintsContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#constraints.
    def exitConstraints(self, ctx: GromacsPTParser.ConstraintsContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#settles_statement.
    def enterSettles_statement(self, ctx: GromacsPTParser.Settles_statementContext):  # pylint: disable=unused-argument
        self.settlesStatements += 1

    # Exit a parse tree produced by GromacsPTParser#settles_statement.
    def exitSettles_statement(self, ctx: GromacsPTParser.Settles_statementContext):
        if ctx.settles(0):
            return
        self.settlesStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#settles.
    def enterSettles(self, ctx: GromacsPTParser.SettlesContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#settles.
    def exitSettles(self, ctx: GromacsPTParser.SettlesContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#virtual_sites1_statement.
    def enterVirtual_sites1_statement(self, ctx: GromacsPTParser.Virtual_sites1_statementContext):  # pylint: disable=unused-argument
        self.virtual_sites1Statements += 1

    # Exit a parse tree produced by GromacsPTParser#virtual_sites1_statement.
    def exitVirtual_sites1_statement(self, ctx: GromacsPTParser.Virtual_sites1_statementContext):
        if ctx.virtual_sites1(0):
            return
        self.virtual_sites1Statements -= 1

    # Enter a parse tree produced by GromacsPTParser#virtual_sites1.
    def enterVirtual_sites1(self, ctx: GromacsPTParser.Virtual_sites1Context):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#virtual_sites1.
    def exitVirtual_sites1(self, ctx: GromacsPTParser.Virtual_sites1Context):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#virtual_sites2_statement.
    def enterVirtual_sites2_statement(self, ctx: GromacsPTParser.Virtual_sites2_statementContext):  # pylint: disable=unused-argument
        self.virtual_sites2Statements += 1

    # Exit a parse tree produced by GromacsPTParser#virtual_sites2_statement.
    def exitVirtual_sites2_statement(self, ctx: GromacsPTParser.Virtual_sites2_statementContext):
        if ctx.virtual_sites2(0):
            return
        self.virtual_sites2Statements -= 1

    # Enter a parse tree produced by GromacsPTParser#virtual_sites2.
    def enterVirtual_sites2(self, ctx: GromacsPTParser.Virtual_sites2Context):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#virtual_sites2.
    def exitVirtual_sites2(self, ctx: GromacsPTParser.Virtual_sites2Context):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#virtual_sites3_statement.
    def enterVirtual_sites3_statement(self, ctx: GromacsPTParser.Virtual_sites3_statementContext):  # pylint: disable=unused-argument
        self.virtual_sites3Statements += 1

    # Exit a parse tree produced by GromacsPTParser#virtual_sites3_statement.
    def exitVirtual_sites3_statement(self, ctx: GromacsPTParser.Virtual_sites3_statementContext):
        if ctx.virtual_sites3(0):
            return
        self.virtual_sites3Statements -= 1

    # Enter a parse tree produced by GromacsPTParser#virtual_sites3.
    def enterVirtual_sites3(self, ctx: GromacsPTParser.Virtual_sites3Context):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#virtual_sites3.
    def exitVirtual_sites3(self, ctx: GromacsPTParser.Virtual_sites3Context):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#virtual_sites4_statement.
    def enterVirtual_sites4_statement(self, ctx: GromacsPTParser.Virtual_sites4_statementContext):  # pylint: disable=unused-argument
        self.virtual_sites4Statements += 1

    # Exit a parse tree produced by GromacsPTParser#virtual_sites4_statement.
    def exitVirtual_sites4_statement(self, ctx: GromacsPTParser.Virtual_sites4_statementContext):
        if ctx.virtual_sites4(0):
            return
        self.virtual_sites4Statements -= 1

    # Enter a parse tree produced by GromacsPTParser#virtual_sites4.
    def enterVirtual_sites4(self, ctx: GromacsPTParser.Virtual_sites4Context):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#virtual_sites4.
    def exitVirtual_sites4(self, ctx: GromacsPTParser.Virtual_sites4Context):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#virtual_sitesn_statement.
    def enterVirtual_sitesn_statement(self, ctx: GromacsPTParser.Virtual_sitesn_statementContext):  # pylint: disable=unused-argument
        self.virtual_sitesnStatements += 1

    # Exit a parse tree produced by GromacsPTParser#virtual_sitesn_statement.
    def exitVirtual_sitesn_statement(self, ctx: GromacsPTParser.Virtual_sitesn_statementContext):
        if ctx.virtual_sitesn(0):
            return
        self.virtual_sitesnStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#virtual_sitesn.
    def enterVirtual_sitesn(self, ctx: GromacsPTParser.Virtual_sitesnContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#virtual_sitesn.
    def exitVirtual_sitesn(self, ctx: GromacsPTParser.Virtual_sitesnContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#system_statement.
    def enterSystem_statement(self, ctx: GromacsPTParser.System_statementContext):  # pylint: disable=unused-argument
        self.systemStatements += 1

    # Exit a parse tree produced by GromacsPTParser#system_statement.
    def exitSystem_statement(self, ctx: GromacsPTParser.System_statementContext):
        if ctx.Simple_name_AA(0):
            title = []
            i = 0
            while ctx.Simple_name_AA(i):
                title.append(str(ctx.Simple_name_AA(i)))
                i += 1

            self.__system = ' '.join(title)
            return
        self.systemStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#molecules_statement.
    def enterMolecules_statement(self, ctx: GromacsPTParser.Molecules_statementContext):  # pylint: disable=unused-argument
        self.moleculesStatements += 1

    # Exit a parse tree produced by GromacsPTParser#molecules_statement.
    def exitMolecules_statement(self, ctx: GromacsPTParser.Molecules_statementContext):
        if ctx.molecules(0):
            return
        self.moleculesStatements -= 1

    # Enter a parse tree produced by GromacsPTParser#molecules.
    def enterMolecules(self, ctx: GromacsPTParser.MoleculesContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#molecules.
    def exitMolecules(self, ctx: GromacsPTParser.MoleculesContext):
        name = str(ctx.Simple_name())
        number = int(str(ctx.Integer()))
        if number > 0:
            self.__molecules.append({'molecule_name': name, 'number_of_copies': number})

    # Enter a parse tree produced by GromacsPTParser#number.
    def enterNumber(self, ctx: GromacsPTParser.NumberContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#number.
    def exitNumber(self, ctx: GromacsPTParser.NumberContext):
        """ not used the 'number' in the '[ atoms ]' statement so that pass through for performance
        if ctx.Real():
            self.numberSelection.append(float(str(ctx.Real())))

        elif ctx.Integer():
            self.numberSelection.append(float(str(ctx.Integer())))

        else:
            self.numberSelection.append(None)
        """

    # Enter a parse tree produced by GromacsPTParser#position_restraints.
    def enterPosition_restraints(self, ctx: GromacsPTParser.Position_restraintsContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#position_restraints.
    def exitPosition_restraints(self, ctx: GromacsPTParser.Position_restraintsContext):  # pylint: disable=unused-argument
        pass

    # Enter a parse tree produced by GromacsPTParser#position_restraint.
    def enterPosition_restraint(self, ctx: GromacsPTParser.Position_restraintContext):  # pylint: disable=unused-argument
        pass

    # Exit a parse tree produced by GromacsPTParser#position_restraint.
    def exitPosition_restraint(self, ctx: GromacsPTParser.Position_restraintContext):  # pylint: disable=unused-argument
        pass

    def getContentSubtype(self):
        """ Return content subtype of GROMACS parameter/topology file.
        """

        contentSubtype = {'default': self.defaultStatements,
                          'moleculetype': self.moleculetypeStatements,
                          'atomtypes': self.atomtypesStatements,
                          'pairtypes': self.pairtypesStatements,
                          'bondtypes': self.bondtypesStatements,
                          'angletypes': self.angletypesStatements,
                          'dihedraltypes': self.dihedraltypesStatements,
                          'constrainttypes': self.constrainttypesStatements,
                          'nonbond_params': self.nonbond_paramsStatements,
                          'atoms': self.atomsStatements,
                          'bonds': self.bondsStatements,
                          'pairs': self.pairsStatements,
                          'pairs_nb': self.pairs_nbStatements,
                          'angles': self.anglesStatements,
                          'dihedrals': self.dihedralsStatements,
                          'exclusions': self.exclusionsStatements,
                          'constraints': self.constraintsStatements,
                          'settles': self.settlesStatements,
                          'virtual_sites1': self.virtual_sites1Statements,
                          'virtual_sites2': self.virtual_sites2Statements,
                          'virtual_sites3': self.virtual_sites3Statements,
                          'virtual_sites4': self.virtual_sites4Statements,
                          'virtual_sitesn': self.virtual_sitesnStatements,
                          'system': self.systemStatements,
                          'molecules': self.moleculesStatements
                          }

        return {k: 1 for k, v in contentSubtype.items() if v > 0}

    def getSystem(self):
        """ Return system name of GROMACS parameter/topology file.
        """
        return self.__system

    def getMolecules(self):
        """ Return list of molecules and its number of copies in GROMACS parameter/topology file.
        """
        return self.__molecules

    def getAtomNumberDict(self):
        """ Return GROMACS atomic number dictionary.
        """
        return self.__atomNumberDict

    def getPolymerSequence(self):
        """ Return polymer sequence of GROMACS parameter/topology file.
        """
        return None if self.__polySeqPrmTop is None or len(self.__polySeqPrmTop) == 0 else self.__polySeqPrmTop

    def getSequenceAlignment(self):
        """ Return sequence alignment between coordinates and GROMACS parameter/topology.
        """
        return None if self.__seqAlign is None or len(self.__seqAlign) == 0 else self.__seqAlign

    def getChainAssignment(self):
        """ Return chain assignment between coordinates and GROMACS parameter/topology.
        """
        return None if self.__chainAssign is None or len(self.__chainAssign) == 0 else self.__chainAssign


# del GromacsPTParser
