#A* -------------------------------------------------------------------
#B* This file contains source code for the PyMOL computer program
#C* copyright 1998-2000 by Warren Lyford Delano of DeLano Scientific.
#D* -------------------------------------------------------------------
#E* It is unlawful to modify or remove this copyright notice.
#F* -------------------------------------------------------------------
#G* Please see the accompanying LICENSE file for further information.
#H* -------------------------------------------------------------------
#I* Additional authors of this source file include:
#-* Scott Dixon, Metaphorics, LLC
#-*
#-*
#Z* -------------------------------------------------------------------

from __future__ import print_function

from chempy import feedback

import copy

default_extra = { # atomic number, and normal valency (for tinker)
    # NOTE: THIS SET IS ONLY USED IF THERE ARE NO TINKER RECORDS
    # IN THE PARAMETER FILE
    # bromine
    'BR' : [                 35 ,               1 ],
    # carbon
    'C'  : [                  6 ,               3 ],
    'CA' : [                  6 ,               3 ],
    'CB' : [                  6 ,               3 ],
    'CC' : [                  6 ,               3 ],
    'CF' : [                  6 ,               3 ],
    'CK' : [                  6 ,               3 ],
    'CM' : [                  6 ,               3 ],
    'CN' : [                  6 ,               3 ],
    'CQ' : [                  6 ,               3 ],
    'CR' : [                  6 ,               3 ],
    'CT' : [                  6 ,               4 ],
    'CV' : [                  6 ,               3 ],
    'CW' : [                  6 ,               3 ],
    'CY' : [                  6 ,               2 ],
    'CX' : [                  6 ,               2 ],
    'C5' : [                  6 ,               3 ],
    'C*' : [                  6 ,               3 ],
    # calcium
    'C0' : [                 20 ,               3 ],
    # chloride
    'Cl' : [                 17 ,               1 ],
    # fluorine
    'F'  : [                  9 ,               1 ],
    # hydrogen
    'H'  : [                  1 ,               1 ],
    'H1' : [                  1 ,               1 ],
    'H2' : [                  1 ,               1 ],
    'H3' : [                  1 ,               1 ],
    'H4' : [                  1 ,               1 ],
    'H5' : [                  1 ,               1 ],
    'HA' : [                  1 ,               1 ],
    'HC' : [                  1 ,               1 ],
    'HO' : [                  1 ,               1 ],
    'HP' : [                  1 ,               1 ],
    'HS' : [                  1 ,               1 ],
    'HW' : [                  1 ,               2 ],
    # iodine
    'I'  : [                 53 ,               1 ],
    # chloride anion
    'IM' : [                 17 ,               0 ],
    # sodium cation
    'IP' : [                 11 ,               0 ],
    # magnesium
    'MG' : [                 12 ,               0 ],
    # nitrogen
    'N'  : [                  7 ,               3 ],
    'N*' : [                  7 ,               2 ],
    'N2' : [                  7 ,               3 ],
    'N3' : [                  7 ,               4 ],
    'NA' : [                  7 ,               3 ],
    'NB' : [                  7 ,               2 ],
    'NC' : [                  7 ,               2 ],
    'NX' : [                  7 ,               2 ],
    'NZ' : [                  7 ,               3 ],
    #
    'OW' : [                  8 ,               2 ],
    'OH' : [                  8 ,               2 ],
    'OS' : [                  8 ,               2 ],
    'O'  : [                  8 ,               1 ],
    'O2' : [                  8 ,               1 ],
    'OM' : [                  8 ,               1 ],
    'OZ' : [                  8 ,               1 ],
    'P'  : [                 15 ,               4 ],
    'S'  : [                 16 ,               2 ],
    'SO'  : [                16 ,               4 ],
    'SX'  : [                16 ,               2 ],
    'SH' : [                 16 ,               2 ],
    # copper
    'CU' : [                 29 ,               0 ],
    # iron
    'FE' : [                 26 ,               0 ],
    # lithium
    'Li' : [                  3 ,               0 ],
    # potassium
    'K'  : [                 19 ,               0 ],
    # rubidium
    'Rb' : [                 37 ,               0 ],
    # cesium
    'Cs' : [                 55 ,               0 ],
    # calcium (alternate)
    'Ca' : [                 20 ,               0 ],
    # dummy
    'X ' : [                  6 ,               1 ],
    # zinc
    'Z0' : [                 30 ,               0 ],
    'Z4' : [                 30 ,               4 ],
    'Z5' : [                 30 ,               5 ],
    }

