##
# File: AmberPTParserListener.py
# Date: 27-Jan-2022
#
# Updates:
""" ParserLister class for AMBER PT files.
    @author: Masashi Yokochi
"""
import sys
import re
import collections
import copy

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.AmberPTParser import AmberPTParser
    from wwpdb.utils.nmr.mr.ParserListenerUtil import (coordAssemblyChecker,
                                                       translateToStdAtomName,
                                                       translateToStdAtomNameOfDmpc,
                                                       translateToStdResName,
                                                       translateToLigandName,
                                                       REPRESENTATIVE_MODEL_ID,
                                                       REPRESENTATIVE_ALT_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,
                                           protonBeginCode,
                                           aminoProtonCode,
                                           isReservedLigCode,
                                           letterToDigit, indexToLetter,
                                           alignPolymerSequence,
                                           assignPolymerSequence,
                                           trimSequenceAlignment,
                                           retrieveAtomIdentFromMRMap,
                                           alignPolymerSequenceWithConflicts,
                                           getRestraintFormatName,
                                           getOneLetterCodeCanSequence)
except ImportError:
    from nmr.align.alignlib import PairwiseAlign  # pylint: disable=no-name-in-module
    from nmr.mr.AmberPTParser import AmberPTParser
    from nmr.mr.ParserListenerUtil import (coordAssemblyChecker,
                                           translateToStdAtomName,
                                           translateToStdAtomNameOfDmpc,
                                           translateToStdResName,
                                           translateToLigandName,
                                           REPRESENTATIVE_MODEL_ID,
                                           REPRESENTATIVE_ALT_ID)
    from nmr.ChemCompUtil import ChemCompUtil
    from nmr.BMRBChemShiftStat import BMRBChemShiftStat
    from nmr.NEFTranslator.NEFTranslator import NEFTranslator
    from nmr.AlignUtil import (monDict3,
                               protonBeginCode,
                               aminoProtonCode,
                               isReservedLigCode,
                               letterToDigit, indexToLetter,
                               alignPolymerSequence,
                               assignPolymerSequence,
                               trimSequenceAlignment,
                               retrieveAtomIdentFromMRMap,
                               alignPolymerSequenceWithConflicts,
                               getRestraintFormatName,
                               getOneLetterCodeCanSequence)


