"""PDBファイルの読み書きおよび分子構造の切り出し・変換を行うモジュール。"""

from __future__ import annotations

import sys
import os
import re
import copy
import logging
from typing import Any
from .abinit_io import abinit_io as fab
import collections
import itertools

logger = logging.getLogger(__name__)
try:
    import numpy as np
except ImportError:
    pass
try:
    from UDFManager import *
except ImportError:
    pass

class pdb_io(fab):
    """PDBファイルの入出力と分子構造操作を提供するクラス。

    PDB形式ファイルの読み込み・書き出し、残基単位での分子切り出し、
    セル内への分子移動などの機能を持つ。
    """

    def __init__(self):
        super().__init__()
        # print('## load pdb io init')
        self.solutes = []
        self.getmode = 'resnum'
        self.assignresname = False
        self.refreshatmtype = False
        self.refreshresid = False
        self.cellsize = 0
        self.totalRes = []
        self.atmtypeRes = []
        self.resnames = []
        self.gatmlabRes = []
        self.posRes = []
        self.headRes = []
        self.labRes = []
        self.chainRes = []
        self.resnumRes = []
        self.codeRes = []
        self.occRes = []
        self.tempRes = []
        self.amarkRes = []
        self.chargeRes = []
        self.rescount = []

        pass

    def readpdb(self, fname: str) -> None:
        """PDBファイルを読み込み、残基ごとの原子情報をインスタンス変数に格納する。

        Args:
            fname: 読み込むPDBファイルのパス。
        """

        logger.info('--- get pdbinfo ---')
        logger.info('infile: %s', fname)
        with open(fname, 'r') as _fh:
            lines = _fh.readlines()
        molnames = []
        poss = []
        atypenames = []
        heads = []
        molnames = []
        atypenames = []
        labs = []
        chains = []
        resnums = []
        codes = []
        poss = []
        occs = []
        temps = []
        amarks = []
        charges = []

        posRes = []
        atmtypeRes = []
        headRes = []
        labRes = []
        chainRes = []
        resnumRes = []
        codeRes = []
        occRes = []
        tempRes = []
        amarkRes = []
        chargeRes = []
        totalRes = 0
        resnames = []
        nums = []

        atomcount = 0
        for line in lines:
            itemlist = line.split()
            # print(itemlist)
            if self.getmode == 'TER':
                if len(itemlist) >= 1 and itemlist[0] == 'TER':
                    logger.debug('%s', itemlist)
                    posRes.append(poss)
                    atmtypeRes.append(atypenames)
                    headRes.append(heads)
                    labRes.append(labs)
                    chainRes.append(chains)
                    resnumRes.append(resnums)
                    codeRes.append(codes)
                    occRes.append(occs)
                    tempRes.append(temps)
                    amarkRes.append(amarks)
                    chargeRes.append(charges)
                    poss = []
                    atypenames =[]
                    heads = []
                    labs = []
                    chains = []
                    resnums = []
                    codes = []
                    occs = []
                    temps = []
                    amarks = []
                    charges = []
                    resnames.append(totalRes)
                    totalRes += 1
                    continue

            if len(itemlist) < 3:
                continue

            if line[0:6] == 'HETATM' or line[0:4] == 'ATOM':
            # if itemlist[0] == 'ATOM' or itemlist[0] == 'HETATM':
            #     molname = itemlist[3]
            #     molnames.append(molname)
            #     poss.append([float(e) for e in itemlist[5:8]])
            #     # atypenames.append(itemlist[2])
            #     atypenames.append(line[12:15])

            #     atomcount += 1
            # # over HETATM10000
            # elif len(itemlist[0]) > 6 and itemlist[0][0:6] == 'HETATM':
            #     molname = itemlist[2]
            #     molnames.append(molname)
            #     poss.append([float(e) for e in itemlist[4:7]])
            #     # atypenames.append(itemlist[1])
            #     atypenames.append(line[12:15])
                atomcount += 1


                num = line[6:12]
                try:
                    # ここでnumが数値として扱えるように、必要なら int() で変換する
                    num_int = int(num)
                except (ValueError, TypeError) as e:
                    # numが整数に変換できなかった場合の処理
                    logger.warning("numの変換に失敗しました: %s", e)
                    num_int = 0  # もしくは、適切なデフォルト値を設定する

                if num_int < 100000:
                    head=line[0:6]
                    num=line[6:11]
                    atypename=line[12:16]
                    lab=line[16]
                    res=line[17:20].strip()
                    chain=line[21]
                    resnum=line[22:26]
                    code=line[26]
                    pos=[float(line[30:38].strip()), float(line[38:46].strip()), float(line[46:54].strip())]
                    occ=line[54:60]
                    temp=line[60:66]
                    amark=line[76:78]
                    charge=line[78:80]

                else:
                    head=line[0:6]
                    num=line[6:12]
                    atypename=line[13:17]
                    lab=line[17]
                    res=line[18:21].strip()
                    chain=line[22]
                    resnum=line[23:27]
                    code=line[27]
                    pos=[float(line[31:39].strip()), float(line[39:47].strip()), float(line[47:55].strip())]
                    occ=line[55:61]
                    temp=line[61:67]
                    amark=line[77:79]
                    charge=line[79:81]

                nums.append(num)
                heads.append(head)
                molnames.append(res)
                atypenames.append(atypename)
                labs.append(lab)
                chains.append(chain)
                resnums.append(resnum)
                codes.append(code)
                poss.append(pos)
                occs.append(occ)
                temps.append(temp)
                amarks.append(amark)
                charges.append(charge.replace('\n', ''))

            totalatom = atomcount
        # print(poss)
        if amarks[0].strip() == '':
            logger.warning('this file dont have elem mark column. -> refreshatmtype = False')
            self.refreshatmtype = False
        if self.getmode == 'rfile':
            molname_set = set(molnames)
            logger.info('totalatom %s', totalatom)
            logger.info('molname_set %s', molname_set)
            mol_confs = []
            for molname in molname_set:
                mol_confs.append(self.config_read(molname, 0))
            # print('mol_confs', mol_confs)
            # print('molnames_atom', molnames)
            molnums = {}
            for molconf in mol_confs:
                molnums[molconf['name']] = sum(molconf['atom'])
            logger.info('molnatoms %s', molnums)

            #search molhead
            i = 0
            resnames = []
            while True:
                resnames.append(molnames[i])
                atomnum = molnums[molnames[i]]
                i += atomnum
                if i + 1> totalatom:
                    break
                # print(i + 1, molnames[i])
            logger.info('molnames_permol %s', resnames)
            totalRes = len(resnames)

            gatmlabRes = self.getpermol(totalRes, molnums, resnames, nums)
            resnameRes = self.getpermol(totalRes, molnums, resnames, molnames)
            posRes = self.getpermol(totalRes, molnums, resnames, poss)
            atmtypeRes = self.getpermol(totalRes, molnums, resnames, atypenames)
            headRes = self.getpermol(totalRes, molnums, resnames, heads)
            labRes = self.getpermol(totalRes, molnums, resnames, labs)
            chainRes  = self.getpermol(totalRes, molnums, resnames, chains)
            resnumRes  = self.getpermol(totalRes, molnums, resnames, resnums)
            codeRes  = self.getpermol(totalRes, molnums, resnames, codes)
            occRes  = self.getpermol(totalRes, molnums, resnames, occs)
            tempRes  = self.getpermol(totalRes, molnums, resnames, temps)
            amarkRes  = self.getpermol(totalRes, molnums, resnames, amarks)
            chargeRes = self.getpermol(totalRes, molnums, resnames, charges)

        if self.getmode == 'resnum':
            #search molhead
            i = 0
            atomnum = 0
            resnames = [molnames[0]]
            anummols = []
            # print(resnums)
            while True:
                # print(resnums[i])
                atomnum += 1
                if i + 1 == totalatom:
                    anummols.append(atomnum)
                    break
                if resnums[i] != resnums[i+1]:
                    # print(resnums[i], resnums[i+1])
                    # print(i)
                    anummols.append(atomnum)
                    resnames.append(molnames[i+1])
                    atomnum = 0
                i += 1
                # print(i + 1, molnames[i])

            # print(anummols)
            totalRes = len(anummols)
            # print(totalRes)
            gatmlabRes = self.getpermol2(totalRes, anummols, nums)
            resnameRes = self.getpermol2(totalRes, anummols, molnames)
            posRes = self.getpermol2(totalRes, anummols, poss)
            atmtypeRes = self.getpermol2(totalRes, anummols, atypenames)
            headRes = self.getpermol2(totalRes, anummols, heads)
            labRes = self.getpermol2(totalRes, anummols, labs)
            chainRes = self.getpermol2(totalRes, anummols, chains)
            resnumRes = self.getpermol2(totalRes, anummols, resnums)
            codeRes = self.getpermol2(totalRes, anummols, codes)
            occRes = self.getpermol2(totalRes, anummols, occs)
            tempRes = self.getpermol2(totalRes, anummols, temps)
            amarkRes = self.getpermol2(totalRes, anummols, amarks)
            chargeRes = self.getpermol2(totalRes, anummols, charges)

            if self.assignresname:
                moldatas = []
                moldatas_el = []
                molidnames = []
                current = 1
                # loop for allmol
                for i in range(totalRes):
                    if anummols[i] not in moldatas:
                        # print(moldatas, anummols[i])
                        moldatas.append(anummols[i])
                        moldatas_el.append(atmtypeRes[i])
                        # print(anummols[i], 'append')
                        molidnames.append('{:0>3}'.format(str(current)))
                        current += 1
                        continue
                    flags = []
                    # print(moldatas)
                    # loop for databasemol
                    for k in range(len(moldatas)):
                        flag = False
                        if moldatas[k] == anummols[i]:
                            # loop for batabase-1molatom
                            for j in range(len(moldatas_el[k])):
                                if moldatas_el[k][j] != atmtypeRes[i][j]:
                                    flag = True
                                    flags.append(flag)
                                    break
                            flags.append(flag)
                            # print(flags)

                            if not flag:
                                molidnames.append('{:0>3}'.format(str(k + 1)))
                                break

                    if True in flags and False not in flags:
                        # print('mol', i, 'different!')
                        moldatas.append(anummols[i])
                        moldatas_el.append(atmtypeRes[i])
                        molidnames.append('{:0>3}'.format(str(current)))
                        current += 1

                # print(molidnames)
                resnames = copy.deepcopy(molidnames)
                # print(moldatas)

        self.totalRes = totalRes
        self.atmtypeRes = atmtypeRes
        self.resnames = resnames
        self.resnameRes = resnameRes
        self.gatmlabRes = gatmlabRes
        self.posRes = posRes
        self.headRes = headRes
        self.labRes = labRes
        self.chainRes = chainRes
        self.resnumRes = resnumRes
        self.codeRes = codeRes
        self.occRes = occRes
        self.tempRes = tempRes
        self.amarkRes = amarkRes
        self.chargeRes = chargeRes
        self.rescount = collections.Counter(self.resnames)

        return
        # return totalRes, atmtypeRes, resnames, gatmlabRes, posRes, headRes, labRes, chainRes ,resnumRes ,codeRes ,occRes ,tempRes ,amarkRes ,chargeRes

    def getpermol(self, totalRes: int, molnums: dict[str, int], resnames: list[str], datas: list[Any]) -> list[list[Any]]:
        """フラットなデータリストを分子名ごとの原子数に基づいて残基単位に分割する。

        Args:
            totalRes: 残基の総数。
            molnums: 分子名をキー、原子数を値とする辞書。
            resnames: 各残基の分子名リスト。
            datas: 分割対象のフラットなデータリスト。

        Returns:
            残基単位に分割されたデータのリスト。
        """
        datamols = []
        count = 0
        for i in range(totalRes):
            datamol = []
            for j in range(molnums[resnames[i]]):
                datamol.append(datas[count])
                count += 1
            datamols.append(datamol)
        return datamols

    def getpermol2(self, totalRes: int, anummols: list[int], datas: list[Any]) -> list[list[Any]]:
        """フラットなデータリストを各残基の原子数リストに基づいて残基単位に分割する。

        Args:
            totalRes: 残基の総数。
            anummols: 各残基の原子数リスト。
            datas: 分割対象のフラットなデータリスト。

        Returns:
            残基単位に分割されたデータのリスト。
        """
        datamols = []
        # print(anummols)
        count = 0
        for i in range(totalRes):
            datamol = []
            for j in range(anummols[i]):
                datamol.append(datas[count])
                count += 1
            datamols.append(datamol)
            # print(datamols)
        return datamols