class Parameters:

    def __init__(self,fname):
        if feedback['actions']:
            print(' '+str(self.__class__)+': loading from "%s"...' % fname)
        f = open(fname)
        # skip
        l = f.readline()
        # read names & molecular weights
        self.type = []
        self.mw = {}
        while 1:
            l = f.readline().strip()
            if not len(l): break
            a2 = l[0:2].strip()
            self.type.append(a2)
            self.mw[a2] = [float(l[3:13]),l[34:].strip()]
        # skip 1
        l = f.readline()
        # read bonds
        self.bond = {}
        while 1:
            l = f.readline().strip()
            if not len(l): break
            a5 = l[0:5]
            self.bond[a5] = [float(l[5:12]),float(l[12:22]),
                                  l[22:].strip()]
        # read angles
        self.angle = {}
        while 1:
            l = f.readline().strip()
            if not len(l): break
            a5 = l[0:8]
            self.angle[a5] = [float(l[8:16]),float(l[16:28]),
                                  l[28:].strip()]
        # read torsion
        self.torsion = {}
        while 1:
            l = f.readline().strip()
            if not len(l): break
            a5 = l[0:11]
            if a5 in self.torsion:
                self.torsion[a5].extend(
                    [int(l[11:15]),
                     float(l[15:27]),
                     float(l[27:36]),
                     abs(int(float(l[40:52]))),
                     l[52:].strip()])
            else:
                self.torsion[a5] = [int(l[11:15]),
                                          float(l[15:27]),
                                          float(l[27:36]),
                                          abs(int(float(l[40:52]))),
                                          l[52:].strip()]
        # read impropers
        self.improper = {}
        while 1:
            l = f.readline().strip()
            if not len(l): break
            a5 = l[0:11]
            self.improper[a5] = [float(l[15:27]),
                                        float(l[27:40]),
                                        abs(int(float(l[40:51]))),
                                        l[51:].strip()]
        # skip
        while 1:
            l = f.readline().strip()
            if not len(l): break
        # read vdw equivalents
        self.vdw_eq = {}
        while 1:
            l = f.readline().strip()
            if not len(l): break
            a4 = l[0:4].strip()
            l = l[4:]
            while len(l):
                self.vdw_eq[l[0:4].strip()] = a4
                l = l[4:]
        # skip
        l = f.readline().strip()
        # read vdw parameters
        self.vdw = {}
        while 1:
            l = f.readline().strip()
            if not len(l): break
            l = '  ' + l
            a4 = l[0:4].strip()
            self.vdw[a4] =  [float(l[4:20]),
                                  float(l[20:37]),
                                  l[37:].strip()]

        # read extra tinker information if present
        self.extra = {}
        while 1:
            l = f.readline()
            if not l: break
            if l[0:6] == 'TINKER':
                self.extra[l[6:12].strip()]  = [
                    int(l[12:18]),
                    int(l[18:24])]
        if not self.extra:
                self.extra = default_extra
        # now generate redundant equivalents
        for a in self.vdw_eq.keys():
            self.vdw[a] = self.vdw[self.vdw_eq[a]]
        for a in list(self.bond.keys()):
            k = a[3:5]+'-'+a[0:2]
            self.bond[k] = self.bond[a]
        for a in list(self.angle.keys()):
            k = a[6:8]+'-'+a[3:5]+'-'+a[0:2]
            self.angle[k] = self.angle[a]

    def dump(self):
        for b in self.type:
            print(b)
        kees = list(self.mw.keys())
        kees.sort()
        for b in kees:
            print(b,self.mw[b])
        kees = list(self.vdw_eq.keys())
        kees.sort()
        for b in kees:
            print(b,self.vdw_eq[b])
        kees = list(self.vdw.keys())
        kees.sort()
        for b in kees:
            print(b,self.vdw[b])
        kees = list(self.bond.keys())
        kees.sort()
        for b in kees:
            print(b,self.bond[b])
        kees = list(self.angle.keys())
        kees.sort()
        for b in kees:
            print(b,self.angle[b])
        kees = list(self.torsion.keys())
        kees.sort()
        for b in kees:
            print(b,self.torsion[b])
        kees = list(self.improper.keys())
        kees.sort()
        for b in kees:
            print(b,self.improper[b])
        kees = list(self.vdw.keys())
        kees.sort()
        for b in kees:
            print(b,self.vdw[b])