def chunk_string(line, length=4):
    """ Split a string into fixed length chunks.
    """
    return [line[i:i + length] for i in range(0, len(line), length)]


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

    __file_type = 'nm-aux-amb'

    # 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.coordAssemblyChecker()
    __polySeqModel = None
    __nonPolyModel = None
    __branchedModel = None
    __coordAtomSite = None
    __coordUnobsRes = None
    __chemCompAtom = None

    __hasPolySeqModel = False
    __hasNonPolyModel = False
    __hasBranchedModel = False

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

    __seqAlign = None
    __chainAssign = None

    # version information
    __version = None
    __date = None
    __time = None

    # title
    __title = None

    # radius set
    __radiusSet = None

    # residue label
    __residueLabel = None

    # residue_pointer
    __residuePointer = None

    # atom_name
    __atomName = None

    # amber_atom_type
    __amberAtomType = None

    # AMBER atom number dictionary
    __atomNumberDict = None

    # Fortran format
    __a_format_pat = re.compile(r'\((\d+)[aA](\d+)\)$')
    __i_format_pat = re.compile(r'\((\d+)[iI](\d+)\)$')
    __i_format_pat = re.compile(r'\((\d+)[eE](\d+)\.?(\d+)?\)$')

    # __cur_column_len = None
    __cur_word_len = None

    __f = None
    warningMessage = None

    def __init__(self, verbose=True, log=sys.stdout,
                 representativeModelId=REPRESENTATIVE_MODEL_ID,
                 representativeAltId=REPRESENTATIVE_ALT_ID,
                 mrAtomNameMapping=None,
                 cR=None, caC=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 = coordAssemblyChecker(verbose, log, representativeModelId, representativeAltId,
                                       cR, caC, None, fullCheck=True)
            self.__polySeqModel = ret['polymer_sequence']
            self.__nonPolyModel = ret['non_polymer']
            self.__branchedModel = ret['branched']
            self.__coordAtomSite = ret['coord_atom_site']
            self.__coordUnobsRes = ret['coord_unobs_res']
            self.__chemCompAtom = ret['chem_comp_atom']

        self.__hasPolySeqModel = self.__polySeqModel is not None and len(self.__polySeqModel) > 0
        self.__hasNonPolyModel = self.__nonPolyModel is not None and len(self.__nonPolyModel) > 0
        self.__hasBranchedModel = self.__branchedModel is not None and len(self.__branchedModel) > 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
        if nefT is None:
            self.__nefT.set_remediation_mode(True)

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

        self.versionStatements = 0
        self.amberAtomTypeStatements = 0
        self.angleEquilValueStatements = 0
        self.angleForceConstantStatements = 0
        self.anglesIncHydrogenStatements = 0
        self.anglesWithoutHydrogenStatements = 0
        self.atomicNumberStatements = 0
        self.atomNameStatements = 0
        self.atomTypeIndexStatements = 0
        self.atomsPerMoleculeStatements = 0
        self.bondEquilValueStatements = 0
        self.bondForceConstantStatements = 0
        self.bondsIncHydrogenStatements = 0
        self.bondsWithoutHydrogenStatements = 0
        self.boxDimensionsStatements = 0
        self.capInfoStatements = 0
        self.capInfo2Statements = 0
        self.chargeStatements = 0
        self.dihedralForceConstantStatements = 0
        self.dihedralPeriodicityStatements = 0
        self.dihedralPhaseStatements = 0
        self.dihedralsIncHydrogenStatements = 0
        self.dihedralsWithoutHydrogenStatements = 0
        self.excludedAtomsListStatements = 0
        self.hbcutStatements = 0
        self.hbondAcoefStatements = 0
        self.hbondBcoefStatements = 0
        self.ipolStatements = 0
        self.irotatStatements = 0
        self.joinArrayStatements = 0
        self.lennardJonesAcoefStatements = 0
        self.lennardJonesBcoefStatements = 0
        self.massStatements = 0
        self.nonbondedParmIndexStatements = 0
        self.numberExcludedAtomsStatements = 0
        self.pointersStatements = 0
        self.polarizabilityStatements = 0
        self.radiiStatements = 0
        self.radiusSetStatements = 0
        self.residueLabelStatements = 0
        self.residuePointerStatements = 0
        self.sceeScaleFactorStatements = 0
        self.scnbScaleFactorStatements = 0
        self.screenStatements = 0
        self.soltyStatements = 0
        self.solventPointersStatements = 0
        self.titleStatements = 0
        self.treeChainClassificationStatements = 0

    # Enter a parse tree produced by AmberPTParser#amber_pt.
    def enterAmber_pt(self, ctx: AmberPTParser.Amber_ptContext):  # pylint: disable=unused-argument
        self.__atomNumberDict = {}
        self.__polySeqPrmTop = []
        self.__f = []

    # Exit a parse tree produced by AmberPTParser#amber_pt.
    def exitAmber_pt(self, ctx: AmberPTParser.Amber_ptContext):  # pylint: disable=unused-argument

        try:

            if not self.__hasPolySeqModel:
                return

            if self.__residueLabel is None or self.__residuePointer is None or self.__atomName is None or self.__amberAtomType is None:
                return

            if len(self.__residueLabel) == 0 or len(self.__residuePointer) == 0 or len(self.__atomName) == 0 or len(self.__amberAtomType) == 0:
                return

            residuePointer2 = [resPoint - 1 for resPoint in self.__residuePointer]
            del residuePointer2[0]
            residuePointer2.append(self.__residuePointer[-1] + 1000)

            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 = [atomName.endswith('T') for atomName in self.__atomName]

            atomTotal = len(self.__atomName)
            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 = []
            retrievedAtomNumList = []

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

            def is_segment(prev_comp_id, prev_atom_name, comp_id, atom_name):
                if prev_comp_id is None:
                    return False
                is_prev_term_atom = prev_atom_name.endswith('T')
                if is_prev_term_atom and atom_name.endswith('T'):
                    return True
                is_prev_3_prime_comp = prev_comp_id.endswith('3')
                if is_prev_3_prime_comp and (is_prev_term_atom
                                             or self.__csStat.peptideLike(translateToStdResName(comp_id, ccU=self.__ccU))):
                    return True
                return comp_id.endswith('5')\
                    and (is_prev_3_prime_comp
                         or self.__csStat.peptideLike(translateToStdResName(prev_comp_id, ccU=self.__ccU)))

            def is_ligand(prev_comp_id, comp_id):
                if prev_comp_id is None or not self.__hasNonPolyModel:
                    return False
                if not prev_comp_id.endswith('3') or prev_comp_id == comp_id:
                    return False
                for np in self.__nonPolyModel:
                    if comp_id in np['comp_id']:
                        return True
                for np in self.__nonPolyModel:
                    if 'alt_comp_id' in np:
                        if comp_id in np['alt_comp_id']:
                            return True
                return False

            def is_metal_ion(comp_id, atom_name):
                if comp_id is None:
                    return False
                if comp_id != atom_name:
                    return False
                return comp_id.split('+')[0].title() in NAMES_ELEMENT\
                    or comp_id.split('-')[0].title() in NAMES_ELEMENT

            def is_metal_elem(prev_atom_name, prev_seq_id, seq_id):
                if len(prev_atom_name) == 0:
                    return False
                return prev_seq_id != seq_id and prev_atom_name[0] not in NON_METAL_ELEMENTS

            hasSegCompId = False
            ancAtomName = prevAtomName = ''
            prevSeqId = prevCompId = None
            offset = 0
            for atomNum, (atomName, atomType) in enumerate(zip(self.__atomName, self.__amberAtomType), start=1):
                _seqId = next(resNum for resNum, (atomNumBegin, atomNumEnd)
                              in enumerate(zip(self.__residuePointer, residuePointer2), start=1)
                              if atomNumBegin <= atomNum <= atomNumEnd)
                compId = self.__residueLabel[_seqId - 1]
                if not hasSegCompId and (compId.endswith('5') or compId.endswith('3')):
                    hasSegCompId = True
                if not hasSegCompId and compId not in monDict3 and self.__mrAtomNameMapping is not None and atomName[0] in protonBeginCode:
                    _, compId, _atomName = retrieveAtomIdentFromMRMap(self.__ccU, self.__mrAtomNameMapping, _seqId, compId, atomName)
                    if _atomName != atomName:
                        atomName = _atomName
                        retrievedAtomNumList.append(atomNum)

                if (terminus[atomNum - 1] and ancAtomName.endswith('T'))\
                   or is_segment(prevCompId, prevAtomName, compId, atomName)\
                   or is_ligand(prevCompId, compId)\
                   or is_metal_ion(compId, atomName)\
                   or is_metal_ion(prevCompId, prevAtomName)\
                   or is_metal_elem(prevAtomName, 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}
                ancAtomName = prevAtomName
                prevAtomName = atomName
                prevSeqId = _seqId
                prevCompId = compId

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

            nonPolyCompIdList = []
            if self.__hasNonPolyModel:
                for np in self.__nonPolyModel:
                    compId = np['comp_id'][0]
                    if compId not in nonPolyCompIdList:
                        nonPolyCompIdList.append(compId)

            for ps in self.__polySeqPrmTop:
                chainId = ps['chain_id']
                compIdList = []
                for seqId, authCompId in zip(ps['seq_id'], ps['auth_comp_id']):
                    authAtomIds = [translateToStdAtomName(atomNum['auth_atom_id'], atomNum['auth_comp_id'],
                                                          ccU=self.__ccU, unambig=True)
                                   for atomNum in self.__atomNumberDict.values()
                                   if atomNum['chain_id'] == chainId
                                   and atomNum['seq_id'] == seqId
                                   and atomNum['auth_atom_id'][0] not in protonBeginCode]
                    authCompId = translateToStdResName(authCompId, ccU=self.__ccU)
                    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 k, atomNum in self.__atomNumberDict.items():
                                if atomNum['chain_id'] == chainId and atomNum['seq_id'] == seqId:
                                    atomNum['comp_id'] = authCompId

                                    if authCompId in nonPolyCompIdList and self.__mrAtomNameMapping is not None\
                                       and atomNum['auth_atom_id'][0] in protonBeginCode and k not in retrievedAtomNumList:
                                        _, _, atomId = retrieveAtomIdentFromMRMap(self.__ccU, self.__mrAtomNameMapping, None, authCompId, atomNum['auth_atom_id'], None, None, True)
                                    else:
                                        atomId = atomNum['auth_atom_id']

                                    atomId = translateToStdAtomName(atomId, authCompId, chemCompAtomIds, ccU=self.__ccU, unambig=True)

                                    if atomId[0] not in protonBeginCode or atomId in chemCompAtomIds:
                                        atomNum['atom_id'] = atomId
                                        if 'atom_type' in atomNum:
                                            del atomNum['atom_type']
                                    else:
                                        if atomId in chemCompAtomIds:
                                            atomNum['atom_id'] = atomId
                                            if 'atom_type' in atomNum:
                                                del atomNum['atom_type']

                        else:
                            compId = self.__csStat.getSimilarCompIdFromAtomIds([translateToStdAtomName(atomNum['auth_atom_id'],
                                                                                                       atomNum['auth_comp_id'],
                                                                                                       ccU=self.__ccU,
                                                                                                       unambig=True)
                                                                                for atomNum in self.__atomNumberDict.values()
                                                                                if atomNum['chain_id'] == chainId
                                                                                and atomNum['seq_id'] == seqId])

                            if self.__hasNonPolyModel and compId != authCompId:
                                ligands = 0
                                for np in self.__nonPolyModel:
                                    if 'alt_comp_id' in np:
                                        ligands += np['alt_comp_id'].count(authCompId)
                                if ligands > 0:
                                    for np in self.__nonPolyModel:
                                        if authCompId in np['alt_comp_id']:
                                            compId = np['comp_id'][0]
                                            break
                                if ligands == 0:
                                    __compId = None
                                    for np in self.__nonPolyModel:
                                        for ligand in np['comp_id']:
                                            __compId = translateToLigandName(authCompId, ligand, self.__ccU)
                                            if __compId == ligand:
                                                ligands += 1
                                    if ligands == 1:
                                        compId = __compId
                                    elif len(self.__nonPolyModel) == 1 and self.__ccU.updateChemCompDict(authCompId):
                                        if self.__ccU.lastChemCompDict['_chem_comp.pdbx_release_status'] == 'OBS' or isReservedLigCode(authCompId):
                                            compId = self.__nonPolyModel[0]['comp_id'][0]

                            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 k, atomNum in self.__atomNumberDict.items():
                                    if atomNum['chain_id'] == chainId and atomNum['seq_id'] == seqId:
                                        atomNum['comp_id'] = compId

                                        if compId in nonPolyCompIdList and self.__mrAtomNameMapping is not None\
                                           and atomNum['auth_atom_id'][0] in protonBeginCode and k not in retrievedAtomNumList:
                                            _, _, atomId = retrieveAtomIdentFromMRMap(self.__ccU, self.__mrAtomNameMapping, None, compId, atomNum['auth_atom_id'], None, None, True)
                                        else:
                                            atomId = atomNum['auth_atom_id']

                                        atomId = translateToStdAtomName(atomId, compId, chemCompAtomIds, ccU=self.__ccU, unambig=True)

                                        if chemCompAtomIds is not None and atomId in chemCompAtomIds:
                                            atomNum['atom_id'] = atomId
                                            if 'atom_type' in atomNum:
                                                del atomNum['atom_type']
                                        elif chemCompAtomIds is not None:
                                            if atomId in chemCompAtomIds:
                                                atomNum['atom_id'] = atomId
                                                if 'atom_type' in atomNum:
                                                    del atomNum['atom_type']
                            else:
                                compIdList.append('.')
                                unknownAtomIds = [_atomId for _atomId in authAtomIds if _atomId not in chemCompAtomIds]
                                self.__f.append(f"[Unknown atom name] "
                                                f"{unknownAtomIds} are unknown atom names for {authCompId} residue.")
                                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 self.__hasNonPolyModel and compId != authCompId:
                            ligands = 0
                            for np in self.__nonPolyModel:
                                if 'alt_comp_id' in np:
                                    ligands += np['alt_comp_id'].count(authCompId)
                            if ligands == 1:
                                for np in self.__nonPolyModel:
                                    if authCompId in np['alt_comp_id']:
                                        compId = np['comp_id'][0]
                            if ligands == 0:
                                __compId = None
                                for np in self.__nonPolyModel:
                                    for ligand in np['comp_id']:
                                        __compId = translateToLigandName(authCompId, ligand, self.__ccU)
                                        if __compId == ligand:
                                            ligands += 1
                                if ligands == 1:
                                    compId = __compId
                                elif len(self.__nonPolyModel) == 1 and self.__ccU.updateChemCompDict(authCompId):
                                    if self.__ccU.lastChemCompDict['_chem_comp.pdbx_release_status'] == 'OBS' or isReservedLigCode(authCompId):
                                        compId = self.__nonPolyModel[0]['comp_id'][0]

                        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 k, atomNum in self.__atomNumberDict.items():
                                if atomNum['chain_id'] == chainId and atomNum['seq_id'] == seqId:
                                    atomNum['comp_id'] = compId

                                    if compId in nonPolyCompIdList and self.__mrAtomNameMapping is not None\
                                       and atomNum['auth_atom_id'][0] in protonBeginCode and k not in retrievedAtomNumList:
                                        _, _, atomId = retrieveAtomIdentFromMRMap(self.__ccU, self.__mrAtomNameMapping, None, compId, atomNum['auth_atom_id'], None, None, True)
                                    else:
                                        atomId = atomNum['auth_atom_id']

                                    atomId = translateToStdAtomName(atomId, compId, chemCompAtomIds, ccU=self.__ccU, unambig=True)

                                    if chemCompAtomIds is not None and atomId in chemCompAtomIds:
                                        atomNum['atom_id'] = atomId
                                        if 'atom_type' in atomNum:
                                            del atomNum['atom_type']
                                    elif chemCompAtomIds is not None:
                                        if atomId in chemCompAtomIds:
                                            atomNum['atom_id'] = atomId
                                            if 'atom_type' in atomNum:
                                                del atomNum['atom_type']
                        else:
                            compIdList.append('.')
                            """ deferred to assignNonPolymer()
                            self.__f.append(f"[Unknown residue name] "
                                            f"{authCompId!r} is unknown residue name.")
                            """

                ps['comp_id'] = compIdList

            for k, atomNum in self.__atomNumberDict.items():
                if 'atom_type' not in atomNum:
                    continue
                if 'comp_id' in atomNum and atomNum['comp_id'] != atomNum['auth_comp_id']\
                   and 'atom_id' not in atomNum:
                    compId = atomNum['comp_id']
                    if self.__ccU.updateChemCompDict(compId):
                        chemCompAtomIds = [cca[self.__ccU.ccaAtomId] for cca in self.__ccU.lastAtomList]

                        if compId in nonPolyCompIdList and self.__mrAtomNameMapping is not None\
                           and atomNum['auth_atom_id'][0] in protonBeginCode and k not in retrievedAtomNumList:
                            _, _, atomId = retrieveAtomIdentFromMRMap(self.__ccU, self.__mrAtomNameMapping, None, compId, atomNum['auth_atom_id'], None, None, True)
                        else:
                            atomId = atomNum['auth_atom_id']

                        atomId = translateToStdAtomName(atomId, compId, chemCompAtomIds, ccU=self.__ccU, unambig=True)

                        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'], ccU=self.__ccU)
                            if self.__ccU.updateChemCompDict(authCompId):
                                chemCompAtomIds = [cca[self.__ccU.ccaAtomId] for cca in self.__ccU.lastAtomList]

                                if authCompId in nonPolyCompIdList and self.__mrAtomNameMapping is not None\
                                   and atomNum['auth_atom_id'][0] in protonBeginCode and k not in retrievedAtomNumList:
                                    _, _, atomId = retrieveAtomIdentFromMRMap(self.__ccU, self.__mrAtomNameMapping, None, authCompId, atomNum['auth_atom_id'], None, None, True)
                                else:
                                    atomId = atomNum['auth_atom_id']

                                atomId = translateToStdAtomName(atomId, authCompId, chemCompAtomIds, ccU=self.__ccU, unambig=True)

                                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'], ccU=self.__ccU)
                    if self.__ccU.updateChemCompDict(authCompId):

                        if authCompId in nonPolyCompIdList and self.__mrAtomNameMapping is not None\
                           and atomNum['auth_atom_id'][0] in protonBeginCode and k not in retrievedAtomNumList:
                            _, _, atomId = retrieveAtomIdentFromMRMap(self.__ccU, self.__mrAtomNameMapping, None, authCompId, atomNum['auth_atom_id'], None, None, True)
                        else:
                            atomId = atomNum['auth_atom_id']

                        atomId = translateToStdAtomName(atomId, authCompId, ccU=self.__ccU, unambig=True)
                        atomIds = self.__nefT.get_valid_star_atom_in_xplor(authCompId, atomId)[0]
                        if len(atomIds) == 1:
                            atomNum['atom_id'] = atomIds[0]
                            if 'atom_type' in atomNum:
                                del atomNum['atom_type']

            polySeqModel = copy.copy(self.__polySeqModel)
            if self.__hasBranchedModel:
                polySeqModel.extend(self.__branchedModel)

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

            if len(self.__seqAlign) == 0:
                for c in range(1, 5):
                    self.__seqAlign, compIdMapping = alignPolymerSequenceWithConflicts(self.__pA, polySeqModel, self.__polySeqPrmTop, c)
                    if len(self.__seqAlign) > 0:
                        break

            if len(self.__seqAlign) == 0:
                len_cif_na = sum(len(ps_cif['seq_id']) for ps_cif in polySeqModel if 'identical_chain_id' in ps_cif and len(ps_cif['seq_id']) > 3)
                len_top_na = sum(len(ps_top['seq_id']) for ps_top in self.__polySeqPrmTop
                                 if len(ps_top['seq_id']) > 3 and any(compId in ('DA?', 'DT?', 'DG?', 'DC?', 'A?', 'U?', 'G?', 'C?') for compId in ps_top['comp_id']))
                if len_cif_na == len_top_na:
                    chainIdList = []
                    seqIdList = []
                    authCompIdList = []
                    for ps_top in self.__polySeqPrmTop:
                        len_ps_cif_seq = len(ps_top['seq_id'])
                        if len_ps_cif_seq > 3 and any(compId in ('DA?', 'DT?', 'DG?', 'DC?', 'A?', 'U?', 'G?', 'C?') for compId in ps_top['comp_id']):
                            chainId = ps_top['chain_id']
                            for seqId, compId in zip(ps_top['seq_id'], ps_top['auth_comp_id']):
                                chainIdList.append(chainId)
                                seqIdList.append(seqId)
                                authCompIdList.append(compId)

                    chainIndex = letterToDigit(self.__polySeqModel[0]['chain_id']) - 1
                    idOffset = 0

                    touched = []

                    polySeqPrmTop = []
                    for ps_cif in polySeqModel:
                        len_ps_cif_seq = len(ps_cif['seq_id'])
                        if 'identical_chain_id' in ps_cif and len_ps_cif_seq > 3:
                            chainId = indexToLetter(chainIndex)
                            polySeqPrmTop.append({'chain_id': chainId,
                                                  'seq_id': seqIdList[idOffset:idOffset + len_ps_cif_seq],
                                                  'comp_id': ps_cif['comp_id'],
                                                  'auth_comp_id': authCompIdList[idOffset:idOffset + len_ps_cif_seq]})

                            for idx, (_chainId, _seqId) in enumerate(zip(chainIdList[idOffset:idOffset + len_ps_cif_seq],
                                                                         seqIdList[idOffset:idOffset + len_ps_cif_seq])):
                                for k, atomNum in self.__atomNumberDict.items():
                                    if atomNum['chain_id'] == _chainId and atomNum['seq_id'] == _seqId:
                                        atomNum['chain_id'] = chainId
                                        atomNum['cif_comp_id'] = ps_cif['comp_id'][idx]
                                        touched.append(k)

                            idOffset += len_ps_cif_seq
                            chainIndex += 1

                    for ps_top in self.__polySeqPrmTop:
                        if len(ps_top['seq_id']) > 3 and any(compId in ('DA?', 'DT?', 'DG?', 'DC?', 'A?', 'U?', 'G?', 'C?') for compId in ps_top['comp_id']):
                            continue
                        _chainId = copy.copy(ps_top['chain_id'])
                        chainId = indexToLetter(chainIndex)
                        ps_top['chain_id'] = chainId
                        polySeqPrmTop.append(ps_top)

                        for k, atomNum in self.__atomNumberDict.items():
                            if k in touched:
                                continue
                            if atomNum['chain_id'] == _chainId:
                                atomNum['chain_id'] = chainId
                                touched.append(k)

                        chainIndex += 1

                    self.__polySeqPrmTop = polySeqPrmTop

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

                    _seqAlign = copy.copy(self.__seqAlign)
                    for sa in _seqAlign:
                        if sa['ref_chain_id'] != sa['test_chain_id']:
                            self.__seqAlign.remove(sa)

            # test chain assignment before applying comp_id mapping
            self.__chainAssign, message = assignPolymerSequence(self.__pA, self.__ccU, self.__file_type, self.__polySeqModel, self.__polySeqPrmTop, self.__seqAlign)

            for cmap in compIdMapping:
                if any(ca for ca in self.__chainAssign if ca['test_chain_id'] == cmap['chain_id']):
                    for k, atomNum in self.__atomNumberDict.items():
                        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']
                            if 'atom_type' in atomNum:
                                authCompId = cmap['auth_comp_id']
                                if self.__ccU.updateChemCompDict(authCompId):
                                    chemCompAtomIds = [cca[self.__ccU.ccaAtomId] for cca in self.__ccU.lastAtomList]

                                    if authCompId in nonPolyCompIdList and self.__mrAtomNameMapping is not None\
                                       and atomNum['auth_atom_id'][0] in protonBeginCode and k not in retrievedAtomNumList:
                                        _, _, atomId = retrieveAtomIdentFromMRMap(self.__ccU, self.__mrAtomNameMapping, None, authCompId, atomNum['auth_atom_id'],
                                                                                  atomNum['comp_id'], None, True)
                                    else:
                                        atomId = atomNum['auth_atom_id']

                                    atomNum['atom_id'] = translateToStdAtomName(atomId, authCompId, chemCompAtomIds, ccU=self.__ccU, unambig=True)
                                    del atomNum['atom_type']

            for k, atomNum in self.__atomNumberDict.items():
                if 'atom_type' not in atomNum:
                    continue
                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'], ccU=self.__ccU)

                        if self.__mrAtomNameMapping is not None\
                           and atomNum['auth_atom_id'][0] in protonBeginCode and k not in retrievedAtomNumList:
                            _, _, atomId = retrieveAtomIdentFromMRMap(self.__ccU, self.__mrAtomNameMapping, None, authCompId, atomNum['auth_atom_id'], None, None, True)
                        else:
                            atomId = atomNum['auth_atom_id']

                        if self.__ccU.updateChemCompDict(authCompId):
                            chemCompAtomIds = [cca[self.__ccU.ccaAtomId] for cca in self.__ccU.lastAtomList]
                            atomId = translateToStdAtomName(atomId, authCompId, chemCompAtomIds, ccU=self.__ccU, unambig=True)
                            if atomId in chemCompAtomIds:
                                atomNum['atom_id'] = atomId
                                continue
                            if self.__chemCompAtom is not None:
                                if 'comp_id' in atomNum and atomNum['comp_id'] in self.__chemCompAtom:
                                    if atomId in self.__chemCompAtom[atomNum['comp_id']]:
                                        atomNum['atom_id'] = atomId
                                        continue
                                if 'cif_comp_id' in atomNum and atomNum['cif_comp_id'] in self.__chemCompAtom:
                                    if atomId in self.__chemCompAtom[atomNum['cif_comp_id']]:
                                        atomNum['atom_id'] = atomId
                                        continue
                            if atomId == "HO5'" and atomNum['seq_id'] == 1 and self.__csStat.getTypeOfCompId(atomNum['auth_comp_id'])[1]:
                                continue
                            self.__f.append(f"[Unknown atom name] "
                                            f"{atomNum['auth_atom_id']!r} is not recognized as the atom name of {atomNum['auth_comp_id']!r} residue.")
                        elif self.__chemCompAtom is not None:
                            if 'comp_id' in atomNum and atomNum['comp_id'] in self.__chemCompAtom:
                                if atomId in self.__chemCompAtom[atomNum['comp_id']]:
                                    atomNum['atom_id'] = atomId
                                    continue
                            if 'cif_comp_id' in atomNum and atomNum['cif_comp_id'] in self.__chemCompAtom:
                                if atomId in self.__chemCompAtom[atomNum['cif_comp_id']]:
                                    atomNum['atom_id'] = atomId
                                    continue

                    else:
                        authCompId = translateToStdResName(atomNum['auth_comp_id'], ccU=self.__ccU)
                        if self.__mrAtomNameMapping is not None\
                           and atomNum['auth_atom_id'][0] in protonBeginCode and k not in retrievedAtomNumList:
                            _, _, atomId = retrieveAtomIdentFromMRMap(self.__ccU, self.__mrAtomNameMapping, None, authCompId, atomNum['auth_atom_id'],
                                                                      atomNum['comp_id'], None, True)
                        else:
                            atomId = atomNum['auth_atom_id']

                        if self.__ccU.updateChemCompDict(authCompId):
                            chemCompAtomIds = [cca[self.__ccU.ccaAtomId] for cca in self.__ccU.lastAtomList]
                            atomId = translateToStdAtomName(atomId, authCompId, chemCompAtomIds, ccU=self.__ccU, unambig=True)
                            if atomId in chemCompAtomIds:
                                atomNum['atom_id'] = atomId
                                continue
                            if self.__chemCompAtom is not None:
                                if 'comp_id' in atomNum and atomNum['comp_id'] in self.__chemCompAtom:
                                    if atomId in self.__chemCompAtom[atomNum['comp_id']]:
                                        atomNum['atom_id'] = atomId
                                        continue
                                if 'cif_comp_id' in atomNum and atomNum['cif_comp_id'] in self.__chemCompAtom:
                                    if atomId in self.__chemCompAtom[atomNum['cif_comp_id']]:
                                        atomNum['atom_id'] = atomId
                                        continue
                            atomNum['atom_id'] = atomNum['auth_atom_id']
                            if atomNum['atom_id'] == "HO5'" and atomNum['seq_id'] == 1 and self.__csStat.getTypeOfCompId(atomNum['comp_id'])[1]:
                                continue
                            self.__f.append(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}).")
                        elif self.__chemCompAtom is not None:
                            if 'comp_id' in atomNum and atomNum['comp_id'] in self.__chemCompAtom:
                                if atomId in self.__chemCompAtom[atomNum['comp_id']]:
                                    atomNum['atom_id'] = atomId
                                    continue
                            if 'cif_comp_id' in atomNum and atomNum['cif_comp_id'] in self.__chemCompAtom:
                                if atomId in self.__chemCompAtom[atomNum['cif_comp_id']]:
                                    atomNum['atom_id'] = atomId
                                    continue

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

            if len(message) > 0:
                self.__f.extend(message)

            if len(self.__seqAlign) == 0:
                mrFormatName = getRestraintFormatName(self.__file_type)
                _a_mr_format_name = 'the ' + mrFormatName

                ref_code = getOneLetterCodeCanSequence(self.__polySeqModel[0]['comp_id'])
                test_code = getOneLetterCodeCanSequence(self.__polySeqPrmTop[0]['comp_id'])

                hint = ''
                if abs(len(ref_code) - len(test_code)) < 20 and len(ref_code) > 40:
                    hint = f"For example, coordinates ({self.__polySeqModel[0]['auth_chain_id']}): {ref_code} vs topology: {test_code}. "

                self.__f.append(f"[Sequence mismatch] Polymer sequence between the coordinate and {_a_mr_format_name} data does not match. {hint}"
                                "Please verify the two sequences and re-upload the correct file(s) if required.")

            assi_ref_chain_ids = {}
            proc_test_chain_ids = []
            atom_nums = []
            delete_atom_nums = []

            def update_atom_num(seq_align, orphan):
                ref_chain_id = seq_align['ref_chain_id']
                test_chain_id = seq_align['test_chain_id']

                if ref_chain_id in assi_ref_chain_ids or test_chain_id in proc_test_chain_ids:
                    return

                ps_cif = next(ps for ps in self.__polySeqModel if ps['auth_chain_id'] == ref_chain_id)

                if ref_chain_id not in assi_ref_chain_ids:
                    assi_ref_chain_ids[ref_chain_id] = seq_align['length'] - seq_align['matched'] - seq_align['conflict']
                else:
                    assi_ref_chain_ids[ref_chain_id] -= seq_align['matched'] + seq_align['conflict']
                proc_test_chain_ids.append(test_chain_id)

                offset = first_seq_id = None

                for atom_num, atomNum in self.__atomNumberDict.items():
                    if atom_num in atom_nums:
                        continue
                    if atomNum['chain_id'] == test_chain_id:
                        atom_nums.append(atom_num)

                        test_seq_id = atomNum['seq_id']

                        if first_seq_id is None:
                            first_seq_id = test_seq_id

                        if test_seq_id in seq_align['test_seq_id']:
                            idx = seq_align['test_seq_id'].index(test_seq_id)
                            if 'ref_auth_seq_id' in seq_align and idx < len(seq_align['ref_auth_seq_id']):
                                ref_seq_id = seq_align['ref_auth_seq_id'][idx]
                            elif offset is not None:
                                ref_seq_id = test_seq_id + offset
                            else:
                                continue
                        elif offset is not None:
                            ref_seq_id = test_seq_id + offset
                        else:
                            continue

                        if offset is None:
                            offset = ref_seq_id - test_seq_id

                        atomNum['chain_id'] = ref_chain_id
                        atomNum['seq_id'] = ref_seq_id

                        if ref_seq_id in ps_cif['auth_seq_id']:
                            idx = ps_cif['auth_seq_id'].index(ref_seq_id)
                            atomNum['comp_id'] = ps_cif['comp_id'][idx]

                        if orphan and test_seq_id == first_seq_id\
                           and self.__csStat.peptideLike(translateToStdResName(atomNum['comp_id'], ccU=self.__ccU)):
                            if self.__ccU.updateChemCompDict(atomNum['comp_id']):
                                chemCompAtomIds = [cca[self.__ccU.ccaAtomId] for cca in self.__ccU.lastAtomList]
                                leavingAtomIds = [cca[self.__ccU.ccaAtomId] for cca in self.__ccU.lastAtomList
                                                  if cca[self.__ccU.ccaLeavingAtomFlag] == 'Y']
                                if atomNum['atom_id'] not in chemCompAtomIds or atomNum['atom_id'] in leavingAtomIds:
                                    delete_atom_nums.append(atom_num)

            while True:

                orphanPolySeqPrmTop = []

                for ps in self.__polySeqPrmTop:
                    test_chain_id = ps['chain_id']
                    if test_chain_id in proc_test_chain_ids:
                        continue
                    try:
                        ca = next(ca for ca in self.__chainAssign if ca['test_chain_id'] == test_chain_id)

                        ref_chain_id = ca['ref_chain_id']
                        sa = next((sa for sa in self.__seqAlign
                                   if sa['ref_chain_id'] == ref_chain_id and sa['test_chain_id'] == test_chain_id), None)

                        if sa is not None:  # and sa['conflict'] == 0:
                            update_atom_num(sa, False)

                    except StopIteration:
                        orphanPolySeqPrmTop.append(ps)

                resolved = False

                if len(orphanPolySeqPrmTop) > 0:
                    max_length = max(len(ps['seq_id']) for ps in orphanPolySeqPrmTop)
                    __polySeqModel__ = [ps for ps in self.__polySeqModel
                                        if ps['auth_chain_id'] not in assi_ref_chain_ids
                                        or assi_ref_chain_ids[ps['auth_chain_id']] >= max_length]
                    __seqAlign__, _ = alignPolymerSequence(self.__pA, __polySeqModel__, orphanPolySeqPrmTop)
                    if len(__seqAlign__) > 0:
                        for sa in __seqAlign__:
                            if sa['conflict'] == 0:
                                update_atom_num(sa, True)

                                resolved = True

                    if not resolved:
                        for c in range(1, 5):
                            __seqAlign__, _ = alignPolymerSequenceWithConflicts(self.__pA, __polySeqModel__, orphanPolySeqPrmTop, c)
                            if len(__seqAlign__) > 0:
                                for sa in __seqAlign__:
                                    if sa['conflict'] <= c:
                                        update_atom_num(sa, True)

                                        resolved = True
                            if resolved:
                                break

                if not resolved:
                    break

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

                if test_chain_id in proc_test_chain_ids:
                    continue

                for cif_ps in self.__polySeqModel:
                    ref_chain_id = cif_ps['auth_chain_id']

                    if ref_chain_id in assi_ref_chain_ids:
                        continue

                    len_gap = abs(len(ps['seq_id']) - len(cif_ps['auth_seq_id']))

                    if len_gap > 20:
                        continue

                    if len_gap == 0:
                        offset = cif_ps['auth_seq_id'][0] - ps['seq_id'][0]

                    for atomNum in self.__atomNumberDict.values():
                        if atomNum['chain_id'] == test_chain_id:
                            atomNum['chain_id'] = ref_chain_id
                            if len_gap == 0:
                                atomNum['seq_id'] += offset

                    proc_test_chain_ids.append(test_chain_id)
                    assi_ref_chain_ids[ref_chain_id] = len_gap

            if len(delete_atom_nums) > 0:
                for atom_num in sorted(delete_atom_nums, reverse=True):
                    del self.__atomNumberDict[atom_num]

            for atomNum in self.__atomNumberDict.values():
                if 'atom_id' in atomNum and atomNum['atom_id'] in aminoProtonCode:
                    _seqKey = (atomNum['chain_id'], atomNum['seq_id'] - 1)
                    seqKey = (atomNum['chain_id'], atomNum['seq_id'])
                    if _seqKey in self.__coordUnobsRes and seqKey in self.__coordAtomSite:
                        coordAtomSite = self.__coordAtomSite[seqKey]
                        if atomNum['atom_id'] not in coordAtomSite['atom_id']:
                            for atomId in aminoProtonCode:
                                if atomId in coordAtomSite['atom_id']:
                                    atomNum['atom_id'] = atomId
                                    break

            if self.__chainAssign is not None:
                trimSequenceAlignment(self.__seqAlign, self.__chainAssign)

                if self.__hasNonPolyModel:

                    # 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'])) > 0 and ps['comp_id'][0] == '.']

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

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

            if self.__hasNonPolyModel:
                compIdMapping = {}
                mappedSeqVal = []
                mappedAtomNum = []

                for np in self.__nonPolyModel:
                    authChainId = np['auth_chain_id']
                    authSeqId = np['auth_seq_id'][0]
                    compId = np['comp_id'][0]

                    for k, v in self.__atomNumberDict.items():
                        if k in mappedAtomNum:
                            continue
                        if 'comp_id' in v and v['comp_id'] == compId:
                            seqKey = (v['comp_id'], v['chain_id'], v['seq_id'])
                            seqVal = (authChainId, authSeqId)
                            if seqKey not in compIdMapping:
                                if seqVal not in mappedSeqVal:
                                    compIdMapping[seqKey] = seqVal
                            if seqKey in compIdMapping:
                                v['chain_id'], v['seq_id'] = compIdMapping[seqKey]
                                mappedSeqVal.append(seqVal)
                                mappedAtomNum.append(k)

            if any(f for f in message if '[Concatenated sequence]' in f):  # DAOTHER-9511: resolve concatenated sequence
                test_chain_id_map = {}
                for ca in self.__chainAssign:
                    ref_chain_id = ca['ref_chain_id']
                    test_chain_id = ca['test_chain_id']
                    if test_chain_id not in test_chain_id_map:
                        test_chain_id_map[test_chain_id] = []
                    test_chain_id_map[test_chain_id].append(ref_chain_id)

                _test_chain_id_map = copy.copy(test_chain_id_map)
                for test_chain_id, cmap in _test_chain_id_map.items():
                    if len(cmap) < 2:
                        del test_chain_id_map[test_chain_id]

                if len(test_chain_id_map) > 0:
                    cmap = {}
                    for test_chain_id, ref_chain_ids in test_chain_id_map.items():
                        ref_chain_id0 = ref_chain_ids[0]
                        for ref_chain_id in ref_chain_ids[1:]:
                            sa = next((sa for sa in self.__seqAlign if sa['ref_chain_id'] == ref_chain_id and sa['test_chain_id'] == test_chain_id), None)
                            if sa is None:
                                continue
                            ps = next((ps for ps in self.__polySeqModel if ps['auth_chain_id'] == ref_chain_id), None)
                            if ps is None:
                                continue
                            for auth_seq_id, comp_id in zip(ps['auth_seq_id'], ps['comp_id']):
                                seq_key = (ref_chain_id0, auth_seq_id)
                                cmap[seq_key] = (ref_chain_id, comp_id)
                    if len(cmap) > 0:
                        for atomNum in self.__atomNumberDict.values():
                            seq_key = (atomNum['chain_id'], atomNum['seq_id'])
                            if seq_key in cmap:
                                atomNum['chain_id'], atomNum['comp_id'] = cmap[seq_key]

        finally:
            self.warningMessage = sorted(list(set(self.__f)), key=self.__f.index)

    def assignMetalIon(self):
        if not self.__hasNonPolyModel:
            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 not self.__hasNonPolyModel:
            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.__f.append(f"[Unknown residue name] "
                                f"{authCompId[0]!r} is unknown residue name.")
                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]]
                authAtomNames = [atomNum['auth_atom_id'] 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:
                        dmpcNameSystemId = -1
                        if compId == 'PX4':
                            if 'OE' in authAtomNames:
                                dmpcNameSystemId = 1
                            elif 'OS31' in authAtomNames:
                                dmpcNameSystemId = 2
                            elif 'O21' in authAtomNames:
                                if 'C314' in authAtomNames:
                                    dmpcNameSystemId = 3
                                elif 'C114' in authAtomNames:
                                    dmpcNameSystemId = 4

                        if dmpcNameSystemId != -1:
                            atomId = translateToStdAtomNameOfDmpc(authAtomId, dmpcNameSystemId)
                        else:
                            atomId = translateToStdAtomName(authAtomId, compId, chemCompAtomIds, ccU=self.__ccU)

                        if atomId in chemCompAtomIds:
                            atomNum['atom_id'] = atomId
                        else:
                            _, _, atomId = retrieveAtomIdentFromMRMap(self.__ccU, self.__mrAtomNameMapping, None, compId, authAtomId, None, None, True)

                            if atomId in chemCompAtomIds:
                                atomNum['atom_id'] = atomId
                                continue
                            if authAtomId not in reported_auth_atom_id:
                                atomNum['atom_id'] = atomNum['auth_atom_id']
                                if authAtomId == "HO5'" and atomNum['seq_id'] == 1 and self.__csStat.getTypeOfCompId(compId)[1]:
                                    continue
                                self.__f.append(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}).")
                                reported_auth_atom_id.append(authAtomId)

    # Enter a parse tree produced by AmberPTParser#version_statement.
    def enterVersion_statement(self, ctx: AmberPTParser.Version_statementContext):  # pylint: disable=unused-argument
        self.versionStatements += 1

    # Exit a parse tree produced by AmberPTParser#version_statement.
    def exitVersion_statement(self, ctx: AmberPTParser.Version_statementContext):
        self.__version = str(ctx.Version())
        self.__date = str(ctx.Date_time(0))
        if ctx.Date_time(0):
            self.__time = str(ctx.Date_time(1))

    # Enter a parse tree produced by AmberPTParser#amber_atom_type_statement.
    def enterAmber_atom_type_statement(self, ctx: AmberPTParser.Amber_atom_type_statementContext):  # pylint: disable=unused-argument
        self.amberAtomTypeStatements += 1

    # Exit a parse tree produced by AmberPTParser#amber_atom_type_statement.
    def exitAmber_atom_type_statement(self, ctx: AmberPTParser.Amber_atom_type_statementContext):
        if ctx.Simple_name(0):
            atomTypeList = []
            i = 0
            while ctx.Simple_name(i):
                chunk = chunk_string(str(ctx.Simple_name(i)).upper(), self.__cur_word_len)
                atomTypeList.extend(chunk)
                i += 1
            self.__amberAtomType = atomTypeList
            return
        self.amberAtomTypeStatements -= 1

    # Enter a parse tree produced by AmberPTParser#angle_equil_value_statement.
    def enterAngle_equil_value_statement(self, ctx: AmberPTParser.Angle_equil_value_statementContext):  # pylint: disable=unused-argument
        self.angleEquilValueStatements += 1

    # Exit a parse tree produced by AmberPTParser#angle_equil_value_statement.
    def exitAngle_equil_value_statement(self, ctx: AmberPTParser.Angle_equil_value_statementContext):
        if ctx.Real(0):
            return
        self.angleEquilValueStatements -= 1

    # Enter a parse tree produced by AmberPTParser#angle_force_constant_statement.
    def enterAngle_force_constant_statement(self, ctx: AmberPTParser.Angle_force_constant_statementContext):  # pylint: disable=unused-argument
        self.angleForceConstantStatements += 1

    # Exit a parse tree produced by AmberPTParser#angle_force_constant_statement.
    def exitAngle_force_constant_statement(self, ctx: AmberPTParser.Angle_force_constant_statementContext):
        if ctx.Real(0):
            return
        self.angleForceConstantStatements -= 1

    # Enter a parse tree produced by AmberPTParser#angles_inc_hydrogen_statement.
    def enterAngles_inc_hydrogen_statement(self, ctx: AmberPTParser.Angles_inc_hydrogen_statementContext):  # pylint: disable=unused-argument
        self.anglesIncHydrogenStatements += 1

    # Exit a parse tree produced by AmberPTParser#angles_inc_hydrogen_statement.
    def exitAngles_inc_hydrogen_statement(self, ctx: AmberPTParser.Angles_inc_hydrogen_statementContext):
        if ctx.Integer(0):
            return
        self.anglesIncHydrogenStatements -= 1

    # Enter a parse tree produced by AmberPTParser#angles_without_hydrogen_statement.
    def enterAngles_without_hydrogen_statement(self, ctx: AmberPTParser.Angles_without_hydrogen_statementContext):  # pylint: disable=unused-argument
        self.anglesWithoutHydrogenStatements += 1

    # Exit a parse tree produced by AmberPTParser#angles_without_hydrogen_statement.
    def exitAngles_without_hydrogen_statement(self, ctx: AmberPTParser.Angles_without_hydrogen_statementContext):
        if ctx.Integer(0):
            return
        self.anglesWithoutHydrogenStatements -= 1

    # Enter a parse tree produced by AmberPTParser#atomic_number_statement.
    def enterAtomic_number_statement(self, ctx: AmberPTParser.Atomic_number_statementContext):  # pylint: disable=unused-argument
        self.atomicNumberStatements += 1

    # Exit a parse tree produced by AmberPTParser#atomic_number_statement.
    def exitAtomic_number_statement(self, ctx: AmberPTParser.Atomic_number_statementContext):
        if ctx.Integer(0):
            return
        self.atomicNumberStatements -= 1

    # Enter a parse tree produced by AmberPTParser#atom_name_statement.
    def enterAtom_name_statement(self, ctx: AmberPTParser.Atom_name_statementContext):  # pylint: disable=unused-argument
        self.atomNameStatements += 1

    # Exit a parse tree produced by AmberPTParser#atom_name_statement.
    def exitAtom_name_statement(self, ctx: AmberPTParser.Atom_name_statementContext):
        if ctx.Simple_name(0):
            atomIdList = []
            i = 0
            while ctx.Simple_name(i):
                chunk = chunk_string(str(ctx.Simple_name(i)).upper(), self.__cur_word_len)
                atomIdList.extend(chunk)
                i += 1
            self.__atomName = atomIdList
            return
        self.atomNameStatements -= 1

    # Enter a parse tree produced by AmberPTParser#atom_type_index_statement.
    def enterAtom_type_index_statement(self, ctx: AmberPTParser.Atom_type_index_statementContext):  # pylint: disable=unused-argument
        self.atomTypeIndexStatements += 1

    # Exit a parse tree produced by AmberPTParser#atom_type_index_statement.
    def exitAtom_type_index_statement(self, ctx: AmberPTParser.Atom_type_index_statementContext):
        if ctx.Integer(0):
            return
        self.atomTypeIndexStatements += 1

    # Enter a parse tree produced by AmberPTParser#atoms_per_molecule_statement.
    def enterAtoms_per_molecule_statement(self, ctx: AmberPTParser.Atoms_per_molecule_statementContext):  # pylint: disable=unused-argument
        self.atomsPerMoleculeStatements += 1

    # Exit a parse tree produced by AmberPTParser#atoms_per_molecule_statement.
    def exitAtoms_per_molecule_statement(self, ctx: AmberPTParser.Atoms_per_molecule_statementContext):
        if ctx.Integer(0):
            return
        self.atomsPerMoleculeStatements -= 1

    # Enter a parse tree produced by AmberPTParser#bond_equil_value_statement.
    def enterBond_equil_value_statement(self, ctx: AmberPTParser.Bond_equil_value_statementContext):  # pylint: disable=unused-argument
        self.bondEquilValueStatements += 1

    # Exit a parse tree produced by AmberPTParser#bond_equil_value_statement.
    def exitBond_equil_value_statement(self, ctx: AmberPTParser.Bond_equil_value_statementContext):
        if ctx.Real(0):
            return
        self.bondEquilValueStatements -= 1

    # Enter a parse tree produced by AmberPTParser#bond_force_constant_statement.
    def enterBond_force_constant_statement(self, ctx: AmberPTParser.Bond_force_constant_statementContext):  # pylint: disable=unused-argument
        self.bondForceConstantStatements += 1

    # Exit a parse tree produced by AmberPTParser#bond_force_constant_statement.
    def exitBond_force_constant_statement(self, ctx: AmberPTParser.Bond_force_constant_statementContext):
        if ctx.Real(0):
            return
        self.bondForceConstantStatements -= 1

    # Enter a parse tree produced by AmberPTParser#bonds_inc_hydrogen_statement.
    def enterBonds_inc_hydrogen_statement(self, ctx: AmberPTParser.Bonds_inc_hydrogen_statementContext):  # pylint: disable=unused-argument
        self.bondsIncHydrogenStatements += 1

    # Exit a parse tree produced by AmberPTParser#bonds_inc_hydrogen_statement.
    def exitBonds_inc_hydrogen_statement(self, ctx: AmberPTParser.Bonds_inc_hydrogen_statementContext):
        if ctx.Integer(0):
            return
        self.bondsIncHydrogenStatements -= 1

    # Enter a parse tree produced by AmberPTParser#bonds_without_hydrogen_statement.
    def enterBonds_without_hydrogen_statement(self, ctx: AmberPTParser.Bonds_without_hydrogen_statementContext):  # pylint: disable=unused-argument
        self.bondsWithoutHydrogenStatements += 1

    # Exit a parse tree produced by AmberPTParser#bonds_without_hydrogen_statement.
    def exitBonds_without_hydrogen_statement(self, ctx: AmberPTParser.Bonds_without_hydrogen_statementContext):
        if ctx.Integer(0):
            return
        self.bondsWithoutHydrogenStatements -= 1

    # Enter a parse tree produced by AmberPTParser#box_dimensions_statement.
    def enterBox_dimensions_statement(self, ctx: AmberPTParser.Box_dimensions_statementContext):  # pylint: disable=unused-argument
        self.boxDimensionsStatements += 1

    # Exit a parse tree produced by AmberPTParser#box_dimensions_statement.
    def exitBox_dimensions_statement(self, ctx: AmberPTParser.Box_dimensions_statementContext):
        if ctx.Real(0):
            return
        self.boxDimensionsStatements -= 1

    # Enter a parse tree produced by AmberPTParser#cap_info_statement.
    def enterCap_info_statement(self, ctx: AmberPTParser.Cap_info_statementContext):  # pylint: disable=unused-argument
        self.capInfoStatements += 1

    # Exit a parse tree produced by AmberPTParser#cap_info_statement.
    def exitCap_info_statement(self, ctx: AmberPTParser.Cap_info_statementContext):
        if ctx.Integer(0):
            return
        self.capInfoStatements -= 1

    # Enter a parse tree produced by AmberPTParser#cap_info2_statement.
    def enterCap_info2_statement(self, ctx: AmberPTParser.Cap_info2_statementContext):  # pylint: disable=unused-argument
        self.capInfo2Statements += 1

    # Exit a parse tree produced by AmberPTParser#cap_info2_statement.
    def exitCap_info2_statement(self, ctx: AmberPTParser.Cap_info2_statementContext):
        if ctx.Real(0):
            return
        self.capInfo2Statements -= 1

    # Enter a parse tree produced by AmberPTParser#charge_statement.
    def enterCharge_statement(self, ctx: AmberPTParser.Charge_statementContext):  # pylint: disable=unused-argument
        self.chargeStatements += 1

    # Exit a parse tree produced by AmberPTParser#charge_statement.
    def exitCharge_statement(self, ctx: AmberPTParser.Charge_statementContext):
        if ctx.Real(0):
            return
        self.chargeStatements -= 1

    # Enter a parse tree produced by AmberPTParser#dihedral_force_constant_statement.
    def enterDihedral_force_constant_statement(self, ctx: AmberPTParser.Dihedral_force_constant_statementContext):  # pylint: disable=unused-argument
        self.dihedralForceConstantStatements += 1

    # Exit a parse tree produced by AmberPTParser#dihedral_force_constant_statement.
    def exitDihedral_force_constant_statement(self, ctx: AmberPTParser.Dihedral_force_constant_statementContext):
        if ctx.Real(0):
            return
        self.dihedralForceConstantStatements -= 1

    # Enter a parse tree produced by AmberPTParser#dihedral_periodicity_statement.
    def enterDihedral_periodicity_statement(self, ctx: AmberPTParser.Dihedral_periodicity_statementContext):  # pylint: disable=unused-argument
        self.dihedralPeriodicityStatements += 1

    # Exit a parse tree produced by AmberPTParser#dihedral_periodicity_statement.
    def exitDihedral_periodicity_statement(self, ctx: AmberPTParser.Dihedral_periodicity_statementContext):
        if ctx.Real(0):
            return
        self.dihedralPeriodicityStatements -= 1

    # Enter a parse tree produced by AmberPTParser#dihedral_phase_statement.
    def enterDihedral_phase_statement(self, ctx: AmberPTParser.Dihedral_phase_statementContext):  # pylint: disable=unused-argument
        self.dihedralPhaseStatements += 1

    # Exit a parse tree produced by AmberPTParser#dihedral_phase_statement.
    def exitDihedral_phase_statement(self, ctx: AmberPTParser.Dihedral_phase_statementContext):
        if ctx.Real(0):
            return
        self.dihedralPhaseStatements -= 1

    # Enter a parse tree produced by AmberPTParser#dihedrals_inc_hydrogen_statement.
    def enterDihedrals_inc_hydrogen_statement(self, ctx: AmberPTParser.Dihedrals_inc_hydrogen_statementContext):  # pylint: disable=unused-argument
        self.dihedralsIncHydrogenStatements += 1

    # Exit a parse tree produced by AmberPTParser#dihedrals_inc_hydrogen_statement.
    def exitDihedrals_inc_hydrogen_statement(self, ctx: AmberPTParser.Dihedrals_inc_hydrogen_statementContext):
        if ctx.Integer(0):
            return
        self.dihedralsIncHydrogenStatements -= 1

    # Enter a parse tree produced by AmberPTParser#dihedrals_without_hydrogen_statement.
    def enterDihedrals_without_hydrogen_statement(self, ctx: AmberPTParser.Dihedrals_without_hydrogen_statementContext):  # pylint: disable=unused-argument
        self.dihedralsWithoutHydrogenStatements += 1

    # Exit a parse tree produced by AmberPTParser#dihedrals_without_hydrogen_statement.
    def exitDihedrals_without_hydrogen_statement(self, ctx: AmberPTParser.Dihedrals_without_hydrogen_statementContext):
        if ctx.Integer(0):
            return
        self.dihedralsWithoutHydrogenStatements -= 1

    # Enter a parse tree produced by AmberPTParser#excluded_atoms_list_statement.
    def enterExcluded_atoms_list_statement(self, ctx: AmberPTParser.Excluded_atoms_list_statementContext):  # pylint: disable=unused-argument
        self.excludedAtomsListStatements += 1

    # Exit a parse tree produced by AmberPTParser#excluded_atoms_list_statement.
    def exitExcluded_atoms_list_statement(self, ctx: AmberPTParser.Excluded_atoms_list_statementContext):
        if ctx.Integer(0):
            return
        self.excludedAtomsListStatements -= 1

    # Enter a parse tree produced by AmberPTParser#hbcut_statement.
    def enterHbcut_statement(self, ctx: AmberPTParser.Hbcut_statementContext):  # pylint: disable=unused-argument
        self.hbcutStatements += 1

    # Exit a parse tree produced by AmberPTParser#hbcut_statement.
    def exitHbcut_statement(self, ctx: AmberPTParser.Hbcut_statementContext):
        if ctx.Real(0):
            return
        self.hbcutStatements -= 1

    # Enter a parse tree produced by AmberPTParser#hbond_acoef_statement.
    def enterHbond_acoef_statement(self, ctx: AmberPTParser.Hbond_acoef_statementContext):  # pylint: disable=unused-argument
        self.hbondAcoefStatements += 1

    # Exit a parse tree produced by AmberPTParser#hbond_acoef_statement.
    def exitHbond_acoef_statement(self, ctx: AmberPTParser.Hbond_acoef_statementContext):
        if ctx.Real(0):
            return
        self.hbondAcoefStatements -= 1

    # Enter a parse tree produced by AmberPTParser#hbond_bcoef_statement.
    def enterHbond_bcoef_statement(self, ctx: AmberPTParser.Hbond_bcoef_statementContext):  # pylint: disable=unused-argument
        self.hbondBcoefStatements += 1

    # Exit a parse tree produced by AmberPTParser#hbond_bcoef_statement.
    def exitHbond_bcoef_statement(self, ctx: AmberPTParser.Hbond_bcoef_statementContext):
        if ctx.Real(0):
            return
        self.hbondBcoefStatements -= 1

    # Enter a parse tree produced by AmberPTParser#ipol_statement.
    def enterIpol_statement(self, ctx: AmberPTParser.Ipol_statementContext):  # pylint: disable=unused-argument
        self.ipolStatements += 1

    # Exit a parse tree produced by AmberPTParser#ipol_statement.
    def exitIpol_statement(self, ctx: AmberPTParser.Ipol_statementContext):
        if ctx.Integer(0):
            return
        self.ipolStatements -= 1

    # Enter a parse tree produced by AmberPTParser#irotat_statement.
    def enterIrotat_statement(self, ctx: AmberPTParser.Irotat_statementContext):  # pylint: disable=unused-argument
        self.irotatStatements += 1

    # Exit a parse tree produced by AmberPTParser#irotat_statement.
    def exitIrotat_statement(self, ctx: AmberPTParser.Irotat_statementContext):
        if ctx.Integer(0):
            return
        self.irotatStatements -= 1

    # Enter a parse tree produced by AmberPTParser#join_array_statement.
    def enterJoin_array_statement(self, ctx: AmberPTParser.Join_array_statementContext):  # pylint: disable=unused-argument
        self.joinArrayStatements += 1

    # Exit a parse tree produced by AmberPTParser#join_array_statement.
    def exitJoin_array_statement(self, ctx: AmberPTParser.Join_array_statementContext):
        if ctx.Integer(0):
            return
        self.joinArrayStatements -= 1

    # Enter a parse tree produced by AmberPTParser#lennard_jones_acoef_statement.
    def enterLennard_jones_acoef_statement(self, ctx: AmberPTParser.Lennard_jones_acoef_statementContext):  # pylint: disable=unused-argument
        self.lennardJonesAcoefStatements += 1

    # Exit a parse tree produced by AmberPTParser#lennard_jones_acoef_statement.
    def exitLennard_jones_acoef_statement(self, ctx: AmberPTParser.Lennard_jones_acoef_statementContext):
        if ctx.Real(0):
            return
        self.lennardJonesAcoefStatements -= 1

    # Enter a parse tree produced by AmberPTParser#lennard_jones_bcoef_statement.
    def enterLennard_jones_bcoef_statement(self, ctx: AmberPTParser.Lennard_jones_bcoef_statementContext):  # pylint: disable=unused-argument
        self.lennardJonesBcoefStatements += 1

    # Exit a parse tree produced by AmberPTParser#lennard_jones_bcoef_statement.
    def exitLennard_jones_bcoef_statement(self, ctx: AmberPTParser.Lennard_jones_bcoef_statementContext):
        if ctx.Real(0):
            return
        self.lennardJonesBcoefStatements -= 1

    # Enter a parse tree produced by AmberPTParser#mass_statement.
    def enterMass_statement(self, ctx: AmberPTParser.Mass_statementContext):  # pylint: disable=unused-argument
        self.massStatements += 1

    # Exit a parse tree produced by AmberPTParser#mass_statement.
    def exitMass_statement(self, ctx: AmberPTParser.Mass_statementContext):
        if ctx.Real(0):
            return
        self.massStatements -= 1

    # Enter a parse tree produced by AmberPTParser#nonbonded_parm_index_statement.
    def enterNonbonded_parm_index_statement(self, ctx: AmberPTParser.Nonbonded_parm_index_statementContext):  # pylint: disable=unused-argument
        self.nonbondedParmIndexStatements += 1

    # Exit a parse tree produced by AmberPTParser#nonbonded_parm_index_statement.
    def exitNonbonded_parm_index_statement(self, ctx: AmberPTParser.Nonbonded_parm_index_statementContext):
        if ctx.Integer(0):
            return
        self.nonbondedParmIndexStatements -= 1

    # Enter a parse tree produced by AmberPTParser#number_excluded_atoms_statement.
    def enterNumber_excluded_atoms_statement(self, ctx: AmberPTParser.Number_excluded_atoms_statementContext):  # pylint: disable=unused-argument
        self.numberExcludedAtomsStatements += 1

    # Exit a parse tree produced by AmberPTParser#number_excluded_atoms_statement.
    def exitNumber_excluded_atoms_statement(self, ctx: AmberPTParser.Number_excluded_atoms_statementContext):
        if ctx.Integer(0):
            return
        self.numberExcludedAtomsStatements -= 1

    # Enter a parse tree produced by AmberPTParser#pointers_statement.
    def enterPointers_statement(self, ctx: AmberPTParser.Pointers_statementContext):  # pylint: disable=unused-argument
        self.pointersStatements += 1

    # Exit a parse tree produced by AmberPTParser#pointers_statement.
    def exitPointers_statement(self, ctx: AmberPTParser.Pointers_statementContext):
        if ctx.Integer(0):
            return
        self.pointersStatements -= 1

    # Enter a parse tree produced by AmberPTParser#polarizability_statement.
    def enterPolarizability_statement(self, ctx: AmberPTParser.Polarizability_statementContext):  # pylint: disable=unused-argument
        self.polarizabilityStatements += 1

    # Exit a parse tree produced by AmberPTParser#polarizability_statement.
    def exitPolarizability_statement(self, ctx: AmberPTParser.Polarizability_statementContext):
        if ctx.Real(0):
            return
        self.polarizabilityStatements -= 1

    # Enter a parse tree produced by AmberPTParser#radii_statement.
    def enterRadii_statement(self, ctx: AmberPTParser.Radii_statementContext):  # pylint: disable=unused-argument
        self.radiiStatements += 1

    # Exit a parse tree produced by AmberPTParser#radii_statement.
    def exitRadii_statement(self, ctx: AmberPTParser.Radii_statementContext):
        if ctx.Real(0):
            return
        self.radiiStatements -= 1

    # Enter a parse tree produced by AmberPTParser#radius_set_statement.
    def enterRadius_set_statement(self, ctx: AmberPTParser.Radius_set_statementContext):  # pylint: disable=unused-argument
        self.radiusSetStatements += 1

    # Exit a parse tree produced by AmberPTParser#radius_set_statement.
    def exitRadius_set_statement(self, ctx: AmberPTParser.Radius_set_statementContext):
        if ctx.Simple_name(0):
            radiusSet = []
            i = 0
            while ctx.Simple_name(i):
                radiusSet.append(str(ctx.Simple_name(i)))
                i += 1

            self.__radiusSet = ' '.join(radiusSet)
            return
        self.radiusSetStatements -= 1

    # Enter a parse tree produced by AmberPTParser#residue_label_statement.
    def enterResidue_label_statement(self, ctx: AmberPTParser.Residue_label_statementContext):  # pylint: disable=unused-argument
        self.residueLabelStatements += 1

        self.__residueLabel = []

    # Exit a parse tree produced by AmberPTParser#residue_label_statement.
    def exitResidue_label_statement(self, ctx: AmberPTParser.Residue_label_statementContext):
        if ctx.Simple_name(0):
            i = 0
            while ctx.Simple_name(i):
                chunk = chunk_string(str(ctx.Simple_name(i)).upper(), self.__cur_word_len)
                self.__residueLabel.extend(chunk)
                i += 1
            return
        self.residueLabelStatements -= 1

    # Enter a parse tree produced by AmberPTParser#residue_pointer_statement.
    def enterResidue_pointer_statement(self, ctx: AmberPTParser.Residue_pointer_statementContext):  # pylint: disable=unused-argument
        self.residuePointerStatements += 1

        self.__residuePointer = []

    # Exit a parse tree produced by AmberPTParser#residue_pointer_statement.
    def exitResidue_pointer_statement(self, ctx: AmberPTParser.Residue_pointer_statementContext):
        if ctx.Integer(0):
            i = 0
            while ctx.Integer(i):
                self.__residuePointer.append(int(str(ctx.Integer(i))))
                i += 1
            return
        self.residueLabelStatements -= 1

    # Enter a parse tree produced by AmberPTParser#scee_scale_factor_statement.
    def enterScee_scale_factor_statement(self, ctx: AmberPTParser.Scee_scale_factor_statementContext):  # pylint: disable=unused-argument
        self.sceeScaleFactorStatements += 1

    # Exit a parse tree produced by AmberPTParser#scee_scale_factor_statement.
    def exitScee_scale_factor_statement(self, ctx: AmberPTParser.Scee_scale_factor_statementContext):
        if ctx.Real(0):
            return
        self.sceeScaleFactorStatements -= 1

    # Enter a parse tree produced by AmberPTParser#scnb_scale_factor_statement.
    def enterScnb_scale_factor_statement(self, ctx: AmberPTParser.Scnb_scale_factor_statementContext):  # pylint: disable=unused-argument
        self.scnbScaleFactorStatements += 1

    # Exit a parse tree produced by AmberPTParser#scnb_scale_factor_statement.
    def exitScnb_scale_factor_statement(self, ctx: AmberPTParser.Scnb_scale_factor_statementContext):
        if ctx.Real(0):
            return
        self.scnbScaleFactorStatements -= 1

    # Enter a parse tree produced by AmberPTParser#screen_statement.
    def enterScreen_statement(self, ctx: AmberPTParser.Screen_statementContext):  # pylint: disable=unused-argument
        self.screenStatements += 1

    # Exit a parse tree produced by AmberPTParser#screen_statement.
    def exitScreen_statement(self, ctx: AmberPTParser.Screen_statementContext):
        if ctx.Real(0):
            return
        self.screenStatements -= 1

    # Enter a parse tree produced by AmberPTParser#solty_statement.
    def enterSolty_statement(self, ctx: AmberPTParser.Solty_statementContext):  # pylint: disable=unused-argument
        self.soltyStatements += 1

    # Exit a parse tree produced by AmberPTParser#solty_statement.
    def exitSolty_statement(self, ctx: AmberPTParser.Solty_statementContext):
        if ctx.Real(0):
            return
        self.soltyStatements -= 1

    # Enter a parse tree produced by AmberPTParser#solvent_pointers_statement.
    def enterSolvent_pointers_statement(self, ctx: AmberPTParser.Solvent_pointers_statementContext):  # pylint: disable=unused-argument
        self.solventPointersStatements += 1

    # Exit a parse tree produced by AmberPTParser#solvent_pointers_statement.
    def exitSolvent_pointers_statement(self, ctx: AmberPTParser.Solvent_pointers_statementContext):
        if ctx.Integer(0):
            return
        self.solventPointersStatements -= 1

    # Enter a parse tree produced by AmberPTParser#title_statement.
    def enterTitle_statement(self, ctx: AmberPTParser.Title_statementContext):  # pylint: disable=unused-argument
        self.titleStatements += 1

    # Exit a parse tree produced by AmberPTParser#title_statement.
    def exitTitle_statement(self, ctx: AmberPTParser.Title_statementContext):
        if ctx.Simple_name(0):
            title = []
            i = 0
            while ctx.Simple_name(i):
                title.append(str(ctx.Simple_name(i)))
                i += 1

            self.__title = ' '.join(title)
            return
        self.titleStatements -= 1

    # Enter a parse tree produced by AmberPTParser#tree_chain_classification_statement.
    def enterTree_chain_classification_statement(self, ctx: AmberPTParser.Tree_chain_classification_statementContext):  # pylint: disable=unused-argument
        self.treeChainClassificationStatements += 1

    # Exit a parse tree produced by AmberPTParser#tree_chain_classification_statement.
    def exitTree_chain_classification_statement(self, ctx: AmberPTParser.Tree_chain_classification_statementContext):
        if ctx.Simple_name(0):
            return
        self.treeChainClassificationStatements -= 1

    # Enter a parse tree produced by AmberPTParser#format_function.
    def enterFormat_function(self, ctx: AmberPTParser.Format_functionContext):
        try:
            if ctx.Fortran_format_A():
                g = self.__a_format_pat.search(str(ctx.Fortran_format_A())).groups()
                # self.__cur_column_len = int(g[0])
                self.__cur_word_len = int(g[1])
            elif ctx.Fortran_format_I():
                g = self.__i_format_pat.search(str(ctx.Fortran_format_I())).groups()
                # self.__cur_column_len = int(g[0])
                self.__cur_word_len = int(g[1])
            else:
                g = self.__e_format_pat.search(str(ctx.Fortran_format_E())).groups()
                # self.__cur_column_len = int(g[0])
                self.__cur_word_len = int(g[1])
        except AttributeError:
            # self.__cur_column_len = None
            self.__cur_word_len = None

    # Exit a parse tree produced by AmberPTParser#format_function.
    def exitFormat_function(self, ctx: AmberPTParser.Format_functionContext):  # pylint: disable=unused-argument
        pass

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

        contentSubtype = {'version': self.versionStatements,
                          'amber_atom_type': self.amberAtomTypeStatements,
                          'angle_equil_value': self.angleEquilValueStatements,
                          'angle_force_constant': self.angleForceConstantStatements,
                          'angles_inc_hydrogen': self.anglesIncHydrogenStatements,
                          'angles_without_hydrogen': self.anglesWithoutHydrogenStatements,
                          'atomic_number': self.atomicNumberStatements,
                          'atom_name': self.atomNameStatements,
                          'atom_type_index': self.atomTypeIndexStatements,
                          'atoms_per_molecule': self.atomsPerMoleculeStatements,
                          'bond_equil_value': self.bondEquilValueStatements,
                          'bond_force_constant': self.bondForceConstantStatements,
                          'bonds_inc_hydrogen': self.bondsIncHydrogenStatements,
                          'bonds_without_hydrogen': self.bondsWithoutHydrogenStatements,
                          'box_dimensions': self.boxDimensionsStatements,
                          'cap_info': self.capInfoStatements,
                          'cap_info2': self.capInfo2Statements,
                          'charge': self.chargeStatements,
                          'dihedral_force_constant': self.dihedralForceConstantStatements,
                          'dihedral_periodicity': self.dihedralPeriodicityStatements,
                          'dihedral_phase': self.dihedralPhaseStatements,
                          'dihedrals_inc_hydrogen': self.dihedralsIncHydrogenStatements,
                          'dihedrals_without_hydrogen': self.dihedralsWithoutHydrogenStatements,
                          'excluded_atoms_list': self.excludedAtomsListStatements,
                          'hbcut': self.hbcutStatements,
                          'hbond_acoef': self.hbondAcoefStatements,
                          'hbond_bcoef': self.hbondBcoefStatements,
                          'ipol': self.ipolStatements,
                          'irotat': self.irotatStatements,
                          'join_array': self.joinArrayStatements,
                          'lennard_jones_acoef': self.lennardJonesAcoefStatements,
                          'lennard_jones_bcoef': self.lennardJonesBcoefStatements,
                          'mass': self.massStatements,
                          'nonbonded_parm_index': self.nonbondedParmIndexStatements,
                          'number_excluded_atoms': self.numberExcludedAtomsStatements,
                          'pointers': self.pointersStatements,
                          'polarizability': self.polarizabilityStatements,
                          'radii': self.radiiStatements,
                          'radius_set': self.radiusSetStatements,
                          'residue_label': self.residueLabelStatements,
                          'residue_pointer': self.residuePointerStatements,
                          'scee_scale_factor': self.sceeScaleFactorStatements,
                          'scnb_scale_factor': self.scnbScaleFactorStatements,
                          'screen': self.screenStatements,
                          'solty': self.soltyStatements,
                          'solvent_pointers': self.solventPointersStatements,
                          'title': self.titleStatements,
                          'tree_chain_classification': self.treeChainClassificationStatements
                          }

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

    def getVersionInfo(self):
        """ Return version information of AMBER parameter/topology file.
            @return: version, date, time
        """
        return self.__version, self.__date, self.__time

    def getTitle(self):
        """ Return title of AMBER parameter/topology file.
        """
        return self.__title

    def getRadiusSet(self):
        """ Return radius set of AMBER parameter/topology file.
        """
        return self.__radiusSet

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

    def getPolymerSequence(self):
        """ Return polymer sequence of AMBER 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 AMBER 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 AMBER parameter/topology.
        """
        return None if self.__chainAssign is None or len(self.__chainAssign) == 0 else self.__chainAssign

# del AmberPTParser