#        aobj.exportardpdbfull(opath + '/' + self.readgeom, index, posMol, atomnameMol, self.resnames, heads, labs, chains, resnums, codes, occs, temps, amarks, charges)

    def devidepdb(self, iname: str, oname: str, index: int) -> None:
        ''' devide pdb file to each molecule '''
        #q 引数を残基番号にして、「その残基を除いた構造」と「その残基のみの構造」をpdbで出力したい





    def exportardpdbfull(self, out_file: str, mollist: list[int]) -> None:
        """インスタンスに格納された残基情報を元にPDBファイルを出力する。

        Args:
            out_file: 出力ファイルパス（拡張子 .pdb は自動付与）。
            mollist: 出力対象の残基インデックスのリスト。
        """

        out_file = out_file + '.pdb'
        logger.info('outfile %s', out_file)

        # # Export position of mol
        # head, ext = os.path.splitext(str(iname))
        # molid = sorted(set(molnames), key=molnames.index)
        # print(molid)

#         molids = []
#         for molname in molnames:
#             for i in range(len(molid)):
#                 if molname == molid[i]:
#                     molids.append(i)
        # print(molnames)

        # header
        # print(out_file)
        with open(out_file, "w", newline = "\n") as f:
            print("COMPND    " + out_file, file=f)
            print("AUTHOR    " + "GENERATED BY python script in abmptools", file=f)
            if self.cellsize != 0:
                print('CRYST1{0[0]:>9.3f}{0[1]:>9.3f}{0[2]:>9.3f}  90.00  90.00  90.00               1'.format(self.cellsize), file=f)
                # CRYST1   38.376   38.376   38.376  90.00  90.00  90.00               1

        with open(out_file, "a+", newline = "\n") as f:
            tatomlab = 0
            reslab = 0
            # print('mollist', mollist)

            for i in mollist:
                posMol = self.posRes[i]
                reslab += 1
                if reslab >= 10000:
                    reslab -= 10000

                for j in range(len(posMol)):
                    tatomlab += 1
                    if tatomlab >= 100000:
                        tatomlab -= 100000

                    if not self.refreshatmtype:
                        atomname = self.atmtypeRes[i][j]
                        if atomname.strip() == 'HS':
                            atomname = 'H  '
                        form ='{0[0]:<6}{0[1]:>5} {0[2]:>4}{0[3]:>1}{0[4]:<3} {0[5]:>1}{0[6]:>4}{0[7]:>1}   {0[8]:>8}{0[9]:>8}{0[10]:>8}{0[11]:>6}{0[12]:>6}          {0[13]:>2}{0[14]:>2}'
                    else:
                        atomname = self.amarkRes[i][j]
                        form ='{0[0]:<6}{0[1]:>5} {0[2]:>2}  {0[3]:>1}{0[4]:<3} {0[5]:>1}{0[6]:>4}{0[7]:>1}   {0[8]:>8}{0[9]:>8}{0[10]:>8}{0[11]:>6}{0[12]:>6}          {0[13]:>2}{0[14]:>2}'

                    if self.refreshresid:
                        resid = reslab
                    else:
                        resid = self.resnumRes[i][j]

                    if atomname.strip() == 'CL':
                        self.headRes[i][j] = 'HETATM'