class Topology:

    def __init__(self,model):
        self.model = model
        if feedback['actions']:
            print(' '+str(self.__class__)+': searching...')
        # get a connected version too
        cmodel = copy.deepcopy(model).convert_to_connected()
        # find atom types in molecule
        self.present = {}
        for a in model.atom:
            self.present[a.text_type] = 1
        # copy bonds
        self.bond = {}
        for b in model.bond:
            a0 = b.index[0]
            a1 = b.index[1]
            if a0 < a1:
                self.bond[(a0,a1)] = 1
            else:
                self.bond[(a1,a0)] = 1
        # find angles
        self.angle = {}
        ang = self.angle
        for b in model.bond:
            a0 = b.index[0]
            a1 = b.index[1]
            for c in cmodel.bond[a0]: # a0 in center
                a2 = c.index[0]
                if a2 not in (a0,a1): # outside atom
                    if a1 < a2:
                        an = (a1,a0,a2)
                    else:
                        an = (a2,a0,a1)
                    ang[an] = 1
                a2 = c.index[1]
                if a2 not in (a0,a1): # outside atom
                    if a1 < a2:
                        an = (a1,a0,a2)
                    else:
                        an = (a2,a0,a1)
                    ang[an] = 1
            for c in cmodel.bond[a1]: # a1 in center
                a2 = c.index[0]
                if a2 not in (a0,a1): # outside atom
                    if a0 < a2:
                        an = (a0,a1,a2)
                    else:
                        an = (a2,a1,a0)
                    ang[an] = 1
                a2 = c.index[1]
                if a2 not in (a0,a1): # outside atom
                    if a0 < a2:
                        an = (a0,a1,a2)
                    else:
                        an = (a2,a1,a0)
                    ang[an] = 1
        # find torsions
        self.torsion = {}
        tors = self.torsion
        for b in model.bond: # use bond as center of torsion
            a1 = b.index[0]
            a2 = b.index[1]
            for c in cmodel.bond[a1]:
                a0 = c.index[0]
                if a0 not in (a1,a2): # outside atom
                    for d in cmodel.bond[a2]:
                        a3 = d.index[0]
                        if a3 not in (a0,a1,a2): # outside atom
                            if a0 < a3:
                                to = (a0,a1,a2,a3)
                            else:
                                to = (a3,a2,a1,a0)
                            tors[to] = 1
                        a3 = d.index[1]
                        if a3 not in (a0,a1,a2): # outside atom
                            if a0 < a3:
                                to = (a0,a1,a2,a3)
                            else:
                                to = (a3,a2,a1,a0)
                            tors[to] = 1
                a0 = c.index[1]
                if a0 not in (a1,a2): # outside atom
                    for d in cmodel.bond[a2]:
                        a3 = d.index[0]
                        if a3 not in (a0,a1,a2): # outside atom
                            if a0 < a3:
                                to = (a0,a1,a2,a3)
                            else:
                                to = (a3,a2,a1,a0)
                            tors[to] = 1
                        a3 = d.index[1]
                        if a3 not in (a0,a1,a2): # outside atom
                            if a0 < a3:
                                to = (a0,a1,a2,a3)
                            else:
                                to = (a3,a2,a1,a0)
                            tors[to] = 1
        # find impropers (only autogenerates for atoms with 3 bonds)
        self.improper = {}
        impr = self.improper
        a2 = 0
        for a in model.atom: # a2 is the center atom
            bnd = cmodel.bond[a2]
            if len(bnd) == 3:
                lst = []
                for b in bnd:
                    at = b.index[0]
                    if at == a2:
                        at = b.index[1]
                    lst.append(at)
                lst.sort()
                impr[(lst[0],lst[1],a2,lst[2])] = 1
            a2 = a2 + 1

        if feedback['actions']:
            print(' '+str(self.__class__)+': found:')
            print(' '+str(self.__class__)+':    types       %6d' % (
                len(list(self.present.keys()))))
            print(' '+str(self.__class__)+':    bonds       %6d' % (
                len(list(self.bond.keys()))))
            print(' '+str(self.__class__)+':    angles      %6d' % (
                len(list(self.angle.keys()))))
            print(' '+str(self.__class__)+':    torsions    %6d' % (
                len(list(self.torsion.keys()))))
            print(' '+str(self.__class__)+':    impropers   %6d' % (
                len(list(self.improper.keys()))))

    def dump(self):
        kees = list(self.present.keys())
        kees.sort()
        for b in kees:
            print(b)
        kees = list(self.bond.keys())
        kees.sort()
        for b in kees:
            print(b)
        kees = list(self.angle.keys())
        kees.sort()
        for b in kees:
            print(b)
        kees = list(self.torsion.keys())
        kees.sort()
        for b in kees:
            print(b)
        kees = list(self.improper.keys())
        kees.sort()
        for b in kees:
            print(b)

    def get_list(self):
        return [list(self.bond.keys()),
                  list(self.angle.keys()),
                  list(self.torsion.keys()),
                  list(self.improper.keys())]

