##
# File: AmberPTParserListener.py
# Date: 27-Jan-2022
#
# Updates:
# Generated from AmberPTParser.g4 by ANTLR 4.9
""" ParserLister class for AMBER PT files.
    @author: Masashi Yokochi
"""
import sys
import re
import copy

from antlr4 import ParseTreeListener

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 (checkCoordinates,
                                                       translateAmberAtomNomenclature,
                                                       REPRESENTATIVE_MODEL_ID)
    from wwpdb.utils.nmr.ChemCompUtil import ChemCompUtil
    from wwpdb.utils.nmr.BMRBChemShiftStat import BMRBChemShiftStat
    from wwpdb.utils.nmr.AlignUtil import (hasLargeSeqGap,
                                           fillBlankCompIdWithOffset, beautifyPolySeq,
                                           getMiddleCode, getGaugeCode, getScoreOfSeqAlign,
                                           getOneLetterCodeSequence,
                                           letterToDigit, indexToLetter)
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 (checkCoordinates,
                                           translateAmberAtomNomenclature,
                                           REPRESENTATIVE_MODEL_ID)
    from nmr.ChemCompUtil import ChemCompUtil
    from nmr.BMRBChemShiftStat import BMRBChemShiftStat
    from nmr.AlignUtil import (hasLargeSeqGap,
                               fillBlankCompIdWithOffset, beautifyPolySeq,
                               getMiddleCode, getGaugeCode, getScoreOfSeqAlign,
                               getOneLetterCodeSequence,
                               letterToDigit, indexToLetter)


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


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

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

    # criterion for minimum sequence coverage when conflict occurs (NMR separated deposition)
    min_seq_coverage_w_conflict = 0.95

    # CCD accessing utility
    __ccU = None

    # BMRB chemical shift statistics
    __csStat = None

    # Pairwise align
    __pA = None

    # polymer sequences of the coordinate file generated by NmrDpUtility.__extractCoordPolymerSequence()
    __hasPolySeqModel = False
    __polySeqModel = None

    # 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 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

    warningMessage = ''

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

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

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

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

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

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

    # 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 = []

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

        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
        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 = []

        offset = 0
        for atomNum, atomName in enumerate(self.__atomName, 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 terminus[atomNum - 1]:
                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}

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

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

            ps['comp_id'] = compIdList

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

                    atomId = translateAmberAtomNomenclature(atomNum['auth_atom_id'])

                    if atomId is not None and atomId in chemCompAtomIds:
                        atomNum['atom_id'] = atomId

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

        self.alignPolymerSequence()
        self.assignPolymerSequence()

        if self.__chainAssign is not None:

            chain_mapping = {}

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

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

            if len(chain_mapping) > 0:

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

                for atomNum in self.__atomNumberDict:
                    atomNum['chain_id'] = chain_mapping[atomNum['chain_id']]

                self.alignPolymerSequence()
                self.assignPolymerSequence()

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

    def alignPolymerSequence(self):
        if not self.__hasPolySeqModel or self.__polySeqPrmTop is None:
            return

        self.__seqAlign = []

        for s1 in self.__polySeqModel:
            chain_id = s1['chain_id']

            for s2 in self.__polySeqPrmTop:
                chain_id2 = s2['chain_id']

                self.__pA.setReferenceSequence(s1['comp_id'], 'REF' + chain_id)
                self.__pA.addTestSequence(s2['comp_id'], chain_id)
                self.__pA.doAlign()

                myAlign = self.__pA.getAlignment(chain_id)

                length = len(myAlign)

                if length == 0:
                    continue

                _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                if length == unmapped + conflict or _matched <= conflict:
                    continue

                _s1 = s1 if offset_1 == 0 else fillBlankCompIdWithOffset(s1, offset_1)
                _s2 = s2 if offset_2 == 0 else fillBlankCompIdWithOffset(s2, offset_2)

                if conflict > 0 and hasLargeSeqGap(_s1, _s2):
                    __s1, __s2 = beautifyPolySeq(_s1, _s2)
                    _s1_ = __s1
                    _s2_ = __s2

                    self.__pA.setReferenceSequence(_s1_['comp_id'], 'REF' + chain_id)
                    self.__pA.addTestSequence(_s2_['comp_id'], chain_id)
                    self.__pA.doAlign()

                    myAlign = self.__pA.getAlignment(chain_id)

                    length = len(myAlign)

                    _matched, unmapped, _conflict, _offset_1, _offset_2 = getScoreOfSeqAlign(myAlign)

                    if _conflict == 0 and len(__s2['comp_id']) - len(s2['comp_id']) == conflict:
                        conflict = 0
                        offset_1 = _offset_1
                        offset_2 = _offset_2
                        _s1 = __s1
                        _s2 = __s2

                ref_length = len(s1['seq_id'])

                ref_code = getOneLetterCodeSequence(_s1['comp_id'])
                test_code = getOneLetterCodeSequence(_s2['comp_id'])
                mid_code = getMiddleCode(ref_code, test_code)
                ref_gauge_code = getGaugeCode(_s1['seq_id'])
                test_gauge_code = getGaugeCode(_s2['seq_id'])

                if any((__s1, __s2) for (__s1, __s2, __c1, __c2)
                       in zip(_s1['seq_id'], _s2['seq_id'], _s1['comp_id'], _s2['comp_id'])
                       if __c1 != '.' and __c2 != '.' and __c1 != __c2):
                    seq_id1 = []
                    seq_id2 = []
                    comp_id1 = []
                    comp_id2 = []
                    idx1 = 0
                    idx2 = 0
                    for i in range(length):
                        myPr = myAlign[i]
                        myPr0 = str(myPr[0])
                        myPr1 = str(myPr[1])
                        if myPr0 != '.':
                            while idx1 < len(_s1['seq_id']):
                                if _s1['comp_id'][idx1] == myPr0:
                                    seq_id1.append(_s1['seq_id'][idx1])
                                    comp_id1.append(myPr0)
                                    idx1 += 1
                                    break
                                idx1 += 1
                        else:
                            seq_id1.append(None)
                            comp_id1.append('.')
                        if myPr1 != '.':
                            while idx2 < len(_s2['seq_id']):
                                if _s2['comp_id'][idx2] == myPr1:
                                    seq_id2.append(_s2['seq_id'][idx2])
                                    comp_id2.append(myPr1)
                                    idx2 += 1
                                    break
                                idx2 += 1
                        else:
                            seq_id2.append(None)
                            comp_id2.append('.')
                    ref_code = getOneLetterCodeSequence(comp_id1)
                    test_code = getOneLetterCodeSequence(comp_id2)
                    mid_code = getMiddleCode(ref_code, test_code)
                    ref_gauge_code = getGaugeCode(seq_id1, offset_1)
                    test_gauge_code = getGaugeCode(seq_id2, offset_2)
                    if ' ' in ref_gauge_code:
                        for p, g in enumerate(ref_gauge_code):
                            if g == ' ':
                                ref_code = ref_code[0:p] + '-' + ref_code[p + 1:]
                    if ' ' in test_gauge_code:
                        for p, g in enumerate(test_gauge_code):
                            if g == ' ':
                                test_code = test_code[0:p] + '-' + test_code[p + 1:]

                matched = mid_code.count('|')

                seq_align = {'ref_chain_id': chain_id, 'test_chain_id': chain_id2, 'length': ref_length,
                             'matched': matched, 'conflict': conflict, 'unmapped': unmapped,
                             'sequence_coverage': float(f"{float(length - (unmapped + conflict)) / ref_length:.3f}"),
                             'ref_seq_id': _s1['seq_id'], 'test_seq_id': _s2['seq_id'],
                             'ref_gauge_code': ref_gauge_code, 'ref_code': ref_code, 'mid_code': mid_code,
                             'test_code': test_code, 'test_gauge_code': test_gauge_code}

                self.__seqAlign.append(seq_align)

    def assignPolymerSequence(self):
        if self.__seqAlign is None:
            return

        top_chains = len(self.__polySeqPrmTop)

        mat = []
        indices = []

        for s1 in self.__polySeqModel:
            chain_id = s1['chain_id']

            cost = [0 for i in range(top_chains)]

            for s2 in self.__polySeqPrmTop:
                chain_id2 = s2['chain_id']

                result = next((seq_align for seq_align in self.__seqAlign
                               if seq_align['ref_chain_id'] == chain_id
                               and seq_align['test_chain_id'] == chain_id2), None)

                if result is not None:
                    cost[self.__polySeqPrmTop.index(s2)] = result['unmapped'] + result['conflict'] - result['length']
                    if result['length'] >= len(s1['seq_id']) - result['unmapped']:
                        indices.append((self.__polySeqModel.index(s1), self.__polySeqPrmTop.index(s2)))

            mat.append(cost)

        self.__chainAssign = []

        concatenated_prmtop_chain = {}

        for row, column in indices:

            if mat[row][column] >= 0:
                _cif_chains = []
                for _row, _column in indices:
                    if column == _column:
                        _cif_chains.append(self.__polySeqModel[_row]['chain_id'])

                if len(_cif_chains) > 1:
                    chain_id2 = self.__polySeqPrmTop[column]['chain_id']
                    concatenated_prmtop_chain[chain_id2] = _cif_chains

                    self.warningMessage += f"[Concatenated sequence] The chain ID {chain_id2!r} of the sequences in the AMBER parameter/topology file "\
                        f"will be re-assigned to the chain IDs {_cif_chains} in the coordinates during biocuration.\n"

            chain_id = self.__polySeqModel[row]['chain_id']
            chain_id2 = self.__polySeqPrmTop[column]['chain_id']

            result = next(seq_align for seq_align in self.__seqAlign
                          if seq_align['ref_chain_id'] == chain_id and seq_align['test_chain_id'] == chain_id2)

            chain_assign = {'ref_chain_id': chain_id, 'test_chain_id': chain_id2, 'length': result['length'],
                            'matched': result['matched'], 'conflict': result['conflict'], 'unmapped': result['unmapped'],
                            'sequence_coverage': result['sequence_coverage']}

            auth_chain_id = chain_id
            if 'auth_chain_id' in self.__polySeqModel[row]:
                auth_chain_id = self.__polySeqModel[row]['auth_chain_id']
                chain_assign['ref_auth_chain_id'] = auth_chain_id

            s1 = next(s for s in self.__polySeqModel if s['chain_id'] == chain_id)
            s2 = next(s for s in self.__polySeqPrmTop if s['chain_id'] == chain_id2)

            self.__pA.setReferenceSequence(s1['comp_id'], 'REF' + chain_id)
            self.__pA.addTestSequence(s2['comp_id'], chain_id)
            self.__pA.doAlign()

            myAlign = self.__pA.getAlignment(chain_id)

            length = len(myAlign)

            _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

            _s1 = s1 if offset_1 == 0 else fillBlankCompIdWithOffset(s1, offset_1)
            _s2 = s2 if offset_2 == 0 else fillBlankCompIdWithOffset(s2, offset_2)

            if conflict > 0 and hasLargeSeqGap(_s1, _s2):
                __s1, __s2 = beautifyPolySeq(_s1, _s2)
                _s1 = __s1
                _s2 = __s2

                self.__pA.setReferenceSequence(_s1['comp_id'], 'REF' + chain_id)
                self.__pA.addTestSequence(_s2['comp_id'], chain_id)
                self.__pA.doAlign()

                myAlign = self.__pA.getAlignment(chain_id)

                length = len(myAlign)

                _matched, unmapped, _conflict, _, _ = getScoreOfSeqAlign(myAlign)

                if _conflict == 0 and len(__s2['comp_id']) - len(s2['comp_id']) == conflict:
                    result['conflict'] = 0
                    s2 = __s2

            if result['unmapped'] > 0 or result['conflict'] > 0:

                aligned = [True] * length
                seq_id1 = []
                seq_id2 = []

                j = 0
                for i in range(length):
                    if str(myAlign[i][0]) != '.':
                        seq_id1.append(s1['seq_id'][j])
                        j += 1
                    else:
                        seq_id1.append(None)

                j = 0
                for i in range(length):
                    if str(myAlign[i][1]) != '.':
                        seq_id2.append(s2['seq_id'][j])
                        j += 1
                    else:
                        seq_id2.append(None)

                for i in range(length):
                    myPr = myAlign[i]
                    myPr0 = str(myPr[0])
                    myPr1 = str(myPr[1])
                    if myPr0 == '.' or myPr1 == '.':
                        aligned[i] = False
                    elif myPr0 != myPr1:
                        pass
                    else:
                        break

                for i in reversed(range(length)):
                    myPr = myAlign[i]
                    myPr0 = str(myPr[0])
                    myPr1 = str(myPr[1])
                    if myPr0 == '.' or myPr1 == '.':
                        aligned[i] = False
                    elif myPr0 != myPr1:
                        pass
                    else:
                        break

                _conflicts = 0

                for i in range(length):
                    myPr = myAlign[i]
                    if myPr[0] == myPr[1]:
                        continue

                    cif_comp_id = str(myPr[0])
                    top_comp_id = str(myPr[1])

                    if top_comp_id == '.' and cif_comp_id != '.':
                        pass

                    elif top_comp_id != cif_comp_id and aligned[i]:
                        _conflicts += 1

                if _conflicts > chain_assign['unmapped'] and chain_assign['sequence_coverage'] < self.min_seq_coverage_w_conflict:
                    continue

                unmapped = []
                conflict = []

                for i in range(length):
                    myPr = myAlign[i]
                    if myPr[0] == myPr[1]:
                        continue

                    cif_comp_id = str(myPr[0])
                    top_comp_id = str(myPr[1])

                    if top_comp_id == '.' and cif_comp_id != '.':

                        unmapped.append({'ref_seq_id': seq_id1[i], 'ref_comp_id': cif_comp_id})

                        if not aligned[i]:
                            cif_seq_code = f"{auth_chain_id}:{seq_id1[i]}:{cif_comp_id}"

                            self.warningMessage += f"[Sequence mismatch] {cif_seq_code} is not present "\
                                f"in the AMBER parameter/topology data (chain_id {chain_id2}).\n"

                    elif top_comp_id != cif_comp_id and aligned[i]:

                        conflict.append({'ref_seq_id': seq_id1[i], 'ref_comp_id': cif_comp_id,
                                         'test_seq_id': seq_id2[i], 'test_comp_id': top_comp_id})

                        cif_seq_code = f"{auth_chain_id}:{seq_id1[i]}:{cif_comp_id}"
                        if cif_comp_id == '.':
                            cif_seq_code += ', insertion error'
                        top_seq_code = f"{chain_id2}:{seq_id2[i]}:{top_comp_id}"
                        if top_comp_id == '.':
                            top_seq_code += ', insertion error'

                        self.warningMessage += f"[Sequence mismatch] Sequence alignment error between the coordinate ({cif_seq_code}) "\
                            f"and the AMBER parameter/topology data ({top_seq_code}). "\
                            "Please verify the two sequences and re-upload the correct file(s) if required.\n"

                if len(unmapped) > 0:
                    chain_assign['unmapped_sequence'] = unmapped

                if len(conflict) > 0:
                    chain_assign['conflict_sequence'] = conflict
                    chain_assign['conflict'] = len(conflict)
                    chain_assign['unmapped'] = chain_assign['unmapped'] - len(conflict)
                    if chain_assign['unmapped'] < 0:
                        chain_assign['conflict'] -= chain_assign['unmapped']
                        chain_assign['unmapped'] = 0

                    result['conflict'] = chain_assign['conflict']
                    result['unmapped'] = chain_assign['unmapped']

            self.__chainAssign.append(chain_assign)

        if len(self.__chainAssign) > 0:

            if len(self.__polySeqModel) > 1:

                if any(s for s in self.__polySeqModel if 'identical_chain_id' in s):

                    for chain_assign in self.__chainAssign:

                        if chain_assign['conflict'] > 0:
                            continue

                        chain_id = chain_assign['ref_chain_id']
                        auth_chain_id = None if 'ref_auth_chain_id' not in chain_assign else chain_assign['ref_auth_chain_id']

                        try:
                            identity = next(s['identical_chain_id'] for s in self.__polySeqModel
                                            if s['chain_id'] == chain_id and 'identical_chain_id' in s)

                            for chain_id in identity:

                                if not any(_chain_assign for _chain_assign in self.__chainAssign if _chain_assign['ref_chain_id'] == chain_id):
                                    _chain_assign = copy.copy(chain_assign)
                                    _chain_assign['ref_chain_id'] = chain_id
                                    if auth_chain_id is not None:
                                        _chain_assign['ref_auth_chain_id'] = auth_chain_id
                                    self.__chainAssign.append(_chain_assign)

                        except StopIteration:
                            pass

    # 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):
            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 getPolymerSequence(self):
        """ Return polymer sequence of AMBER parameter/topology file.
        """
        return self.__polySeqPrmTop

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

    def getSequenceAlignment(self):
        """ Return sequence alignment between coordinates and AMBER parameter/topology.
        """
        return self.__seqAlign

    def getChainAssignment(self):
        """ Return chain assignment between coordinates and AMBER parameter/topology.
        """
        return self.__chainAssign

# del AmberPTParser