#                 print(self.headRes[i][j])
#                 print(str(tatomlab))
#                 print(atomname)
#                 print(self.labRes[i][j])
#                 print(self.resnames[i])
#                 print(self.chainRes[i][j])
#                 print(resid)
#                 print(self.codeRes[i][j])
#                 print('{:.3f}'.format(posMol[j][0]))
#                 print('{:.3f}'.format(posMol[j][1]))
#                 print('{:.3f}'.format(posMol[j][2]))
#                 print(self.occRes[i][j])
#                 print(self.tempRes[i][j])
#                 print(self.amarkRes[i][j])
#                 print(self.chargeRes[i][j])

                    olist = [self.headRes[i][j], str(tatomlab), atomname, self.labRes[i][j], self.resnames[i], self.chainRes[i][j], resid, self.codeRes[i][j], '{:.3f}'.format(posMol[j][0]), '{:.3f}'.format(posMol[j][1]), '{:.3f}'.format(posMol[j][2]), self.occRes[i][j], self.tempRes[i][j], self.amarkRes[i][j], self.chargeRes[i][j]]
                    print(form.format(olist), file=f)
                # ATOM      1  H   UNK     1     -12.899  32.293   3.964  1.00  0.00           H

            print("END", file=f)

    #1 1 – 6  HETATM
    #2 7 – 11 原子の通し番号
    # blank 1
    #3 13 – 16    原子名
    #4 17  Alternate location識別子
    #5 18 - 20 残基名
    # blank 1
    #6 22  鎖名
    #7 23 - 26 残基番号
    #8 27  残基の挿入コード
    # blank 3
    #9 31 - 38 原子のX座標の値（Å単位）
    #10 39 - 46 原子のY座標の値（Å単位）
    #11 47 - 54 原子のZ座標の値（Å単位）
    #12 55 - 60 占有率
    #13 61 - 66 温度因子
    #14 77 - 78 元素記号
    #15 79 - 80 原子の電荷

    def exportardxyzfull(self, ifile: str, out_file: str, gatmlabRes: list[list[str]]) -> None:
        """XYZファイルから原子座標を読み取り、指定された原子ラベルに基づきXYZ形式で出力する。

        Args:
            ifile: 入力XYZファイルのパス。
            out_file: 出力ファイルパス（拡張子 .xyz は自動付与）。
            gatmlabRes: 残基ごとの原子ラベルリスト。
        """

        out_file = out_file + '.xyz'
        logger.info('outfile %s', out_file)

        with open(ifile, "r", newline = "\n") as _fh:
            itemlines = _fh.readlines()
        # print(itemlines)

        #flatten gatm
        gatms = list(itertools.chain.from_iterable(gatmlabRes))
        self.natom = len(gatms)
        with open(out_file, "w", newline = "\n") as f:
            print(len(gatms), file=f)
            print("", file=f)
            xyzi = 0
            for gatm in gatms:
                xyzi += 1
                # print('gatm', gatm)
                tgt = itemlines[int(gatm) + 1].split()
                print(tgt[0], tgt[1], tgt[2], tgt[3], file=f)
                self.xyzstr += str(xyzi) + ' ' + tgt[0] + ' 1 ' + ' ' + tgt[1] + ' ' + tgt[2] + ' ' + tgt[3] + ' 1\n'
        logger.debug('%s', self.xyzstr)
        return

    def exportardpdb(self, out_file: str, mollist: list[int], posRes: list[list[list[float]]], nameAtom: list[list[str]], molnames_orig: list[str]) -> None:
        """指定された分子リストの座標と原子名からPDBファイルを出力する。

        Args:
            out_file: 出力ファイルパス。
            mollist: 出力対象の分子インデックスリスト。
            posRes: 残基ごとの座標リスト。
            nameAtom: 残基ごとの原子名リスト。
            molnames_orig: 各残基の分子名リスト。
        """

        ohead, ext = os.path.splitext(out_file)
        out_file = ohead + '.pdb'

        # # Export position of mol
        # head, ext = os.path.splitext(str(iname))

        molids = sorted(set(molnames_orig), key=molnames_orig.index)
        logger.debug('%s', molids)

        molnames = []
        for molname in molnames_orig:
            for i in range(len(molids)):
                if molname == molids[i]:
                    molnames.append(i)

        # print(molnames)
        # header
        # print(out_file)
        with open(out_file, "w", newline = "\n") as f:
            print("COMPND    " + out_file, file=f)
            print("AUTHOR    " + "GENERATED BY python script in abmptools", file=f)

        with open(out_file, "a+", newline = "\n") as f:
            tatomlab = 0
            reslab = 0
            logger.debug('%s', mollist)
            for i in mollist:
                molname = str(molnames[i])
                Molnum = i
                posMol = posRes[i]
                reslab += 1
                if reslab >=10000:
                    reslab -= 10000

                for j in range(len(posMol)):
                    tatomlab += 1
                    if tatomlab >= 100000:
                        tatomlab -= 100000

                    list = ["HETATM", str(tatomlab), nameAtom[i][j], molname.zfill(3), str(i + 1), '{:.3f}'.format(posMol[j][0]), '{:.3f}'.format(posMol[j][1]), '{:.3f}'.format(posMol[j][2]), "1.00", "0.00", nameAtom[i][j]]
                    print('{0[0]:<6}{0[1]:>5} {0[2]:>2}   {0[3]:>3}  {0[4]:>4}    {0[5]:>8}{0[6]:>8}{0[7]:>8}{0[8]:>6}{0[9]:>6}{0[10]:>12}'.format(list), file=f)
            # ATOM      1  H   UNK     1     -12.899  32.293   3.964  1.00  0.00           H

            print("END", file=f)

    def getcontact_rmapfmopdb(self, path: str, fname: str, oname: str) -> None:
        """PDBファイルから指定条件で近傍分子を切り出し、FMO計算用入力ファイルを生成する。

        Args:
            path: 作業ディレクトリのパス。
            fname: 入力PDBファイル名。
            oname: 出力ファイルのベース名。
        """
        molname = self.molname
        criteria = self.criteria
        tgtpos = self.tgtpos
        solutes = self.solutes
        solutes_charge = self.solutes_charge

        self.mainpath = '.'
        self.assignresname = False

        # print(path, molname, fname, oname, tgtpos, criteria)
        # (this func)totalRes, self.atmtypeRes, self.resnames, self.posRes, self.headRes, self.labRes, self.chainRes ,self.resnumRes ,self.codeRes ,self.occRes ,self.tempRes ,self.amarkRes ,self.chargeRes = self.getpdbinfo(fname)
        # (class)    totalRes, atmtypeRes, resnames, gatmlabRes, posRes,     headRes,    labRes,    chainRes , resnumRes ,  codeRes ,  occRes ,  tempRes ,  amarkRes ,  chargeRes

        self.readpdb(fname)

        posRes_orig     = copy.deepcopy(self.posRes)
        atmtypeRes_orig = copy.deepcopy(self.atmtypeRes)
        resnames_orig   = copy.deepcopy(self.resnames)
        headRes_orig    = copy.deepcopy(self.headRes)
        labRes_orig     = copy.deepcopy(self.labRes)
        chainRes_orig   = copy.deepcopy(self.chainRes)
        resnumRes_orig  = copy.deepcopy(self.resnumRes)
        codeRes_orig    = copy.deepcopy( self.codeRes)
        occRes_orig     = copy.deepcopy(self.occRes)
        tempRes_orig    = copy.deepcopy(self.tempRes)
        amarkRes_orig   = copy.deepcopy(self.amarkRes)
        chargeRes_orig  = copy.deepcopy(self.chargeRes)
        gatmlabRes_orig = copy.deepcopy(self.gatmlabRes)

        logger.info("totalmol: %s", self.totalRes)
        # getmolatomnum(_udf_, totalRes)
        logger.info("resnames_orig %s", resnames_orig)

        # 1. -- 切り取らない場合 --
        if self.cutmode == 'none':
            logger.info('none mode')
            # -- get neighbor mol --
            neighborindex = []
            for i in range(self.totalRes):
                neighborindex.append(i)

        # 2. -- cut sphere shape--
        if self.cutmode == 'sphere':
            logger.info('sphere mode')
            # -- get neighbor mol --
            neighborindex = []
            for i in range(len(self.posRes)):
                # check ion
                icflag = False
                if len(self.ionname) != 0:
                    for ion in self.ionname:
                        if  self.resnames[i].strip() == ion.strip():
                            if self.ionmode == 'del':
                                icflag = True
                                break
                            if self.ionmode == 'remain':
                                icflag = True
                                neighborindex.append(i)
                                # print('add mol', i, self.resnames[i])
                                break

                if icflag:
                    icflag = False
                    continue

                for j in range(len(self.posRes[i])):
                    dist = self.getdist(np.array(tgtpos), np.array(self.posRes[i][j]))
                    if dist < criteria:
                        neighborindex.append(i)
                        break

        # 3. -- cut cube shape
        if self.cutmode == 'cube':
            logger.info('cube mode')
            # -- get neighbor mol --
            tgtx = [tgtpos[0] - criteria[0], tgtpos[0] + criteria[0]]
            tgty = [tgtpos[1] - criteria[1], tgtpos[1] + criteria[1]]
            tgtz = [tgtpos[2] - criteria[2], tgtpos[2] + criteria[2]]

            neighborindex = []
            for i in range(len(self.posRes)):
                # check ion
                icflag = False
                if len(self.ionname) != 0:
                    for ion in self.ionname:
                        if  self.resnames[i].strip() == ion.strip():
                            if self.ionmode == 'del':
                                icflag = True
                                break
                            if self.ionmode == 'remain':
                                icflag = True
                                neighborindex.append(i)
                                # print('add mol', i, self.resnames[i])
                                break

                if icflag:
                    icflag = False
                    continue

                for j in range(len(self.posRes[i])):
                    if tgtx[0] < self.posRes[i][j][0] < tgtx[1] and \
                       tgty[0] < self.posRes[i][j][1] < tgty[1] and \
                       tgtz[0] < self.posRes[i][j][2] < tgtz[1]:
                        neighborindex.append(i)
                        break

        # 4. -- screening dist from solute --
        if self.cutmode == 'around':
            logger.info('around mode')
            # -- get neighbor mol --
            neighborindex = []
            # solutes = [0, 1]

            # i: residue(mol) index
            for i in self.solutes:
                neighborindex.append(i)
                # print('add mol', i, self.resnames[i])


            for i in range(len(self.posRes)):
                # check ion
                icflag = False
                if len(self.ionname) != 0:
                    for ion in self.ionname:
                        if  self.resnames[i].strip() == ion.strip():
                            if self.ionmode == 'del':
                                icflag = True
                                break
                            elif self.ionmode == 'remain':
                                icflag = True
                                neighborindex.append(i)
                                logger.debug('add mol %s %s', i, self.resnames[i])
                                break

                if icflag:
                    icflag = False
                    continue

                if i % 100 == 0:
                    logger.info('check mol %s', i)
                nextf = False
                # solute: skip
                if i in self.solutes:
                   continue
                # no solute: check dist between solute and no solute
                # j: atom loop in mol i
                for j in range(len(self.posRes[i])):
                    if nextf:
                        break
                    # k: solute mol id
                    for k in self.solutes:
                        if nextf:
                            break
                        # l: solute atom id in mol k
                        for l in range(len(self.posRes[k])):
                            dist = self.getdist(np.array(self.posRes[k][l]), np.array(self.posRes[i][j]))
                            if dist < criteria:
                                neighborindex.append(i)
                                # print('add mol', i, self.resnames[i])
                                nextf = True
                                break

        # 5. ion neautoral
        if self.cutmode == 'neutral':

            logger.info('### start neutral mode ###')
            logger.info('solutes_charge: %s', self.solutes_charge)
            logger.info('dist_criteria: %s', self.criteria)
            neighborindex = []
            # solutes = [0, 1]
            ionindex = []
            naindex = []
            clindex = []

            # i: residue(mol) index
            for i in range(len(self.posRes)):
                for ion in self.ionname:
                    if self.resnames[i].strip() == ion.strip():
                        ionindex.append(i)
                        if ion.strip().upper() == 'NA':
                            naindex.append(i)
                        elif ion.strip().upper() == 'CL':
                            clindex.append(i)
                        else:
                            logger.error('this elem is not support in this version')
                            sys.exit()
                        break
            # print('naindex', naindex)
            # print('clindex', clindex)

            for i in range(len(self.posRes)):
                if i not in ionindex:
                    neighborindex.append(i)

            # print(neighborindex)
            checknadists = []
            checkcldists = []
            checknaid = []
            checkclid = []
            minnaid = []
            minclid = []

            for i in ionindex:
                dists = []
                # j: atom loop in mol i
                for j in range(len(self.posRes[i])):
                    # k: solute mol id
                    for k in self.solutes:
                        # l: solute atom id in mol k
                        for l in range(len(self.posRes[k])):
                            dists.append(self.getdist(np.array(self.posRes[k][l]), np.array(self.posRes[i][j])))
                mindist = min(dists)
                # print(mindist)
                if mindist <= criteria:
                    if i in naindex:
                        logger.info('%s %s is near the solute %s angstrom', self.resnames[i], i, mindist)
                        # minnadists.append(mindist)
                        minnaid.append(i)
                    elif i in clindex:
                        logger.info('%s %s is near the solute %s angstrom', self.resnames[i], i, mindist)
                        # mincldists.append(mindist)
                        minclid.append(i)
                else:
                    if i in naindex:
                        checknadists.append(mindist)
                        checknaid.append(i)
                    elif i in clindex:
                        checkcldists.append(mindist)
                        checkclid.append(i)

            neighborindex += minnaid
            neighborindex += minclid

            cur_totchg = len(minnaid) - len(minclid) + solutes_charge
            logger.info('current total charge %s\n-> add %s nearest ions', cur_totchg, abs(cur_totchg))

            if cur_totchg == 0:
                pass
            else:
                if cur_totchg > 0:
                    checkdists = np.array(checkcldists)
                    checkids =  checkclid
                elif cur_totchg < 0:
                    checkdists = np.array(checknadists)
                    checkids = checknaid
                logger.info('ion-solute dist-list\n %s %s', checkdists, checkids)

                sortedids = checkdists.argsort()[:abs(cur_totchg)]
                for tgtid in sortedids:
                    neighborindex.append(checkids[tgtid])
                # print(neighborindex)

            # sort neighborindex
            neighborindex.sort()

        # print('neighborindex', neighborindex)
        # print vec
        # print("cellsize", cell)
        posRes = []
        atmtypeRes = []
        resnames = []
        headRes = []
        labRes = []
        chainRes = []
        resnumRes = []
        codeRes = []
        occRes = []
        tempRes = []
        amarkRes = []
        chargeRes = []
        gatmlabRes = []
        # for i in range(totalRes):
        for i in neighborindex:
        # for i in range(1, 2):
            posRes.append(self.posRes[i])
            atmtypeRes.append(self.atmtypeRes[i])
            resnames.append(self.resnames[i])
            headRes.append(self.headRes[i])
            labRes.append(self.labRes[i])
            chainRes.append(self.chainRes[i])
            resnumRes.append(self.resnumRes[i])
            codeRes.append(self.codeRes[i])
            occRes.append(self.occRes[i])
            tempRes.append(self.tempRes[i])
            amarkRes.append(self.amarkRes[i])
            chargeRes.append(self.chargeRes[i])
            gatmlabRes.append(self.gatmlabRes[i])

        self.posRes = posRes
        self.atmtypeRes = atmtypeRes
        self.resnames = resnames
        self.headRes = headRes
        self.labRes = labRes
        self.chainRes = chainRes
        self.resnumRes = resnumRes
        self.codeRes = codeRes
        self.occRes = occRes
        self.tempRes = tempRes
        self.amarkRes = amarkRes
        self.chargeRes = chargeRes
        self.gatmlabRes = gatmlabRes

        # get atomnum
        atomnums = []
        tgtmolnames = []
        for i in range(len(molname)):
            for j in range(self.totalRes):
            # for j in range(1):
                # print (molname[i], self.resnames[j])
                if molname[i] == resnames_orig[j]:
                    atomnums.append(len(posRes_orig[j]))
                    tgtmolnames.append(resnames_orig[j])
                    break
        logger.info('atomnums %s', atomnums)

        # print (posRes)
        opath = 'for_abmp'
        # oname = "mdout"
        os.makedirs(opath, exist_ok=True)

        # refresh
        index = [i for i in range(len(posRes))]
        self.exportardpdbfull(opath + '/' + oname, index)

        if self.is_xyz:
            self.exportardxyzfull(os.path.splitext(fname)[0] + '.xyz', opath + '/' + oname, gatmlabRes)

        self.make_abinput_rmap(tgtmolnames, resnames, oname, opath, atomnums)
        # monomer structure

    def movemoltranspdb(self, posVec: np.ndarray, transVec: np.ndarray) -> np.ndarray:
        """分子の座標を並進ベクトル分だけ平行移動する。

        Args:
            posVec: 移動前の座標ベクトル。
            transVec: 並進ベクトル。

        Returns:
            平行移動後の座標ベクトル。
        """
        # Parallel shift
        posVec = posVec + transVec
        return posVec

    def getpdbcell(self, fname: str) -> list[float]:
        """PDBファイルからCRYST1レコードのセルサイズを取得する。

        Args:
            fname: PDBファイルのパス。

        Returns:
            セルサイズ [a, b, c] のリスト。CRYST1が無い場合は空リスト。
        """
        with open(fname, 'r') as _fh:
            f = _fh.readlines()
        cell = []
        for line in f:
            items = line.split()
            if line[0:5] == 'CRYST':
                cell = [float(items[1]), float(items[2]), float(items[3])]
                break
        return cell

    def moveintocellpdb(self, posMol: list[list[list[float]]], totalRes: int, cell: list[float]) -> list[Any]:
        """各分子の重心がセル内に収まるように座標を周期境界条件で移動する。

        Args:
            posMol: 残基ごとの原子座標リスト。
            totalRes: 残基の総数。
            cell: セルサイズ [a, b, c]。

        Returns:
            セル内に移動された座標リスト。
        """
        # # Move into cell
        posintoMol = []
        cell2 = []
        logger.debug('%s', cell)
        for i in range(3):
            cell2.append(cell[i]/2.0)
        logger.debug('cell %s', cell)
        for i in range(totalRes):
            transVec = np.array([0., 0., 0.])  # x,y,z
            centerOfMol = self.getCenter(posMol[i])
            for j in range(3):
                if centerOfMol[j] > cell2[j]:
                    while centerOfMol[j] > cell2[j]:
                        centerOfMol[j] = centerOfMol[j] - cell[j]
                        transVec[j] = transVec[j] - cell[j]
                elif centerOfMol[j] < -cell2[j]:
                    while centerOfMol[j] < -cell2[j]:
                        centerOfMol[j] = centerOfMol[j] + cell[j]
                        transVec[j] = transVec[j] + cell[j]

            posintoMol.append(self.moveMolTrans(posMol[i], transVec))

        logger.info("move_done.")
        return posintoMol

    def getpdbinfowrap(self, fname: str) -> pdb_io:
        """PDBファイルの読み込みとセル情報の取得をまとめて行うラッパーメソッド。

        Args:
            fname: PDBファイルのパス。

        Returns:
            セル情報を含む自身のインスタンス。
        """
        # get pdbinfo
        self.readpdb(fname)
        logger.info('totalres %s', self.totalRes)
        # print('atomnameMol', atomnameMol)
        # print('resnames', molnames)
        logger.info('res_count %s', collections.Counter(self.resnames))
        # print('nummols', nummols)

        cellsize = self.getpdbcell(fname)
        self.cellsize = cellsize
    #
        if len(self.cellsize) == 0:
            self.cellsize = 0
            logger.info('cellinfo: None')
        else:
            logger.info('cellsize: %s', self.cellsize)
    #
        return self