class Subset:

    def __init__(self,par,top):
        if feedback['actions']:
            print(' '+str(self.__class__)+': applying parameter set to topology...')
        self.model = top.model
        self.present = copy.deepcopy(top.present)
        atype = []
        for a in self.model.atom:
            atype.append(a.text_type)
        # extra tinker info (atomic number, valency)
        self.miss_extra = []
        self.extra = {}
        s_extra = self.extra
        present = top.present
        p_extra = par.extra
        for kee in present.keys():
            if kee in p_extra:
                s_extra[kee] = p_extra[kee]
            else:
                self.miss_extra.append(kee)
        # molecular weight
        self.miss_mw = []
        self.mw = {}
        s_mw = self.mw
        present = top.present
        p_mw = par.mw
        for kee in present.keys():
            if kee in p_mw:
                s_mw[kee] = p_mw[kee]
            else:
                self.miss_mw.append(kee)

        # van der waals
        self.miss_vdw = []
        self.vdw = {}
        s_vdw = self.vdw
        present = top.present
        p_vdw = par.vdw
        for kee in present.keys():
            if kee in p_vdw:
                s_vdw[kee] = p_vdw[kee]
            else:
                self.miss_vdw.append(kee)
        # bonds
        self.miss_bond = []
        self.bond = {}
        s_bond = self.bond
        bond = top.bond
        p_bond = par.bond
        for a in list(bond.keys()):
            kee = "%-2s-%-2s" % (atype[a[0]],atype[a[1]])
            if kee in p_bond:
                s_bond[kee] = p_bond[kee]
            else:
                self.miss_bond.append((a,kee))
        # angles
        self.miss_angle = []
        self.angle = {}
        s_angle = self.angle
        angle = top.angle
        p_angle = par.angle
        for a in angle.keys():
            kee = "%-2s-%-2s-%-2s" % (atype[a[0]],atype[a[1]],atype[a[2]])
            if kee in p_angle:
                s_angle[kee] = p_angle[kee]
            else:
                self.miss_angle.append((a,kee))
        # torsions
        self.miss_torsion = []
        self.torsion = {}
        s_torsion = self.torsion
        torsion = top.torsion
        p_torsion = par.torsion
        for a in list(torsion.keys()):
            at0=atype[a[0]]
            at1=atype[a[1]]
            at2=atype[a[2]] # center atom
            at3=atype[a[3]]
            while 1: # not a real loop, just "else" avoidance
                kee1 = "%-2s-%-2s-%-2s-%-2s" % (at0,at1,at2,at3)
                if kee1 in p_torsion:
                    s_torsion[kee1] = p_torsion[kee1]
                    break
                kee2 = "%-2s-%-2s-%-2s-%-2s" % (at3,at2,at1,at0)
                if kee2 in p_torsion:
                    s_torsion[kee2] = p_torsion[kee2]
                    break
                kee = "X -%-2s-%-2s-X " % (at1,at2)
                if kee in p_torsion:
                    s_torsion[kee1] = p_torsion[kee]
                    break
                kee = "X -%-2s-%-2s-X " % (at2,at1)
                if kee in p_torsion:
                    s_torsion[kee2] = p_torsion[kee]
                    break
                self.miss_torsion.append((a,kee1))
                break
        # impropers
        self.miss_improper = []
        self.improper = {}
        s_improper = self.improper
        improper = top.improper
        p_improper = par.improper
        for a in improper.keys():
            at0=atype[a[0]]
            at1=atype[a[1]]
            at2=atype[a[2]] # center atom
            at3=atype[a[3]]
            while 1: # not a real loop, just "else" avoidance

                kee1 = "%-2s-%-2s-%-2s-%-2s" % (at1,at3,at2,at0)
                if kee1 in p_improper:
                    s_improper[kee1] = p_improper[kee1]
                    break
                kee2 = "%-2s-%-2s-%-2s-%-2s" % (at3,at1,at2,at0)
                if kee2 in p_improper:
                    s_improper[kee2] = p_improper[kee2]
                    break

                kee3 = "%-2s-%-2s-%-2s-%-2s" % (at0,at3,at2,at1)
                if kee3 in p_improper:
                    s_improper[kee3] = p_improper[kee3]
                    break
                kee4 = "%-2s-%-2s-%-2s-%-2s" % (at3,at0,at2,at1)
                if kee4 in p_improper:
                    s_improper[kee4] = p_improper[kee4]
                    break

                kee5 = "%-2s-%-2s-%-2s-%-2s" % (at0,at1,at2,at3)
                if kee5 in p_improper:
                    s_improper[kee5] = p_improper[kee5]
                    break
                kee6 = "%-2s-%-2s-%-2s-%-2s" % (at1,at0,at2,at3)
                if kee6 in p_improper:
                    s_improper[kee6] = p_improper[kee6]
                    break

                kee = "X -%-2s-%-2s-%-2s" % (at3,at2,at0)
                if kee in p_improper:
                    s_improper[kee1] = p_improper[kee]
                    break
                kee = "X -%-2s-%-2s-%-2s" % (at1,at2,at0)
                if kee in p_improper:
                    s_improper[kee2] = p_improper[kee]
                    break
                kee = "X -%-2s-%-2s-%-2s" % (at3,at2,at1)
                if kee in p_improper:
                    s_improper[kee3] = p_improper[kee]
                    break
                kee = "X -%-2s-%-2s-%-2s" % (at0,at2,at1)
                if kee in p_improper:
                    s_improper[kee4] = p_improper[kee]
                    break
                kee = "X -%-2s-%-2s-%-2s" % (at1,at2,at3)
                if kee in p_improper:
                    s_improper[kee5] = p_improper[kee]
                    break
                kee = "X -%-2s-%-2s-%-2s" % (at0,at2,at3)
                if kee in p_improper:
                    s_improper[kee6] = p_improper[kee]
                    break

                kee = "X -X -%-2s-%-2s" % (at2,at0)
                if kee in p_improper:
                    s_improper[kee1] = p_improper[kee]
                    break
                kee = "X -X -%-2s-%-2s" % (at2,at1)
                if kee in p_improper:
                    s_improper[kee3] = p_improper[kee]
                    break
                kee = "X -X -%-2s-%-2s" % (at2,at3)
                if kee in p_improper:
                    s_improper[kee5] = p_improper[kee]
                    break
                self.miss_improper.append((a,kee1))
                break

        if feedback['actions']:
            print(' '+str(self.__class__)+': missing:')
            print(' '+str(self.__class__)+':    mol. wts.         %6d' % (
                len(self.miss_mw)))
            print(' '+str(self.__class__)+':    vdw               %6d' % (
                len(self.miss_vdw)))
            print(' '+str(self.__class__)+':    bonds             %6d' % (
                len(self.miss_bond)))
            print(' '+str(self.__class__)+':    angles            %6d' % (
                len(self.miss_angle)))
            print(' '+str(self.__class__)+':    torsions          %6d' % (
                len(self.miss_torsion)))
            print(' '+str(self.__class__)+':    impropers         %6d (usually okay)' % (
                len(self.miss_improper)))
            print(' '+str(self.__class__)+':    extra tinker info %6d' % (
                len(self.miss_extra)))

    def dump(self):
        kees = list(self.mw.keys())
        kees.sort()
        for b in kees:
            print(b,self.mw[b])
        kees = list(self.bond.keys())
        kees.sort()
        for b in kees:
            print(b,self.bond[b])
        kees = list(self.angle.keys())
        kees.sort()
        for b in kees:
            print(b,self.angle[b])
        kees = list(self.torsion.keys())
        kees.sort()
        for b in kees:
            print(b,self.torsion[b])
        kees = list(self.improper.keys())
        kees.sort()
        for b in kees:
            print(b,self.improper[b])
        kees = list(self.vdw.keys())
        kees.sort()
        for b in kees:
            print(b,self.vdw[b])

    def dump_missing(self,impropers=0):
        if len(self.miss_mw):
            print(' '+str(self.__class__)+': missing molecular weights...')
        for b in self.miss_mw:
            print(" ",b)
        if len(self.miss_vdw):
            print(' '+str(self.__class__)+': missing van der Waalss...')
        for b in self.miss_vdw:
            print(" ",b)
        if len(self.miss_bond):
            print(' '+str(self.__class__)+': missing bonds...')
        for b in self.miss_bond:
            print(" ",b)
        if len(self.miss_angle):
            print(' '+str(self.__class__)+': missing angless...')
        for b in self.miss_angle:
            print(" ",b)
        if len(self.miss_torsion):
            print(' '+str(self.__class__)+': missing torsions...')
        for b in self.miss_torsion:
            print(" ",b)
        if impropers:
            if len(self.miss_improper):
                print(' '+str(self.__class__)+': missing impropers...')
            for b in self.miss_improper:
                print(" ",b)

    def complete(self):
        if not ( len(self.miss_mw) or
                    len(self.miss_vdw) or
                    len(self.miss_bond) or
                    len(self.miss_angle) or
                    len(self.miss_torsion) ):
            return 1
        else:
            return 0

    def assign_types(self):
        c = 0
        self.mapping = {}
        map = self.mapping
        kees = list(self.present.keys())
        label = []
        type = []
        kees.sort()
        for a in kees:
            c = c + 1
            st = str(c)
            label.append(st)
            type.append(a)
            map[a] = st
        # assign numeric types
        for a in self.model.atom:
            a.numeric_type = map[a.text_type]

    def write_tinker_prm(self,fname,proofread=None,smooth=None):
        c = 0
        self.mapping = {}
        map = self.mapping
        kees = list(self.present.keys())
        label = []
        type = []
        kees.sort()
        if proofread:
            for a in kees:
                label.append(a)
                type.append(a)
                map[a] = a
        else:
            for a in kees:
                c = c + 1
                st = str(c)
                label.append(st)
                type.append(a)
                map[a] = st
        # assign numeric types as per the parameter we're writing
        for a in self.model.atom:
            a.numeric_type = map[a.text_type]
        f = open(fname,'w')
        if smooth:
            f.write('''
forcefield              SMOOTH AMBER
vdwtype                 GAUSSIAN
gausstype               LJ-2
''')
        else:
            f.write('''
forcefield              AMBER95
vdwtype                 LENNARD-JONES
''')
        f.write('''
radiusrule              ARITHMETIC
radiustype              R-MIN
radiussize              RADIUS
epsilonrule             GEOMETRIC
vdw-14-use
vdw-scale               2.0
chg-14-use
chg-scale               1.2
dielectric              1.0
''')

        # types
        for c in range(len(self.present)):
            at = type[c]
            f.write("atom %6s %6s    %-2s      %-25s %3d %10.3f%6d\n" % (
                label[c],label[c],at,'"'+self.mw[at][1][0:23]+'"',
                self.extra[at][0],
                self.mw[at][0],self.extra[at][1]))
        # van der waals
        for c in range(len(self.present)):
            at = type[c]
            f.write("vdw   %8s          %10.4f %10.4f\n" % (
                map[at],self.vdw[at][0],self.vdw[at][1]))
        # bonds
        bond = {}
        for a in list(self.bond.keys()):
            kee = (map[a[0:2].strip()],map[a[3:5].strip()])
            bond[kee] = a
        kees = list(bond.keys())
        kees.sort()
        for a in kees:
            kee = bond[a]
            f.write("bond    %6s%5s     %10.1f %10.4f\n" % (
                a[0],a[1],self.bond[kee][0],self.bond[kee][1]))
        # angles
        angle = {}
        for a in list(self.angle.keys()):
            kee = (map[a[0:2].strip()],
                     map[a[3:5].strip()],
                     map[a[6:8].strip()],
                     )
            angle[kee] = a
        kees = list(angle.keys())
        kees.sort()
        for a in kees:
            kee = angle[a]
            f.write("angle    %5s%5s%5s%9.2f    %8.2f\n" % (
                a[0],a[1],a[2],self.angle[kee][0],self.angle[kee][1]))
        # impropers
        if not smooth:
            improper = {}
            for a in self.improper.keys():
                kee = (map[a[0:2].strip()],
                         map[a[3:5].strip()],
                         map[a[6:8].strip()],
                         map[a[9:11].strip()],
                         )
                improper[kee] = a
            kees = list(improper.keys())
            kees.sort()
            for a in kees:
                kee = improper[a]
                f.write("imptors  %5s%5s%5s%5s     %10.3f%7.1f%3d\n" % (
                    a[0],a[1],a[2],a[3],self.improper[kee][0],
                    self.improper[kee][1],self.improper[kee][2]))
        else:
            improper = {}
            for a in self.improper.keys():
                kee = (map[a[0:2].strip()],
                         map[a[3:5].strip()],
                         map[a[6:8].strip()],
                         map[a[9:11].strip()],
                         )
                improper[kee] = a
            kees = list(improper.keys())
            kees.sort()
            for a in kees:
                kee = improper[a]
                f.write("improper %5s%5s%5s%5s      %10.2f       0.00\n" % (
                    a[2],a[0],a[1],a[3],self.improper[kee][0]*3.5))
        # torsions
        torsion = {}
        for a in self.torsion.keys():
            kee = (map[a[0:2].strip()],
                     map[a[3:5].strip()],
                     map[a[6:8].strip()],
                     map[a[9:11].strip()],
                     )
            torsion[kee] = a
        kees = list(torsion.keys())
        kees.sort()
        for a in kees:
            kee = torsion[a]
            st = "torsion  %5s%5s%5s%5s     " % (
                a[0],a[1],a[2],a[3])
            lst = self.torsion[kee]
            div = lst[0]
            lst = lst[1:]
            while len(lst):
                st = st + "%10.3f%7.1f%3d  " % (
                    lst[0]/div,lst[1],lst[2])
                lst = lst[5:]
            while len(st)>79:
                st = st.replace('  ',' ')
            f.write(st+"\n")
        # null charge records
        for c in range(len(self.present)):
            a = label[c]
            f.write("charge  %6s    %10.4f\n" % (a,0.0))
        f.close()


    def get_list(self):
        list = []
        c = 0
        self.mapping = {}
        map = self.mapping
        kees = list(self.present.keys())
        label = []
        type = []
        kees.sort()
        for a in kees:
            c = c + 1
            st = str(c)
            type.append(a)
            map[a] = c
        # assign numeric types as per the parameter we're writing
        for a in self.model.atom:
            a.numeric_type = map[a.text_type]
        # types
        typ_list = []
        for c in range(len(self.present)):
            at = type[c]
            typ_list.append((map[at],self.extra[at][0],self.mw[at][0],self.extra[at][1]))
        list.append(typ_list)
        # van der waals
        vdw_list = []
        for c in range(len(self.present)):
            at = type[c]
            vdw_list.append((map[at],self.vdw[at][0],self.vdw[at][1]))
        list.append(vdw_list)
        # bonds
        bnd_list = []
        bond = {}
        for a in list(self.bond.keys()):
            kee = (map[a[0:2].strip()],map[a[3:5].strip()])
            bond[kee] = a
        kees = list(bond.keys())
        kees.sort()
        for a in kees:
            kee = bond[a]
            bnd_list.append((a[0],a[1],self.bond[kee][0],self.bond[kee][1]))
        list.append(bnd_list)
        # angles
        ang_list = []
        angle = {}
        for a in self.angle.keys():
            kee = (map[a[0:2].strip()],
                     map[a[3:5].strip()],
                     map[a[6:8].strip()],
                     )
            angle[kee] = a
        kees = list(angle.keys())
        kees.sort()
        for a in kees:
            kee = angle[a]
            ang_list.append((a[0],a[1],a[2],self.angle[kee][0],self.angle[kee][1]))
        list.append(ang_list)
        # impropers
        imp_list = []
        improper = {}
        for a in self.improper.keys():
            kee = (map[a[0:2].strip()],
                     map[a[3:5].strip()],
                     map[a[6:8].strip()],
                     map[a[9:11].strip()],
                     )
            improper[kee] = a
        kees = list(improper.keys())
        kees.sort()
        for a in kees:
            kee = improper[a]
            imp_list.append((a[0],a[1],a[2],a[3],self.improper[kee][0],
                self.improper[kee][1],self.improper[kee][2]))
        list.append(imp_list)
        # torsions
        tor_list = []
        torsion = {}
        for a in self.torsion.keys():
            kee = (map[a[0:2].strip()],
                     map[a[3:5].strip()],
                     map[a[6:8].strip()],
                     map[a[9:11].strip()],
                     )
            torsion[kee] = a
        kees = list(torsion.keys())
        kees.sort()
        for a in kees:
            kee = torsion[a]
            tor = [ a[0],a[1],a[2],a[3] ]
            lst = self.torsion[kee]
            div = lst[0]
            lst = lst[1:]
            while len(lst):
                tor.extend([lst[0]/div,lst[1],lst[2]])
                lst = lst[5:]
            tor_list.append(tor)
        list.append(tor_list)
        return list
