#!/usr/bin/env python

# pmx  Copyright Notice
# ============================
#
# The pmx source code is copyrighted, but you can freely use and
# copy it as long as you don't change or remove any of the copyright
# notices.
#
# ----------------------------------------------------------------------
# pmx is Copyright (C) 2006-2013 by Daniel Seeliger
#
#                        All Rights Reserved
#
# Permission to use, copy, modify, distribute, and distribute modified
# versions of this software and its documentation for any purpose and
# without fee is hereby granted, provided that the above copyright
# notice appear in all copies and that both the copyright notice and
# this permission notice appear in supporting documentation, and that
# the name of Daniel Seeliger not be used in advertising or publicity
# pertaining to distribution of the software without specific, written
# prior permission.
#
# DANIEL SEELIGER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS.  IN NO EVENT SHALL DANIEL SEELIGER BE LIABLE FOR ANY
# SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# ----------------------------------------------------------------------

"""
This module contains classes and functions to read gromacs forcefield files.
"""

import sys
import os
from collections import OrderedDict
from numpy import shape
from . import _pmx as _p
from .parser import kickOutComments, readSection
from .atom import Atom
from .molecule import Molecule
from .ffparser import BondedParser, NBParser, RTPParser
from .utils import get_ff_path


def TR(s):
    print("pmx.forcefield_> " + s)


def cpp_parse_file(fn,  itp=False, ffpath=None, cpp_defs=[],
                   cpp_path=[os.environ.get('GMXLIB'), '%s/top' % os.environ.get('GMXDATA')]):

    """Expands a gromacs topology by including all force field files etc.

    Parameters
    ----------
    fn : str
        topology file
    cpp_defs : list, optional
        ???
    cpp_path : list, optional
        paths to force fields library
    """
    defs = []
    incs = []
    for d in cpp_defs:
        defs.append('-D%s' % d)
    for i in cpp_path:
        incs.append('-I%s' % i)

    if itp is True:
        cmd1 = 'cpp -traditional %s %s %s ' % (' '.join(defs), ' '.join(incs), fn)
        l1 = os.popen(cmd1, 'r').readlines()
        if ffpath is not None:
            ffname = ffpath + '/forcefield.itp'
            cmd2 = 'cpp -traditional %s %s %s ' % (' '.join(defs), ' '.join(incs), ffname)
            l2 = os.popen(cmd2, 'r').readlines()
            return(l1+l2)
        else:
            return l1
    elif itp is False:
        cmd = 'cpp -traditional %s %s %s ' % (' '.join(defs), ' '.join(incs), fn)
        return os.popen(cmd, 'r').readlines()


# ==============================================================================
# CLASSES
# ==============================================================================
# FIXME/QUESTION: is "old" version still needed? If not, we could simplify the
# code by removing all if statements and arguments related to this
class TopolBase:
    """Base class for topology objects. It reads/writes topology files.
    """

    def __init__(self, filename=None, version='old'):
        self.filename = filename
        self.version = version
        self.defaults = ''
        self.header = []
        self.atomtypes = []
        self.nonbond_params = []
        self.atoms = []
        self.residues = []
        self.name = ''
        self.nrexcl = 0
        self.bonds = []
        self.constraints = []
        self.have_constraints = False
        self.pairs = []
        self.cmap = []
        self.angles = []
        self.dihedrals = []
        self.virtual_sites2 = []
        self.virtual_sites3 = []
        self.virtual_sites4 = []
        self.has_vsites2 = False
        self.has_vsites3 = False
        self.has_vsites4 = False
        self.has_posre = False
        self.has_exclusions = False
        self.exclusions = []
        self.posre = []
        self.molecules = []
        self.footer = []
        self.system = ''
        self.ii = {}  # dict for intermolecular interactions
        self.qA = 0.
        self.qB = 0.
        self.include_itps = []
        self.forcefield = ''
        self.is_itp = True
        if filename!=None:
            if os.path.splitext(filename)[1] == '.itp':
                self.is_itp = True
            else:
                self.is_itp = False
            self.read()

    # ==============
    # read functions
    # ==============
    def read(self):
        lines = open(self.filename).readlines()
        lines = kickOutComments(lines, ';')
        self.read_defaults(lines)
        self.read_header(lines)
        self.read_footer(lines)
        posre_sections = self.get_posre_section(lines)
        self.read_include_itps(lines)
        lines = kickOutComments(lines, '#')
        self.read_moleculetype(lines)
        if self.name:  # atoms, bonds, ... section
            self.read_atomtypes(lines)
            self.read_nonbond_params(lines)
            self.read_atoms(lines)
            self.read_bonds(lines)
            self.read_constraints(lines)
            self.read_pairs(lines)
            self.read_angles(lines)
            self.read_dihedrals(lines)
            self.read_cmap(lines)
            self.read_vsites2(lines)
            self.read_vsites3(lines)
            self.read_vsites4(lines)
            self.read_exclusions(lines)
            self.read_intermolecular_interactions(lines)
            if self.has_posre:
                self.read_posre(posre_sections)
            self.__make_residues()
        else: # maybe only atomtypes are available
            self.read_atomtypes(lines)
        if not self.is_itp:
            self.read_system(lines)
            self.read_molecules(lines)

    def __atom_from_top_line(self, line):
        entr = line.split()
        idx = int(entr[0])
        atomtype = entr[1]
        resnr = int(entr[2])
        resname = entr[3]
        name = entr[4]
        cgnr = int(entr[5])
        q = float(entr[6])
        m = float(entr[7])
        try:
            atomtypeB = entr[8]
            qB = float(entr[9])
            mB = float(entr[10])
        except:
            atomtypeB = None
            qB = None
            mB = None
        a = Atom(id=idx, atomtype=atomtype,
                 resnr=resnr, resname=resname,
                 name=name, cgnr=cgnr, q=q,
                 m=m, atomtypeB=atomtypeB,
                 qB=qB, mB=mB)
        return a

    def __make_residues(self):
        cur_mol = None
        mol = None
        for atom in self.atoms:
            if atom.resnr == cur_mol:
                if mol:
                    mol.atoms.append(atom)
                else:
                    mol = Molecule()
                    cur_mol = atom.resnr
                    mol.resname = atom.resname
                    mol.id = cur_mol
                    mol.atoms.append(atom)
            else:
                if mol:
                    self.residues.append(mol)
                    mol = Molecule()
                    cur_mol = atom.resnr
                    mol.resname = atom.resname
                    mol.id = cur_mol
                    mol.atoms.append(atom)
                else:
                    mol = Molecule()
                    cur_mol = atom.resnr
                    mol.resname = atom.resname
                    mol.id = cur_mol
                    mol.atoms.append(atom)
        self.residues.append(mol)
        for r in self.residues:
            atom.molecule = r

    def read_system(self, lines):
        lst = readSection(lines, '[ system ]', '[')
        self.system = lst[0].strip()

    def read_defaults(self, lines):
        lst = readSection(lines, '[ defaults ]', '[')
        if lst:
            self.defaults = lst[0].strip()

    def read_molecules(self, lines):
        lst = readSection(lines, '[ molecules ]', '[')
        self.molecules = []
        for line in lst:
            entr = line.split()
            self.molecules.append([entr[0], int(entr[1])])

    def read_moleculetype(self, lines):
        l = readSection(lines, '[ moleculetype ]', '[')
        if l:
            self.name, self.nrexcl = l[0].split()[0], int(l[0].split()[1])

    def read_header(self, lines):
        '''Reads the include statemets at the top of the topology file.
        '''
        # TODO/FIXME: this header reader does not work when the topology starts
        # with a defaults section. All include itp statemets are ignored
        # a fix would be having more specialised reader/writers for include
        # statements
        for line in lines:
            if not line.strip().startswith('[') and \
                   not line.strip().startswith('#ifdef POSRES'):
                self.header.append(line.rstrip())
            else:
                break

    def read_footer(self, lines):
        # read all lines in between #ifdef and [ system ]
        for line in lines:
            if line.strip().startswith('#ifdef POSRES'):
                idx = lines.index(line)
                self.footer = [l.rstrip() for l in lines[idx:]]
                break
        try:
            idx = self.footer.index('[ system ]')
            self.footer = self.footer[:idx]
        except:
            pass

    def read_atomtypes(self, lines):
        lst = readSection(lines, '[ atomtypes ]', '[')
        for line in lst:
            atomtype = dict()
            elements = line.split()
            # take into account there can be 3 formats for atomtypes
            if len(elements) == 6:
                atomtype['name'] = str(elements[0])
                atomtype['bond_type'] = str(elements[0])
                atomtype['mass'] = float(elements[1])
                atomtype['charge'] = float(elements[2])
                atomtype['ptype'] = str(elements[3])
                atomtype['sigma'] = float(elements[4])
                atomtype['epsilon'] = float(elements[5])
                self.atomtypes.append(atomtype)
            elif len(elements) == 7:
                atomtype['name'] = str(elements[0])
                atomtype['bond_type'] = str(elements[1])
                atomtype['mass'] = float(elements[2])
                atomtype['charge'] = float(elements[3])
                atomtype['ptype'] = str(elements[4])
                atomtype['sigma'] = float(elements[5])
                atomtype['epsilon'] = float(elements[6])
                self.atomtypes.append(atomtype)
            elif len(elements) == 8:
                atomtype['name'] = str(elements[0])
                atomtype['bond_type'] = str(elements[1])
                atomtype['anum'] = int(elements[2])
                atomtype['mass'] = float(elements[3])
                atomtype['charge'] = float(elements[4])
                atomtype['ptype'] = str(elements[5])
                atomtype['sigma'] = float(elements[6])
                atomtype['epsilon'] = float(elements[7])
                self.atomtypes.append(atomtype)
            else:
                raise ValueError('format of atomtypes section not recognised')

    def read_nonbond_params(self, lines):
        lst = readSection(lines, '[ nonbond_params ]', '[')
        for line in lst:
            param = dict()
            elements = line.split()

            if len(elements) == 5:
                param['a1'] = str(elements[0])
                param['a2'] = str(elements[1])
                param['func'] = int(elements[2])
                param['sigma'] = float(elements[3])
                param['epsilon'] = float(elements[4])
                self.nonbond_params.append(param)
            else:
                raise ValueError('format of nonbond_params section not recognised')

    def read_atoms(self, lines):
        lst = readSection(lines, '[ atoms ]', '[')
        self.atoms = []
        for line in lst:
            a = self.__atom_from_top_line(line)
            self.atoms.append(a)

    def read_bonds(self, lines):
        # filter out intermolecular_interactions section
        idx = [i for i, l in enumerate(lines) if 'intermolecular_interactions' in l]
        if len(idx) == 1:
            lines = lines[:idx[0]]

        lst = readSection(lines, '[ bonds ]', '[')
        self.bonds = []
        for line in lst:
            entries = line.split()
            if len(entries) == 3:
                idx = [int(x) for x in line.split()]
                self.bonds.append([self.atoms[idx[0]-1], self.atoms[idx[1]-1],
                                   idx[2]])
            elif len(entries) == 5:
                idx = [int(x) for x in entries[:3]]
                l = float(entries[3])
                k = float(entries[4])
                self.bonds.append([self.atoms[idx[0]-1], self.atoms[idx[1]-1],
                                   idx[2], [l, k]])

            elif len(entries) == 7:
                idx = [int(x) for x in entries[:3]]
                lA = float(entries[3])
                kA = float(entries[4])
                lB = float(entries[5])
                kB = float(entries[6])
                self.bonds.append([self.atoms[idx[0]-1], self.atoms[idx[1]-1],
                                   idx[2], [idx[2], lA, kA], [idx[2], lB, kB]])

    def read_pairs(self, lines):
        lst = readSection(lines, '[ pairs ]', '[')
        self.pairs = []
        for line in lst:
            idx = [int(x) for x in line.split()]
            self.pairs.append([self.atoms[idx[0]-1], self.atoms[idx[1]-1],
                               idx[2]])

    def read_constraints(self, lines):
        lst = readSection(lines, '[ constraints ]', '[')
        self.constraints = []
        for line in lst:
            idx = [int(x) for x in line.split()]
            self.constraints.append([self.atoms[idx[0]-1],
                                     self.atoms[idx[1]-1],
                                     idx[2]])
        if self.constraints:
            self.have_constraints = True

    def read_angles(self, lines):
        # filter out intermolecular_interactions section
        idx = [i for i, l in enumerate(lines) if 'intermolecular_interactions' in l]
        if len(idx) == 1:
            lines = lines[:idx[0]]

        lst = readSection(lines, '[ angles ]', '[')
        for line in lst:
            entries = line.split()
            if len(entries) == 4: # only atoms and angle type
                idx = [int(x) for x in line.split()]
                self.angles.append([self.atoms[idx[0]-1], self.atoms[idx[1]-1],
                                    self.atoms[idx[2]-1], idx[3]])
            elif len(entries) == 6: # angle with parameters for type 1, stateA only
                idx = [int(x) for x in entries[:4]]
                l = float(entries[4])
                k = float(entries[5])
                self.angles.append([self.atoms[idx[0]-1], self.atoms[idx[1]-1],
                                    self.atoms[idx[2]-1], idx[3], [l, k]])
            elif len(entries) == 8 and entries[3] == '1': # type 1 for states A and B
                idx = [int(x) for x in entries[:4]]
                lA = float(entries[4])
                kA = float(entries[5])
                lB = float(entries[6])
                kB = float(entries[7])
                self.angles.append([self.atoms[idx[0]-1], self.atoms[idx[1]-1],
                                    self.atoms[idx[2]-1], idx[3],
                                    [idx[3], lA, kA], [idx[3], lB, kB]])
            elif len(entries) == 8 and entries[3] == '5': # type 5 with parameters, stateA only
                idx = [int(x) for x in entries[:4]]
                lA1 = float(entries[4])
                kA1 = float(entries[5])
                lA2 = float(entries[6])
                kA2 = float(entries[7])
                self.angles.append([self.atoms[idx[0]-1],
                                    self.atoms[idx[1]-1],
                                    self.atoms[idx[2]-1],
                                    idx[3],
                                    [idx[3], lA1, kA1, lA2, kA2]])
            elif len(entries) == 12: # type 5 with parameters, states A and B
                idx = [int(x) for x in entries[:4]]
                lA1 = float(entries[4])
                kA1 = float(entries[5])
                lA2 = float(entries[6])
                kA2 = float(entries[7])
                lB1 = float(entries[8])
                kB1 = float(entries[9])
                lB2 = float(entries[10])
                kB2 = float(entries[11])
                self.angles.append([self.atoms[idx[0]-1],
                                    self.atoms[idx[1]-1],
                                    self.atoms[idx[2]-1],
                                    idx[3],
                                    [idx[3], lA1, kA1, lA2, kA2],
                                    [idx[3], lB1, kB1, lB2, kB2]])

    def read_dihedrals(self, lines):
        # filter out intermolecular_interactions section
        idx = [i for i, l in enumerate(lines) if 'intermolecular_interactions' in l]
        if len(idx) == 1:
            lines = lines[:idx[0]]

        starts = []
        for i, line in enumerate(lines):
            if line.strip().startswith('[ dihedrals ]'):
                starts.append(i)
        for s in starts:
            lst = readSection(lines[s:], '[ dihedrals ]', '[')
            for line in lst:
                entr = line.split()
                idx = [int(x) for x in entr[:4]]

                func = int(entr[4])
                try:
                    rest = ' '.join(entr[5:])
                except:
                    rest = ''
                self.dihedrals.append([self.atoms[idx[0]-1],
                                       self.atoms[idx[1]-1],
                                       self.atoms[idx[2]-1],
                                       self.atoms[idx[3]-1],
                                       func, rest])

    def read_cmap(self, lines):
        starts = []
        for i, line in enumerate(lines):
            if line.strip().startswith('[ cmap ]'):
                starts.append(i)
        for s in starts:
            lst = readSection(lines[s:], '[ cmap ]', '[')
            for line in lst:
                entr = line.split()
                idx = [int(x) for x in entr[:5]]

                func = int(entr[5])
                try:
                    rest = ' '.join(entr[6:])
                except:
                    rest = ''
                self.cmap.append([self.atoms[idx[0]-1],
                                  self.atoms[idx[1]-1],
                                  self.atoms[idx[2]-1],
                                  self.atoms[idx[3]-1],
                                  self.atoms[idx[4]-1],
                                  func, rest])

    def read_exclusions(self, lines):
        starts = []
        for i, line in enumerate(lines):
            if line.strip().startswith('[ exclusions ]'):
                starts.append(i)
        if starts:
            self.has_exclusions = True
        for s in starts:
            lst = readSection(lines[s:], '[ exclusions ]', '[')
            for line in lst:
                entr = line.split()
                idx = [int(x) for x in entr[:]]
                toappend = [self.atoms[x-1] for x in idx] 
                self.exclusions.append( toappend )

    def read_vsites2(self, lines):
        starts = []
        for i, line in enumerate(lines):
            if line.strip().startswith('[ virtual_sites2 ]'):
                starts.append(i)
        if starts:
            self.has_vsites2 = True
        for s in starts:
            lst = readSection(lines[s:], '[ virtual_sites2 ]', '[')
            for line in lst:
                entr = line.split()
                idx = [int(x) for x in entr[:3]]

                func = int(entr[3])
                try:
                    rest = ' '.join(entr[4:])
                except:
                    rest = ''
                self.virtual_sites2.append([self.atoms[idx[0]-1],
                                            self.atoms[idx[1]-1],
                                            self.atoms[idx[2]-1],
                                            func, rest])

    def read_vsites3(self, lines):
        starts = []
        for i, line in enumerate(lines):
            if line.strip().startswith('[ virtual_sites3 ]'):
                starts.append(i)
        if starts:
            self.has_vsites3 = True
        for s in starts:
            lst = readSection(lines[s:], '[ virtual_sites3 ]', '[')
            for line in lst:
                entr = line.split()
                idx = [int(x) for x in entr[:4]]

                func = int(entr[4])
                try:
                    rest = ' '.join(entr[5:])
                except:
                    rest = ''
                self.virtual_sites3.append([self.atoms[idx[0]-1],
                                            self.atoms[idx[1]-1],
                                            self.atoms[idx[2]-1],
                                            self.atoms[idx[3]-1],
                                            func, rest])

    def read_intermolecular_interactions(self, lines):
        iilines = readSection(lines, begin='[ intermolecular_interactions ]',
                              end='gotoendoffile')
        self.ii = {}

        # Note that here we are not storing Atom objects, but just the indices
        # as they come in the topology file. this is because things get tricky
        # when having multiple moleculetypes in the same topology, some of
        # which are defined in itp files included via include statement.
        # It is easier to just copy the indices as they are.

        # -----
        # Bonds
        # -----
        lst = readSection(iilines, '[ bonds ]', '[')
        if lst:
            self.ii['bonds'] = []
            for line in lst:
                entries = line.split()
                if len(entries) == 3:
                    el = [int(x) for x in line.split()]
                    self.ii['bonds'].append([int(el[0]), int(el[1]), int(el[2])])

                elif len(entries) == 5:
                    el = [int(x) for x in entries[:3]]
                    lA = float(entries[3])
                    kA = float(entries[4])
                    self.ii['bonds'].append([int(el[0]), int(el[1]), int(el[2]), [lA, kA]])

                elif len(entries) == 7:
                    el = [int(x) for x in entries[:3]]
                    lA = float(entries[3])
                    kA = float(entries[4])
                    lB = float(entries[5])
                    kB = float(entries[6])
                    self.ii['bonds'].append([int(el[0]), int(el[1]), int(el[2]),
                                            [lA, kA, lB, kB]])

        # ------
        # Angles
        # ------
        lst = readSection(iilines, '[ angles ]', '[')
        if lst:
            self.ii['angles'] = []
            for line in lst:
                entries = line.split()
                if len(entries) == 4:
                    idx = [int(x) for x in line.split()]
                    self.ii['angles'].append([int(idx[0]), int(idx[1]), int(idx[2]), int(idx[3])])

                elif len(entries) == 6:
                    idx = [int(x) for x in entries[:4]]
                    l = float(entries[4])
                    k = float(entries[5])
                    self.ii['angles'].append([int(idx[0]), int(idx[1]), int(idx[2]), int(idx[3]),
                                             [l, k]])

                elif len(entries) == 8 and entries[3] == '1':
                    idx = [int(x) for x in entries[:4]]
                    lA = float(entries[4])
                    kA = float(entries[5])
                    lB = float(entries[6])
                    kB = float(entries[7])
                    self.ii['angles'].append([int(idx[0]), int(idx[1]), int(idx[2]), int(idx[3]),
                                             [lA, kA, lB, kB]])

                elif len(entries) == 8 and entries[3] == '5':
                    idx = [int(x) for x in entries[:4]]
                    lA1 = float(entries[4])
                    kA1 = float(entries[5])
                    lA2 = float(entries[6])
                    kA2 = float(entries[7])
                    self.ii['angles'].append([int(idx[0]), int(idx[1]), int(idx[2]), int(idx[3]),
                                             [lA1, kA1, lA2, kA2]])

                elif len(entries) == 12:
                    idx = [int(x) for x in entries[:4]]
                    lA1 = float(entries[4])
                    kA1 = float(entries[5])
                    lA2 = float(entries[6])
                    kA2 = float(entries[7])
                    lB1 = float(entries[8])
                    kB1 = float(entries[9])
                    lB2 = float(entries[10])
                    kB2 = float(entries[11])
                    self.ii['angles'].append([int(idx[0]), int(idx[1]), int(idx[2]), int(idx[3]),
                                             [lA1, kA1, lA2, kA2, lB1, kB1, lB2, kB2]])

        # ---------
        # Dihedrals
        # ---------
        lst = readSection(iilines, '[ dihedrals ]', '[')
        if lst:
            self.ii['dihedrals'] = []
            for line in lst:
                entr = line.split()
                idx = [int(x) for x in entr[:4]]
                func = int(entr[4])
                if len(entr) > 5:
                    rest = entr[5:]
                else:
                    rest = []
                self.ii['dihedrals'].append([int(idx[0]), int(idx[1]),
                                             int(idx[2]), int(idx[3]),
                                             func, rest])

    def read_vsites4(self, lines):
        starts = []
        for i, line in enumerate(lines):
            if line.strip().startswith('[ virtual_sites4 ]'):
                starts.append(i)
        if starts:
            self.has_vsites4 = True
        for s in starts:
            lst = readSection(lines[s:], '[ virtual_sites4 ]', '[')
            for line in lst:
                entr = line.split()
                idx = [int(x) for x in entr[:5]]

                func = int(entr[5])
                try:
                    rest = ' '.join(entr[6:])
                except:
                    rest = ''
                self.virtual_sites4.append([self.atoms[idx[0]-1],
                                            self.atoms[idx[1]-1],
                                            self.atoms[idx[2]-1],
                                            self.atoms[idx[3]-1],
                                            self.atoms[idx[4]-1],
                                            func, rest])

    def get_posre_section(self, lines):
        starts = []
        bIfDef = False
        for i, line in enumerate(lines):
            if line.strip().startswith('#endif'):
                bIfDef = False
            if line.strip().startswith('#ifdef'):
                bIfDef = True
            if bIfDef:
                continue
            if line.strip().startswith('[ position_restraints ]'):
                starts.append(i)
        if starts:
            self.has_posre = True

        lstList = {}
        counter = 0
        for s in starts:
            lst = readSection(lines[s:], '[ position_restraints ]', '[')
            lst = kickOutComments(lst, '#')
            lstList[counter] = lst
            counter += 1

        return(lstList)

    def read_posre(self, lstList):
        for lstKey in lstList:
            lst = lstList[lstKey]
            for line in lst:
                entr = line.split()
                idx = int(entr[0])
                func = int(entr[1])
                try:
                    rest = ' '.join(entr[2:])
                except:
                    rest = ''
                self.posre.append([self.atoms[idx-1], func, rest])

    def read_include_itps(self, lines):
        """Finds additional itp files included to in topology, and identifies
        the forcefield if it is included.
        """
        inc = []
        # we need to separate the itps included before/after the moleculetype
        # in the topology file, if present, to maintain the correct order of
        # molecules. At the moment, the most complex topology we can deal with
        # has include_mol1/moltype2/include_mol3.
        where = 'top'
        for line in lines:
            if line.strip()[:8] == '#include':
                # exclude standard include statemets
                if (('posre' not in line) and ('.ff' not in line)):
                    f = line.split()[1].strip('"')
                    itp = (f, where)
                    inc.append(itp)
                if 'forcefield.itp' in line:
                    ff = line.split()[1].strip('"').split('/')[0].split('.')[0]
                    self.forcefield = ff

            if line.strip().replace(' ', '') == '[moleculetype]':
                where = 'bottom'

        if len(inc) > 0:
            self.has_include_itps = True
            self.include_itps = inc

    # ===============
    # write functions
    # ===============
    def write(self, outfile, stateBonded='AB', stateTypes='AB', stateQ='AB',
              scale_mass=1.0, dummy_qA='on', dummy_qB='on', target_qB=None,
              full_morphe=True, write_atypes=True, posre_ifdef=True, posre_include=False,
              verbose=False, qPrec=6):
        """Writes the Topology to file.

        The parameters ``stateBonded``, ``stateTypes``, and ``stateQ`` control
        what to write in the B-state columns of the topology. This is needed
        for free energy calculations. However, if you are writing a
        standard topology file that do not contain information on B-states,
        choose the option 'A' so that only the A-state data will be written.

        Parameters
        ----------
        outfile : str
            filename of topology file
        stateBonded : A|AA|BB|AB, optional
            write bonded terms with states A only, AA, BB, or AB.
            Default is AB. If you want to write a 'standard' topology with no
            B-state columns, select 'A'.
        stateTypes: A|AA|BB|AB, optional
            write atomtypes with states A only, AA, BB, or AB.
            Default is AB. If you want to write a 'standard' topology with no
            B-state columns, select 'A'.
        stateQ : A|AA|BB|AB, optional
            write charges with statse A only, AA, BB, or AB.
            Default is AB. If you want to write a 'standard' topology with no
            B-state columns, select 'A'.
        scale_mass : float, optional
            scale the mass of dummy atoms by multiplying with scale_mass
        dummy_qA : on|off
            whether to have charges on dummy atoms in state A ('on') or
            not ('off'). Default is 'on'.
        dummy_qB : on|off
            whether to have charges on dummy atoms in state B ('on') or
            not ('off'). Default is 'on'.
        target_qB : float?
            target charge for hybrid B states? Default is None.
        full_morphe : bool, optional
            ???
        write_atypes : bool, optional
            whether to write the atomtypes section. Default is True. If you are
            including atomtypes in another Topology and do not want to write
            this section in this file, set this to False.
        posre_ifdef : bool, optional
            whether to use an "ifdef POSRES" statement for the position
            restraints. Default is True.
        posre_include : bool, optional
            whether to place the position restraints in a separate itp file
            that is included in the topology via an "#include" statement.
            Default is False.
        verbose : bool, optional
            whether to print out information about each atom written. Default
            is False.
        qPrec : int, optional
            number of significant digits for charges in topology. Default is 6.
        """
        # open file for writing
        fp = open(outfile, 'w')

        # determine the target charges for hybrid residues if they are not
        # provided explicitly
        if target_qB is None:
            target_qB = self.get_hybrid_qB()

        # write defaults if they are present while a forcefield is not defined,
        # and if we are writing a top rather than an itp file
        if self.defaults and not self.forcefield and not self.is_itp:
            self.write_defaults(fp)

        # write the ff include statement only for top files
        if self.is_itp is False and self.forcefield:
            self.write_ffline(fp)

        # write the atomtypes section if present
        if self.atomtypes and write_atypes is True:
            self.write_atomtypes(fp)

        # write nonbonded params section if present
        if self.nonbond_params:
            self.write_nonbond_params(fp)

        # write the rest of the header without the
        # line that imports the forcefield
        self.includes = [] # here collect the included itp's to avoid doubly including them
        self.write_header(fp, write_ff=False)

        # write itps included at top of the file
        self.write_include_itps(fp, which='top')

        # write the molecule section, if there are atoms
        if self.atoms:
            self.write_moleculetype(fp)
            self.write_atoms(fp, charges=stateQ, atomtypes=stateTypes,
                             dummy_qA=dummy_qA, dummy_qB=dummy_qB,
                             scale_mass=scale_mass, target_qB=target_qB,
                             full_morphe=full_morphe, verbose=verbose, qPrec=qPrec)
            self.write_bonds(fp, state=stateBonded)
            if self.have_constraints:
                self.write_constraints(fp)
            self.write_pairs(fp)
            self.write_angles(fp, state=stateBonded)
            self.write_dihedrals(fp, state=stateBonded)
            # write cmap only if needed/present
            if self.cmap:
                self.write_cmap(fp)
            if self.has_vsites2:
                self.write_vsites2(fp)
            if self.has_vsites3:
                self.write_vsites3(fp)
            if self.has_vsites4:
                self.write_vsites4(fp)
            if self.has_exclusions:
                self.write_exclusions(fp)
            if self.has_posre:
                # NOTE: posre are dealt with in 2 ways: via specific posre
                # readers/writers and via the footer. This may cause issues
                # at some point. Ideally, all posre should be written with this
                # function, and no posre as part of the footer
                self.write_posre(fp, ifdef=posre_ifdef, use_include=posre_include)

        # write posre section if present in original top
        self.write_footer(fp, what=['posre'])
        # write additional itps that one wants to included
        self.write_include_itps(fp, which='bottom')

        if self.is_itp is False:
            # write posre section if present in original top
            self.write_footer(fp, what=['water', 'posre_water', 'ions'])
            self.write_system(fp)
            self.write_molecules(fp)
        # write intermolecular interactions if present
        if self.ii:
            self.write_intermolecular_interactions(fp)
        fp.close()

    def write_ffline(self, fp):
        '''Writes the line at the beginning of topology file that imports
        a forcefield file.'''

        print('', file=fp)
        print('#include "{ff}.ff/forcefield.itp"'.format(ff=self.forcefield),
              file=fp)

    def write_header(self, fp, write_ff=True):
        '''Writes the include statemets at the top of the topology file.

        Parameters
        ----------
        fp : file
            handle for output file.
        write_ff : bool
            whether to write also the line that includes the forcefield
            parameters or not.
        '''
        # This 'odd' split of the writer is because one wants to write the
        # ff include statement before the atomtypes section, and the rest of
        # the inlcude statements (e.g. ligand.itp included) after the
        # atomtypes

        print('', file=fp)
        for line in self.header:
            if 'include' in line:
                foo = line.rstrip()
                self.includes.append(foo.split()[-1])
            if 'forcefield' in line and write_ff is False:
                continue
            else:
                print(line, file=fp)

    def write_include_itps(self, fp, which='top'):
        '''Writes the include statemets at the top and bottom of the topology
        file.

        Parameters
        ----------
        fp : file
            handle for output file.
        which : top | bottom
            which include statements to write. "top" are the include statements
            included before any [ moleculetype ], while "bottom" are the ones
            after other moleculetypes.
        '''

        print('', file=fp)
        for itp, where in self.include_itps:
            if (itp in self.includes) or ('"{0}"'.format(itp) in self.includes): # already included
                continue
            if where == which:
                print('#include "{}"'.format(itp), file=fp)

    def write_footer(self, fp, what=['all']):
        '''Writes bottom part of topology file. After all moleculetype info
        and before [ system ].

        The argument "what" takes a list of keywords that will be used to write
        elements of the footer in a certain order. The available keywords are
        the following:

            "posre"       : write #ifdef POSRES section
            "water"       : write #include for water parameters
            "posre_water" : write #ifdef POSRES_WATER section
            "ions"        : write #include for ion parameters
            "all"         : write all lines present in the footer

        Parameters
        ----------
        fp : file
            handle for output file.
        what : list
            list of keywords corresponsding to the sections you want to write.
            The order of the sections will reflect the order of keywords in
            this list.
        '''

        def _write_foot_section(fp, keyword):
            '''writes only specific lines/sections of footer.
            '''
            if keyword == 'all':
                for line in self.footer:
                    print(line, file=fp)

            # posre for molecule
            elif keyword == 'posre':
                # exclude POSRES_WATER lines
                lines = []
                copy = True
                for line in self.footer:
                    if 'POSRES_WATER' in line:
                        copy = False
                    if copy is True:
                        lines.append(line)
                    if copy is False and '#endif' in line:
                        copy = True

                write = False
                ifdef = False
                for line in lines:
                    if write is True and ifdef is False:
                        if len(line.split()) != 5:
                            write = False

                    if line.strip() == '#ifdef POSRES':
                        write = True
                        ifdef = True
                    if 'position_restraints' in line.strip():
                        write = True

                    if write is True:
                        print(line, file=fp)

                    if line.strip() == '#endif':
                        write = False

            # water params
            elif keyword == 'water':
                for line in self.footer:
                    if 'ff' in line and ('tip' in line or 'spc' in line):
                        print(line, file=fp)

            # posre for water
            elif keyword == 'posre_water':
                write = False
                for line in self.footer:
                    if line.strip() == '#ifdef POSRES_WATER':
                        write = True

                    if write is True:
                        print(line, file=fp)

                    if line.strip() == '#endif':
                        write = False

            # ion params
            elif keyword == 'ions':
                for line in self.footer:
                    if 'ff' in line and 'ion' in line:
                        print(line, file=fp)

            else:
                raise ValueError('keyword does not exists/not implemented')

        # write all sections
        print('', file=fp)
        for key in what:
            _write_foot_section(fp=fp, keyword=key)

    def write_moleculetype(self, fp):
        print('\n[ moleculetype ]', file=fp)
        print('; Name        nrexcl', file=fp)
        print('%s  %d' % (self.name, self.nrexcl), file=fp)

    def write_atomtypes(self, fp):
        # choose header
        if len(self.atomtypes[0]) == 7:
            print('\n[ atomtypes ]\n;  name  bond_type          mass        '
                  'charge  ptype   sigma      epsilon', file=fp)
        elif len(self.atomtypes[0]) == 8:
            print('\n[ atomtypes ]\n;     name bond_type anum      mass    '
                  'charge  ptype       sigma               epsilon', file=fp)

        # write data
        for at in self.atomtypes:
            if len(at) == 7:
                # we leave some space at the start because we may have dummy
                # types with long names (e.g. DUM_*)
                print('{name:>10}{bond_type:>10}{mass:>10.4f}{charge:>10.4f}'
                      '{ptype:>5}{sigma:>20.5e}{epsilon:>20.5e}'.format(**at),
                      file=fp)
            elif len(at) == 8:
                print('{name:>10}{bond_type:>10}{anum:>5}{mass:>10.4f}{charge:>10.4f}'
                      '{ptype:>5}{sigma:>20.5e}{epsilon:>20.5e}'.format(**at),
                      file=fp)

    def write_nonbond_params(self, fp):
        print('\n[ nonbond_params ]\n; i  j    func  sigma           epsilon', file=fp)
        for param in self.nonbond_params:
            print('{a1:>10}{a2:>10}{func:>5}{sigma:>20.5e}{epsilon:>20.5e}'.format(**param), file=fp)

    def write_atoms(self, fp, charges='AB', atomtypes='AB', dummy_qA='on',
                    dummy_qB='on', scale_mass=1.0, target_qB=[],
                    full_morphe=True, verbose=False, qPrec=6):

        # number of significant digits for charges in topology
        if qPrec>10:
            qPrec=10

        self.qA = 0
        self.qB = 0
        for r in self.residues:
            if _is_perturbed_residue(r):
                try:
                    target_chargeB = target_qB.pop(0)
                except:
                    target_chargeB = 0
                if verbose is True:
                    TR('Making target charge %g for residue %s' %
                       (round(target_chargeB, 5), r.resname))
                for atom in r.atoms:
                    if _atoms_morphe([atom]):
                        # we move the charges from state A to state B
                        if charges == 'AB':
                            atom.qqA = atom.q
                            atom.qqB = atom.qB
                            # we change a charge from + to - or vice versa
                            if not full_morphe and (atom.q*atom.qB < 0 or atom.atomtype != atom.atomtypeB):
                                atom.qqB = 0
                                atom.to_be_morphed = True
                            else:
                                atom.qqB = atom.qB
                        # we keep the charges
                        elif charges == 'AA':
                            atom.qqA = atom.q
                            atom.qqB = atom.q
                        # take charges of state B
                        elif charges == 'BB':
                            if not full_morphe:
                                if hasattr(atom, "contQ"):
                                    atom.qqA = atom.contQ
                                    atom.qqB = atom.qqA
                                # this a big q morphe
                                # has been set to zero before
                                if hasattr(atom, "to_be_morphed"):
                                    if atomtypes == 'BB':
                                        atom.qqA = 0
                                        atom.qqB = atom.qB
                                    elif atomtypes == 'AB':
                                        atom.qqA = 0
                                        atom.qqB = 0
                                elif not hasattr(atom, "contQ") and not hasattr(atom, "to_be_morphed"):
                                    atom.qqA = atom.qB
                                    atom.qqB = atom.qB
                            else:
                                atom.qqA = atom.qB
                                atom.qqB = atom.qB
                        if atom.atomtype.startswith('DUM') or atom.atomtypeB.startswith('DUM'):
                            if dummy_qA == 'off':
                                atom.qqA = 0.
                            if dummy_qB == 'off':
                                atom.qqB = 0.
                    else:
                        atom.qqA = atom.q
                        atom.qqB = atom.q
                qA_tot = sum(map(lambda a: a.qqA, r.atoms))  # unused variable
                qB_tot = sum(map(lambda a: a.qqB, r.atoms))
                if round(qB_tot, 5) != round(target_chargeB, 5):
                    if verbose is True:
                        TR('State B has total charge of %g' % round(qB_tot, 5))
                        TR('Applying charge correction to ensure integer charges')
                    latom = _last_perturbed_atom(r)
                    if verbose is True:
                        TR('Selecting atom %d-%s (%s) as perturbed atom with highest order'
                           % (latom.id, latom.name, latom.resname))
                    newqB = latom.qqB-(qB_tot-target_chargeB)
                    if verbose is True:
                        TR('Changing chargeB of atom %s from %g to %g'
                           % (latom.name, latom.qqB, newqB))
                    latom.qqB = newqB
                    qB_tot = sum(map(lambda a: a.qqB, r.atoms))
                    if verbose is True:
                        TR('New total charge of B-state is %g' % round(qB_tot, 5))
                else:
                    if verbose is True:
                        TR('No corrections applied to ensure integer charges')

        print('\n[ atoms ]', file=fp)
        print(';   nr       type  resnr residue  atom   cgnr     charge       mass  typeB    chargeB      massB', file=fp)
        al = self.atoms
        for atom in al:
            if _atoms_morphe([atom]):

                if atomtypes == 'AB':
                    atA = atom.atomtype
                    atB = atom.atomtypeB
                    mA = atom.m
                    mB = atom.mB
                elif atomtypes == 'AA':
                    atA = atom.atomtype
                    atB = atom.atomtype
                    mA = atom.m
                    mB = atom.m
                elif atomtypes == 'BB':
                    atA = atom.atomtypeB
                    atB = atom.atomtypeB
                    mA = atom.mB
                    mB = atom.mB
                if scale_mass:
                    if atA.startswith('DUM'): # scale mass at state A
                        mA *= scale_mass
                        if( mA < 1.0 and mB != 0.0):
                            mA = 1.0
                    if atB.startswith('DUM'): # scale mass at state B
                        mB *= scale_mass
                        if( mB < 1.0 and mA != 0.0):
                            mB = 1.0
                if hasattr(atom, "qqB"):
                    qqB = atom.qqB
                    if hasattr(atom, "contQ") and not full_morphe:
                        qqA = atom.contQ
                    else:
                        qqA = atom.qqA
                else:
                    qqA = atom.q
                    qqB = atom.qB
                print('%6d %11s%7d%7s%7s%7d %11.{0}f%11.4f %11s %11.{0}f%11.4f'.format(qPrec)
                      % (atom.id, atA, atom.resnr, atom.resname,
                         atom.name, atom.cgnr, qqA, mA, atB, qqB, mB), file=fp)
                self.qA += qqA
                self.qB += qqB
            else:
                print('%6d %11s%7d%7s%7s%7d %11.{0}f%11.4f'.format(qPrec)
                      % (atom.id, atom.atomtype, atom.resnr, atom.resname,
                         atom.name, atom.cgnr, atom.q, atom.m), file=fp)
                self.qA += atom.q
                self.qB += atom.q
        # write qB of latom to qA
        if not full_morphe:
            try:
                latom.contQ = latom.qqB
            except:
                pass

    def write_bonds(self, fp, state='AB'):

        print('\n[ bonds ]', file=fp)
        print(';  ai    aj funct            c0            c1            c2            c3', file=fp)
        for b in self.bonds:
            if len(b) == 3:
                print('%6d %6d %6d' % (b[0].id, b[1].id, b[2]), file=fp)
            elif len(b) == 4:
                s = '   '+'   '.join([str(x) for x in b[3]])
                print('%6d %6d %6d %s' % (b[0].id, b[1].id, b[2], s), file=fp)
            else:
                lA = b[3][1]
                kA = b[3][2]
                lB = b[4][1]
                kB = b[4][2]
                if state == 'AB':
                    print('%6d %6d %6d %14.6f %14.6f %14.6f %14.6f' %
                          (b[0].id, b[1].id, b[2], lA, kA, lB, kB), file=fp)
                elif state == 'AA':
                    print('%6d %6d %6d %14.6f %14.6f %14.6f %14.6f' %
                          (b[0].id, b[1].id, b[2], lA, kA, lA, kA), file=fp)
                elif state == 'BB':
                    print('%6d %6d %6d %14.6f %14.6f %14.6f %14.6f' %
                          (b[0].id, b[1].id, b[2], lB, kB, lB, kB), file=fp)

    def write_pairs(self, fp):
        # CHECK HOW THIS GOES WITH B-STATES
        print('\n[ pairs ]', file=fp)
        print(';  ai    aj funct            c0            c1            c2            c3', file=fp)
        for p in self.pairs:
            print('%6d %6d %6d' % (p[0].id, p[1].id, p[2]), file=fp)

    def write_constraints(self, fp):
        # CHECK HOW THIS GOES WITH B-STATES
        print('\n[ constraints ]', file=fp)
        print(';  ai    aj funct            c0            c1            c2            c3', file=fp)
        for p in self.constraints:
            if len(p) == 3:
                print('%6d %6d %6d' % (p[0].id, p[1].id, p[2]), file=fp)
            else:
                print('%6d %6d %6d %8s' % (p[0].id, p[1].id, p[2], p[3]), file=fp)

    def __print_any_angle(self, ang, fp ):
        print('%6d %6d %6d %6d' % (ang[0].id, ang[1].id, ang[2].id, ang[3]), file=fp, end='')
        for p in ang[4]:
            print('%14.6f ' % p,file=fp,end='')
        print('\n',file=fp,end='')

    def write_angles(self, fp, state='AB'):
        print('\n[ angles ]', file=fp)
        print(';  ai    aj    ak funct            c0            c1            c2            c3', file=fp)
        for ang in self.angles:
            if len(ang) == 4:
                print('%6d %6d %6d %6d' % (ang[0].id, ang[1].id, ang[2].id, ang[3]), file=fp)
            else:
                if state == 'A':
                    if ang[3] == 1:
                        print('%6d %6d %6d %6d %14.6f %14.6f'
                              % (ang[0].id, ang[1].id, ang[2].id, ang[3], ang[4][0], ang[4][1]), file=fp)
                    elif ang[3] == 5:
                        if shape(ang[4])[0] == 4:
                            print('%6d %6d %6d %6d %14.6f %14.6f %14.6f %14.6f'
                                  % (ang[0].id, ang[1].id, ang[2].id, ang[3],
                                     ang[4][0], ang[4][1], ang[4][2], ang[4][3]), file=fp)
                        else:
                            print('%6d %6d %6d %6d %14.6f %14.6f %14.6f %14.6f'
                                  % (ang[0].id, ang[1].id, ang[2].id, ang[3],
                                     ang[4][1], ang[4][2], ang[4][3], ang[4][4]), file=fp)
                    else:
                        raise ValueError("Don't know how to print angletype %d" % ang[3])

                if state == 'AB':
                    # check type here, for charmm its different, Urey-Bradley
                    try:
                        if ang[3] == 1:
                            print('%6d %6d %6d %6d %14.6f %14.6f %14.6f %14.6f ; %s %s %s'
                                  % (ang[0].id, ang[1].id, ang[2].id, ang[3], ang[4][1],
                                     ang[4][2], ang[5][1], ang[5][2], ang[0].name, ang[1].name, ang[2].name), file=fp)
                        elif ang[3] == 5:
                            print('%6d %6d %6d %6d %14.6f %14.6f %14.6f %14.6f %14.6f %14.6f %14.6f %14.6f ; %s %s %s'
                                  % (ang[0].id, ang[1].id, ang[2].id, ang[3], ang[4][1],
                                     ang[4][2], ang[4][3], ang[4][4], ang[5][1],
                                     ang[5][2], ang[5][3], ang[5][4],
                                     ang[0].name, ang[1].name, ang[2].name), file=fp)
                        else:
                            raise ValueError("Don't know how to print angletype %d" % ang[3])
                    except:
                        self.__print_any_angle( ang, fp ) # can be that an angle has parameters for one state only

                elif state == 'AA':
                    if ang[3] == 1:
                        print('%6d %6d %6d %6d %14.6f %14.6f %14.6f %14.6f ; %s %s %s'
                              % (ang[0].id, ang[1].id, ang[2].id, ang[3], ang[4][1],
                                 ang[4][2], ang[4][1], ang[4][2],
                                 ang[0].name, ang[1].name, ang[2].name), file=fp)
                    elif ang[3] == 5:
                        print('%6d %6d %6d %6d %14.6f %14.6f %14.6f %14.6f %14.6f %14.6f %14.6f %14.6f ; %s %s %s'
                              % (ang[0].id, ang[1].id, ang[2].id, ang[3], ang[4][1],
                                 ang[4][2], ang[4][3], ang[4][4], ang[4][1],
                                 ang[4][2], ang[4][3], ang[4][4],
                                 ang[0].name, ang[1].name, ang[2].name), file=fp)
                    else:
                        raise ValueError("Don't know how to print angletype %d" % ang[3])

                elif state == 'BB':
                    if ang[3] == 1:
                        print('%6d %6d %6d %6d %14.6f %14.6f %14.6f %14.6f ; %s %s %s'
                              % (ang[0].id, ang[1].id, ang[2].id, ang[3], ang[5][1],
                                 ang[5][2], ang[5][1], ang[5][2], ang[0].name,
                                 ang[1].name, ang[2].name), file=fp)
                    elif ang[3] == 5:
                        print('%6d %6d %6d %6d %14.6f %14.6f %14.6f %14.6f %14.6f %14.6f %14.6f %14.6f ; %s %s %s'
                              % (ang[0].id, ang[1].id, ang[2].id, ang[3], ang[5][1],
                                 ang[5][2], ang[5][3], ang[5][4], ang[5][1],
                                 ang[5][2], ang[5][3], ang[5][4],
                                 ang[0].name, ang[1].name, ang[2].name), file=fp)
                    else:
                        raise ValueError("Don't know how to print angletype %d" % ang[3])

    def write_cmap(self, fp):
        print('\n[ cmap ]', file=fp)
        print(';  ai    aj    ak    al    am funct', file=fp)
        for d in self.cmap:
            print("%6d %6d %6d %6d %6d %4d" % (d[0].id, d[1].id,
                                               d[2].id, d[3].id,
                                               d[4].id, d[5]), file=fp)

    def write_dihedrals(self, fp, state='AB'):
        print('\n[ dihedrals ]', file=fp)
        print(';  ai    aj    ak    al funct            c0            c1            c2            c3            c4            c5', file=fp)
        for d in self.dihedrals:
            if len(d) == 5:
                print("%6d %6d %6d %6d %4d" % (d[0].id, d[1].id,
                                               d[2].id, d[3].id, d[4]),
                                               file=fp)
            elif len(d) == 6:
                print("%6d %6d %6d %6d %4d %s" % (d[0].id, d[1].id,
                                                  d[2].id, d[3].id,
                                                  d[4], d[5]), file=fp)
            elif len(d) == 7:
                A, B = _check_case(d[:4])
                ast = d[5]
                bs = d[6]
                if ast is None or bs is None:
                    print(d[0].name, d[1].name, d[2].name, d[3].name,
                          d[0].atomtype, d[1].atomtype, d[2].atomtype, d[3].atomtype,
                          d[0].atomtypeB, d[1].atomtypeB, d[2].atomtypeB, d[3].atomtypeB)
                    print(d[0].type, d[1].type, d[2].type, d[3].type,
                          d[0].typeB, d[1].typeB, d[2].typeB, d[3].typeB)
                if ast == 'NULL':
                    if d[4] == 3:  # Ryckaert-Bellemans
                        ast = ' '.join(["%g" % x for x in [0, 0, 0, 0, 0, 0]])
                    elif d[4] == 1 or d[4] == 4:
                        ast = ' '.join(["%g" % x for x in [0, 0, 0]])
                    elif d[4] == 9:
                        ast = ' '.join(["%g" % x for x in [0, 0, 0]])
                    elif d[4] == 2:
                        ast = ' '.join(["%g" % x for x in [0, 0]])

                elif ast != 'NULL' and hasattr(ast, "append"):
                    ast = ' '.join(["%.10g" % x for x in d[5][1:]])
                if bs == 'NULL':
                    if d[4] == 3:
                        bs = ' '.join(["%g" % x for x in [0, 0, 0, 0, 0, 0]])
                    elif d[4] == 1 or d[4] == 4:
                        bs = ' '.join(["%g" % x for x in [0, 0, 0]])
                    elif d[4] == 9:
                        bs = ' '.join(["%g" % x for x in [0, 0, 0]])
                    elif d[4] == 2:
                        bs = ' '.join(["%g" % x for x in [0, 0]])

                elif bs != 'NULL' and hasattr(bs, "append"):
                    bs = ' '.join(["%.10g" % x for x in d[6][1:]])
                if state == 'AB':
                    print("%6d %6d %6d %6d %4d %s %s ; %s %s %s %s %s %s %s %s (%s->%s)" %
                          (d[0].id, d[1].id, d[2].id, d[3].id, d[4], ast, bs,
                           d[0].name, d[1].name, d[2].name, d[3].name,
                           d[0].type, d[1].type, d[2].type, d[3].type,
                           A, B), file=fp)
                elif state == 'AA':
                    print("%6d %6d %6d %6d %4d %s %s ; %s %s %s %s %s %s %s %s (%s->%s)" %
                          (d[0].id, d[1].id, d[2].id, d[3].id, d[4], ast, ast,
                           d[0].name, d[1].name, d[2].name, d[3].name,
                           d[0].type, d[1].type, d[2].type, d[3].type,
                           A, B), file=fp)
                elif state == 'BB':
                    print("%6d %6d %6d %6d %4d %s %s ; %s %s %s %s %s %s %s %s (%s->%s)" %
                          (d[0].id, d[1].id, d[2].id, d[3].id, d[4], bs, bs,
                           d[0].name, d[1].name, d[2].name, d[3].name,
                           d[0].type, d[1].type, d[2].type, d[3].type,
                           A, B), file=fp)

    def write_exclusions(self, fp):
        print('\n[ exclusions ]', file=fp)
        for excl in self.exclusions:
            for ex in excl:
                print("%6d " % (ex.id), end='', file=fp )
            print("\n", file=fp )

    def write_vsites2(self, fp):
        print('\n[ virtual_sites2 ]', file=fp)
        print(';  ai    aj    ak  funct            c0            c1', file=fp)
        for vs in self.virtual_sites2:
            if len(vs) == 4:
                print("%6d %6d %6d %4d" % (vs[0].id, vs[1].id,
                                           vs[2].id, vs[3]), file=fp)
            elif len(vs) == 5:
                print("%6d %6d %6d %4d %s" % (vs[0].id, vs[1].id,
                                              vs[2].id, vs[3], vs[4]), file=fp)
            else:
                sys.stderr.write('EEK! Something went wrong while writing virtual_sites2!!!!\n')
                print(vs)
                sys.exit(1)

    def write_vsites3(self, fp):
        print('\n[ virtual_sites3 ]', file=fp)
        print(';  ai    aj    ak    al funct            c0            c1', file=fp)
        for vs in self.virtual_sites3:
            if len(vs) == 5:
                print("%6d %6d %6d %6d %4d" % (vs[0].id, vs[1].id, vs[2].id,
                                               vs[3].id, vs[4]), file=fp)
            elif len(vs) == 6:
                print("%6d %6d %6d %6d %4d %s" % (vs[0].id, vs[1].id,
                                                  vs[2].id, vs[3].id,
                                                  vs[4], vs[5]), file=fp)
            else:
                sys.stderr.write('EEK! Something went wrong while writing virtual_sites3!!!!\n')
                print(vs)
                sys.exit(1)

    def write_vsites4(self, fp):
        print('\n[ virtual_sites4 ]', file=fp)
        print(';  ai    aj    ak    al    am  funct            c0            c1          c2', file=fp)
        for vs in self.virtual_sites4:
            if len(vs) == 6:
                print("%6d %6d %6d %6d %6d %4d" % (vs[0].id, vs[1].id,
                                                   vs[2].id, vs[3].id,
                                                   vs[4].id, vs[5]), file=fp)
            elif len(vs) == 7:
                print("%6d %6d %6d %6d %6d %4d %s" % (vs[0].id, vs[1].id,
                                                      vs[2].id, vs[3].id,
                                                      vs[4].id, vs[5], vs[6]), file=fp)
            else:
                sys.stderr.write('EEK! Something went wrong while writing virtual_sites4!!!!\n')
                print(vs)
                sys.exit(1)

    def write_posre(self, fp, ifdef=True, use_include=False):
        '''Write position restraints section.

        Parameters
        ----------
        ifdef : bool, optional
            whether to use an "ifdef POSRES" statement. Default is True.
        use_include : bool, optional
            whether to place the position restraints in a separate itp file
            that is included in the topology via an "#include" statement.
            Default is False.
        '''

        if ifdef is True:
            print('\n#ifdef POSRES', file=fp)

        # write posres to different file that will be included
        if use_include is True:
            f = os.path.basename(fp.name)
            path = os.path.dirname(fp.name)
            posre_fname = 'posre_%s.itp' % f.split('.')[0]
            print('#include "%s"' % posre_fname, file=fp)

            if path:
                fp2 = open(path+'/'+posre_fname, 'w')
            else:
                fp2 = open(posre_fname, 'w')

            print('\n[ position_restraints ]', file=fp2)
            print('; atom  type      fx      fy      fz', file=fp2)
            for pr in self.posre:
                if len(pr) == 3:
                    print("%6d %4d %s" % (pr[0].id, pr[1], pr[2]), file=fp2)
                else:
                    sys.stderr.write('EEK! Something went wrong while writing position_restraints!!!!\n')
                    print(pr)
                    sys.exit(1)
            fp2.close()

        # write posres directly in topology file
        elif use_include is False:
            print('\n[ position_restraints ]', file=fp)
            print('; atom  type      fx      fy      fz', file=fp)
            for pr in self.posre:
                if len(pr) == 3:
                    print("%6d %4d %s" % (pr[0].id, pr[1], pr[2]), file=fp)
                else:
                    sys.stderr.write('EEK! Something went wrong while writing position_restraints!!!!\n')
                    print(pr)
                    sys.exit(1)

        if ifdef is True:
            print('#endif', file=fp)

    def write_system(self, fp):
        print('\n[ system ]', file=fp)
        print('{0}'.format(self.system), file=fp)

    def write_defaults(self, fp):
        print('\n[ defaults ]', file=fp)
        print('; nbfunc        comb-rule       gen-pairs       fudgeLJ fudgeQQ', file=fp)
        print('{0}'.format(self.defaults), file=fp)

    def write_molecules(self, fp):
        print('\n[ molecules ]', file=fp)
        for mol, num in self.molecules:
            print("%s %d" % (mol, num), file=fp)

    def write_intermolecular_interactions(self, fp):
        print('\n[ intermolecular_interactions ]', file=fp)
        # -----
        # bonds
        # -----
        if 'bonds' in self.ii.keys():
            print('\n[ bonds ]', file=fp)
            for b in self.ii['bonds']:
                print('%6d %6d %6d' % (b[0], b[1], b[2]), file=fp, end='')
                if len(b) > 3:
                    for x in b[3]:
                        print(' %14.6f' % x, file=fp, end='')
                print('', file=fp)

        # ------
        # angles
        # ------
        if 'angles' in self.ii.keys():
            print('\n[ angles ]', file=fp)
            for ang in self.ii['angles']:
                print('%6d %6d %6d %6d' % (ang[0], ang[1], ang[2], ang[3]), file=fp, end='')
                if len(ang) > 4:
                    for x in ang[4]:
                        print(' %14.6f' % x, file=fp, end='')
                print('', file=fp)

        # ---------
        # dihedrals
        # ---------
        if 'dihedrals' in self.ii.keys():
            print('\n[ dihedrals ]', file=fp)
            for dih in self.ii['dihedrals']:
                print('%6d %6d %6d %6d %6d' % (dih[0], dih[1], dih[2], dih[3], dih[4]), file=fp, end='')
                if len(dih) > 5:
                    rest = dih[5]
                    for x in rest:
                        print(' %14.6f' % float(x), file=fp, end='')
                print('', file=fp)

    # ===============
    # other functions
    # ===============
    def get_qA(self):
        """Returns the total charge of state A.
        """
        qA = 0
        for atom in self.atoms:
            qA += atom.q
        return round(qA, 3)

    def get_qB(self):
        """Returns the total charge of state B.
        """
        qB = 0
        for atom in self.atoms:
            if atom.atomtypeB is not None:
                qB += atom.qB
            else:
                qB += atom.q
        return round(qB, 3)

    def get_hybrid_qA(self):
        """Returns the charge of state A for hybrid residues only.
        """
        qA = []
        rlist = []
        # identify hybrid residues
        for res in self.residues:
            if res.is_hybrid():
                rlist.append(res)
        # add all atom q of hybrid residues
        for r in rlist:
            qa = 0
            for atom in r.atoms:
                qa += atom.q
            qA.append(qa)
        return qA

    def get_hybrid_qB(self):
        """Returns the charge of state B for hybrid residues only.
        """
        qB = []
        rlist = []
        # identify hybrid residues
        for res in self.residues:
            if res.is_hybrid():
                rlist.append(res)
        # add all atom q of hybrid residues
        for r in rlist:
            qb = 0
            for atom in r.atoms:
                if _atoms_morphe([atom]):
                    qb += atom.qB
                else:
                    qb += atom.q
            qB.append(qb)
        return qB


class Topology(TopolBase):
    """Topology class.

    Parameters
    ----------
    filename : str
        topology file
    is_itp : bool
        whether the topology provided is an ``itp`` file. If not provided,
        this is automatically determined by the file extension (``.itp`` vs
        ``.top``).
    assign_types : bool
        whether to assign types for the atoms in the Topology. Default is True.
    self_contained : bool
        Whether the file contains all bonded and non-bonded parameters needed
        to simulate the system. This can be the case for some small-molecule
        topologies. If this is set to true, atomtypes are assigned to each
        atom without looking for a forcefield file.
    ff : str, optional
        force field to use. If not provided, it is determined based on the
        forcefield.itp include statement in the ``top`` file. If you are providing
        an ``itp`` file without a reference to the force field, assign_types is
        True, and self_contained is False, then you have to provide a force field
        name, so that pmx will know where to look to assign atom and bond types.
    version : str?
        what is version? is it still needed?

    Attributes
    ----------
    filename : str
        name of the input topology file
    forcefield : str
        forcefield included in the topology file, if present
    is_itp : bool
        whether the Topology is an ``itp`` file rather than ``top``
    include_itps : list
        list of itp files included in the topology file, if present
    nrexcl : int
        number of bonds between atoms from which to exclude interactions.
        See Gromacs manual.
    atoms : list
        list of atoms
    residues : list
        list of residues
    bonds : list
        list of bonds
    have_constraints : bool
        whether constraints are present
    constrains : list
        list of constraints
    pairs : list
        list of pairs
    cmap : list
        list of cmap
    angles : list
        list of angles
    dihedrals : list
        list of dihedrals
    has_vsites2 : bool
        whether vsites2 are present
    has_vsites3 : bool
        whether vsites3 are present
    has_vsites4 : bool
        whether vsites4 are present
    virtual_sites2 : list
        list of vsites2
    virtual_sites3 : list
        list of vsites3
    virtual_sites4 : list
        list of vsites4
    has_posre : bool
        whether position restraints are present
    posre : list
        list of  position restraints
    molecules : list
        list of molecules
    system : str
        name of system
    qA : float
        net charge of state A
    qB : float
        net charge of state B
    """

    def __init__(self, filename, is_itp=None, ff=None,
                 assign_types=True, self_contained=False, version='new'):
        TopolBase.__init__(self, filename, version)

        # is_itp is already an attribute of TopolBase that is assigned at init
        # however, if is_itp explicitly set, then overwrite attribute
        if is_itp is not None:
            self.is_itp = is_itp

        # Similarly, the forcefield is determined automatically, but it can
        # be defined explicitly via the ff parameter. If we have an itp file
        # self.forcefield = '', then in this case ff is not optinal anymore
        if ff is not None:
            self.forcefield = ff

        if assign_types is True and self_contained is False:
            if self.forcefield == '':
                raise ValueError('The topology file provided does not contain an '
                                 'include statement pointing towards a forcefield'
                                 '\nfile. This is likely because you are providing'
                                 ' a itp rather than a top file. Thus, you need to'
                                 '\nprovide the forcefield to use via the ff '
                                 'parameter.')
            # get full path to ff
            self.ffpath = get_ff_path(self.forcefield)
            # read ff files and assign types
            fulltop = cpp_parse_file(self.filename, itp=self.is_itp,
                                     ffpath=self.ffpath,
                                     cpp_path=[os.environ.get('GMXLIB'),
                                               '%s/top' % os.environ.get('GMXDATA')])
            fulltop = kickOutComments(fulltop, '#')
            fulltop = kickOutComments(fulltop, ';')
            self.BondedParams = BondedParser(fulltop)
            self.NBParams = NBParser(fulltop, version, ff=self.forcefield)
            self.assign_fftypes()

        # no need to get ff files - all info is within the itp/top
        elif assign_types is True and self_contained is True:
            if not self.atomtypes:
                raise ValueError('The topology file provided does not contain '
                                 'atomtypes despite "self_contained" being "True".')
            # assign non-bonded types
            for atom in self.atoms:
                atype = next(i for i in self.atomtypes if i["name"] == atom.atomtype)
                atom.type = atype['name']
                if atom.atomtypeB is not None:
                    atypeB = next(i for i in self.atomtypes if i["name"] == atom.atomtypeB)
                    atom.typeB = atypeB['name']
                else:
                    atom.typeB = atom.type

    def set_molecule(self, molname, n):
        mol_exists = False
        for i, mol in enumerate(self.molecules):
            if mol[0] == molname:
                self.molecules[i][1] = n
                mol_exists = True
        if not mol_exists:
            self.molecules.append([molname, n])

    def del_molecule(self, molname):
        if not hasattr(molname, "append"):
            molname = [molname]
        new = []
        for m in self.molecules:
            if m[0] not in molname:
                new.append(m)
        self.molecules = new

    def assign_fftypes(self):
        for atom in self.atoms:
            atom.type = self.NBParams.atomtypes[atom.atomtype]['bond_type']
            if atom.atomtypeB is not None:
                atom.typeB = self.NBParams.atomtypes[atom.atomtypeB]['bond_type']
            else:
                atom.typeB = atom.type

    def make_bond_params(self):
        for i, (at1, at2, func) in enumerate(self.bonds):
            param = self.BondedParams.get_bond_param(at1.type, at2.type)
            if param is None:
                print('Error! No bonded parameters found! (%s-%s)' % (at1.type, at2.type))
                sys.exit(1)
            self.bonds[i].append(param[1:])

    def make_angle_params(self):
        for i, (at1, at2, at3, func) in enumerate(self.angles):
            param = self.BondedParams.get_angle_param(at1.type, at2.type, at3.type)
            if param is None:
                print('Error! No angle parameters found! (%s-%s-%s)' % (at1.type, at2.type, at3.type))
                sys.exit(1)
            self.angles[i].append(param[1:])

    def make_dihedral_params(self):
        for i, d in enumerate(self.dihedrals):
            if d[5] != '':  # we have a prefefined dihedral
                continue
            else:
                at1, at2, at3, at4, func, dih = d
                param = self.BondedParams.get_dihedral_param(at1.type, at2.type,
                                                             at3.type, at4.type,
                                                             func)
                if param is None:
                    print('Error! No dihedral parameters found! (%s-%s-%s-%s)' % (at1.type, at2.type, at3.type, at4.type))
                    print('{0} {1}'.format(func, dih))
                    sys.exit(1)
                del self.dihedrals[i][-1]
                self.dihedrals[i].append(param[1:])

    def make_nonbond_params(self, rule=2):
        '''Create nonbonded params needed to keep LJ intramolecular interactions
        when decoupling a ligand.

        Parameters
        ----------
        rule : int
            combination rule to use, following notation in the Gromacs manual
            in section 5.3.2.
        '''

        for i, a1 in enumerate(self.atomtypes):
            for j, a2 in enumerate(self.atomtypes):
                if i >= j:
                    param = {}
                    param['a1'] = a1['name']
                    param['a2'] = a2['name']
                    param['func'] = 1
                    if rule == 2:
                        param['sigma'] = (a1['sigma'] + a2['sigma'])/2.0
                        param['epsilon'] = (a1['epsilon'] + a2['epsilon'])**0.5
                    else:
                        raise ValueError('combination rule not yet supported')
                    self.nonbond_params.append(param)

    def make_posre(self, heavy=True, k=1000):
        '''Generate position restraints for the atoms in Topology.

        Parameters
        ----------
        heavy : bool, optional
            whether to restrain only heavy atoms (True) or also hydrogens
            (False). Default is True
        k : float, optional
            force constant of position restraints. Default is 1000 kJ/nm^2.
        '''
        # reset posre
        self.posre = []
        # fill list
        for a in self.atoms:
            a.make_long_name()
            a.get_symbol()
            if heavy is True:
                if a.symbol != 'H':
                    self.posre.append([a, 1, '{0:14.6f} {0:14.6f} {0:14.6f}'.format(k)])
            else:
                self.posre.append([a, 1, '{0:14.6f} {0:14.6f} {0:14.6f}'.format(k)])

        self.has_posre = True

    def as_rtp(self):
        for i, bond in enumerate(self.bonds):
            id1 = bond[0].id
            id2 = bond[1].id
            self.bonds[i][0] = self.atoms[id1-1]
            self.bonds[i][1] = self.atoms[id2-1]
        for i, angle in enumerate(self.angles):
            id1 = angle[0].id
            id2 = angle[1].id
            id3 = angle[2].id
            self.angles[i][0] = self.atoms[id1-1]
            self.angles[i][1] = self.atoms[id2-1]
            self.angles[i][2] = self.atoms[id3-1]

        for i, dih in enumerate(self.dihedrals):
            id1 = dih[0].id
            id2 = dih[1].id
            id3 = dih[2].id
            id4 = dih[3].id
            self.dihedrals[i][0] = self.atoms[id1-1]
            self.dihedrals[i][1] = self.atoms[id2-1]
            self.dihedrals[i][2] = self.atoms[id3-1]
            self.dihedrals[i][3] = self.atoms[id4-1]

        for i, vs in enumerate(self.virtual_sites2):
            id1 = dih[0].id
            id2 = dih[1].id
            id3 = dih[2].id
            self.virtual_sites2[i][0] = self.atoms[id1-1]
            self.virtual_sites2[i][1] = self.atoms[id2-1]
            self.virtual_sites2[i][2] = self.atoms[id3-1]


    def write_rtp(self, filename ='mol.rtp'):
        fp = open(filename,'w')
        fp.write('[ {0} ]\n'.format(self.name))
        fp.write(' [ atoms ]\n')
        for atom in self.atoms:
            fp.write("%8s %-12s %8.6f %5d\n" % \
                  (atom.name,atom.atomtype,atom.q,atom.cgnr) )

        fp.write(' [ bonds ]\n')
        for bond in self.bonds:
            if len(bond)<=3:
                fp.write("%8s %8s\n"% \
                      (bond[0].name, bond[1].name) )
            else:
                fp.write("%8s %8s %8.4f %8.4f\n"% \
                      (bond[0].name, bond[1].name, bond[3][0], bond[3][1]) )

        fp.write(' [ angles ]\n')
        for angle in self.angles:
            if len(angle)<=4:
                fp.write("%8s %8s %8s\n"% \
                      (angle[0].name, angle[1].name,angle[2].name) )
            elif angle[3]==5: # U-B
                fp.write("%8s %8s %8s %8.4f %8.4f %8.4f %8.4f\n"% \
                      (angle[0].name, angle[1].name,angle[2].name,
                        angle[4][0],angle[4][1],angle[4][2],angle[4][3]) )
            else:
                fp.write("%8s %8s %8s %8.4f %8.4f\n"% \
                      (angle[0].name, angle[1].name,angle[2].name,angle[4][0],angle[4][1]) )


        fp.write(' [ dihedrals ]\n')
        for dih in self.dihedrals:
            if len(dih)<=5: # no parameters
                fp.write("%8s %8s %8s %s\n"% \
                      (dih[0].name, dih[1].name,dih[2].name, dih[3].name))
            elif dih[4]==3:
                param = dih[5].split()
                fp.write("%8s %8s %8s %s %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f\n"% \
                      (dih[0].name, dih[1].name,dih[2].name, dih[3].name, float(param[0]), float(param[1]), float(param[2]), float(param[3]), float(param[4]), float(param[5])) )
            elif (dih[4]==1) or (dih[4]==4) or (dih[4]==9):
                param = dih[5].split()
                fp.write("%8s %8s %8s %s %8.4f %8.4f %8.4f\n"% \
                      (dih[0].name, dih[1].name,dih[2].name, dih[3].name,
                       float(param[0]), float(param[1]), float(param[2])) )
            elif (dih[4]==2) or (dih[4]==11):
                param = dih[5].split()
                fp.write("%8s %8s %8s %s %8.4f %8.4f\n"% \
                      (dih[0].name, dih[1].name,dih[2].name, dih[3].name,
                       float(param[0]), float(param([1]))) )



class MDPError(Exception):
    """MDP Error class.
    """
    def __init__(self, s):
        self.s = s

    def __str__(self):
        return repr(self.s)


class MDP:
    """Class for Gromacs MDP files.
    """

    def __init__(self):
        self.parameters = OrderedDict([
        ('include'                  , ''),
        ('define'                   , ''),
        ('integrator'               , 'md'),
        ('tinit'                    , 0),
        ('dt'                       , 0.002),
        ('nsteps'                   , 25000),
        ('simulation_part'          , 1),
        ('init_step'                , 0),
        ('comm-mode'                , 'Linear'),
        ('nstcomm'                  , 1),
        ('comm-grps'                , ''),
        ('bd-fric'                  , 0),
        ('ld-seed'                  , 1993),
        ('emtol'                    , 100),
        ('emstep'                   , 0.01),
        ('niter'                    , 0),
        ('fcstep'                   , 0),
        ('nstcgsteep'               , 1000),
        ('nbfgscorr'                , 10),
        ('rtpi'                     , 0.05),
        ('nstxout'                  , 10000),
        ('nstvout'                  , 10000),
        ('nstfout'                  , 0),
        ('nstlog'                   , 1000),
        ('nstenergy'                , 100),
        ('nstxtcout'                , 100),
        ('xtc-precision'            , 1000),
        ('xtc-grps'                 , ''),
        ('energygrps'               , ''),
        ('nstlist'                  , 10),
        ('ns-type'                  , 'Grid'),
        ('pbc'                      , 'xyz'),
        ('periodic_molecules'       , 'no'),
        ('rlist'                    , 1.2),
        ('coulombtype'              , 'PME'),
        ('rcoulomb-switch'          , 0),
        ('rcoulomb'                 , 1.2),
        ('epsilon-r'                , 1),
        ('epsilon_rf'               , 1),
        ('vdw-type'                 , 'switch'),
        ('rvdw-switch'              , 1),
        ('rvdw'                     , 1.1),
        ('DispCorr'                 , 'EnerPres'),
        ('table-extension'          , 1),
        ('energygrp_table'          , ''),
        ('fourierspacing'           , 0.14),
        ('fourier_nx'               , 0),
        ('fourier_ny'               , 0),
        ('fourier_nz'               , 0),
        ('pme_order'                , 4),
        ('ewald_rtol'               , 1e-05),
        ('ewald_geometry'           , '3d'),
        ('epsilon_surface'          , 0),
        ('optimize_fft'             , 'no'),
        ('implicit_solvent'         , 'No'),
        ('gb_algorithm'             , 'Still'),
        ('nstgbradii'               , 1),
        ('rgbradii'                 , 2),
        ('gb_epsilon_solvent'       , 80),
        ('gb_saltconc'              , 0),
        ('gb_obc_alpha'             , 1),
        ('gb_obc_beta'              , 0.8),
        ('gb_obc_gamma'             , 4.85),
        ('sa_surface_tension'       , 2.092),
        ('tcoupl'                   , 'v-rescale'),
        ('tc-grps'                  , ['Protein', 'non-protein']),
        ('tau-t'                    , [0.1, 0.1]),
        ('ref-t'                    , [298, 298]),
        ('Pcoupl'                   , 'Parrinello-Rahman'),
        ('Pcoupltype'               , 'Isotropic'),
        ('tau-p'                    , 1),
        ('compressibility'          , 4.6E-5),
        ('ref-p'                    , 1),
        ('refcoord_scaling'         , 'No'),
        ('andersen_seed'            , 815131),
        ('QMMM'                     , 'no'),
        ('QMMM-grps'                , ''),
        ('QMmethod'                 , ''),
        ('QMMMscheme'               , 'normal'),
        ('QMbasis'                  , ''),
        ('QMcharge'                 , ''),
        ('QMmult'                   , ''),
        ('SH'                       , ''),
        ('CASorbitals'              , ''),
        ('CASelectrons'             , ''),
        ('SAon'                     , ''),
        ('SAoff'                    , ''),
        ('SAsteps'                  , ''),
        ('MMChargeScaleFactor'      , 1),
        ('bOPT'                     , ''),
        ('bTS'                      , ''),
        ('annealing'                , ['no', 'no']),
        ('annealing_npoints'        , [2, 2]),
        ('annealing_time'           , [0, 50, 0, 50]),
        ('annealing_temp'           , [0, 298, 0, 298]),
        ('gen-vel'                  , 'no'),
        ('gen-temp'                 , 300),
        ('gen-seed'                 , 173529),
        ('constraints'              , 'all-bonds'),
        ('constraint-algorithm'     , 'Lincs'),
        ('continuation'             , 'yes'),
        ('Shake-SOR'                , 'no'),
        ('shake-tol'                , 1e-04),
        ('lincs-order'              , 4),
        ('lincs-iter'               , 1),
        ('lincs-warnangle'          , 30),
        ('morse'                    , 'no'),
        ('energygrp_excl'           , ''),
        ('nwall'                    , 0),
        ('wall_type'                , '9-3'),
        ('wall_r_linpot'            , -1),
        ('wall_atomtype'            , ''),
        ('wall_density'             , ''),
        ('wall_ewald_zfac'          , 3),
        ('pull'                     , 'no'),
        ('disre'                    , 'No'),
        ('disre-weighting'          , 'Equal'),
        ('disre-mixed'              , 'no'),
        ('disre-fc'                 , 1000),
        ('disre-tau'                , 0),
        ('nstdisreout'              , 100),
        ('orire'                    , 'no'),
        ('orire-fc'                 , 0),
        ('orire-tau'                , 0),
        ('orire-fitgrp'             ,''),
        ('nstorireout'              , 100),
        ('dihre'                    , 'No'),
        ('dihre-fc'                 , 1000),
        ('free-energy'              , 'yes'),
        ('init-lambda'              , 0.5),
        ('delta-lambda'             , 0),
        ('sc-alpha'                 , 0.3),
        ('sc-power'                 , 1),
        ('sc-sigma'                 , 0.25),
        ('couple-moltype'           , ''),
        ('couple-lambda0'           , 'vdw-q'),
        ('couple-lambda1'           , 'vdw-q'),
        ('couple-intramol'          , 'no'),
        ('acc-grps'                 , ''),
        ('accelerate'               , ''),
        ('freezegrps'               , ''),
        ('freezedim'                , ''),
        ('cos-acceleration'         , 0),
        ('deform'                   , ''),
        ('E-x'                      , ''),
        ('E-xt'                     , ''),
        ('E-y'                      , ''),
        ('E-yt'                     , ''),
        ('E-z'                      , ''),
        ('E-zt'                     , ''),
        ('user1-grps'               , ''),
        ('user2-grps'               , ''),
        ('userint1'                 , 0),
        ('userint2'                 , 0),
        ('userint3'                 , 0),
        ('userint4'                 , 0),
        ('userreal1'                , 0),
        ('userreal2'                , 0),
        ('userreal3'                , 0),
        ('userreal4'                , 0)
        ])

    def __str__(self):
        line = ''
        for key, val in self.parameters.items():
            if hasattr(val, "append"):
                s = ''
                for x in val:
                    s += str(x)+' '
            else:
                s = str(val)
            line += "%-25s = %s\n" % (key, s)
        return line

    def __setitem__(self, item, value):
        if item not in self.parameters:
            raise(MDPError, "No such option %s" % item)

        self.parameters[item] = value

    def write(self, fp=None):

        if fp is None:
            fp = sys.stdout
        else:
            if not hasattr(fp, "write"):
                fp = open(fp, "w")
        print('{}'.format(self), file=fp)

    def read(self, filename):
        lines = open(filename).readlines()
        l = kickOutComments(lines, ';')
        for line in l:
            entr = line.split('=')
            key = entr[0].strip()
            val = entr[1].strip().split()
            if key not in self.parameters:
                print('Warning! Ignoring entry \'%s\'' % key)
            else:
                if len(val) == 0:
                    self[key] = ''
                elif len(val) == 1:
                    self[key] = val[0]
                else:
                    self[key] = val
        return self


# ==============================================================================
# FUNCTIONS
# ==============================================================================

# -----------------
# "check" functions
# -----------------
def _check_case(atoms):
        A = ''
        B = ''
        for a in atoms:
            if a.atomtype.startswith('DUM'):
                A += 'D'
            else:
                A += 'A'
            if a.atomtypeB is not None:
                if a.atomtypeB.startswith('DUM'):
                    B += 'D'
                else:
                    B += 'A'
            else:
                B += 'A'
        return A, B


def _atoms_morphe(atoms):
    for atom in atoms:
        if atom.atomtypeB is not None and (atom.q != atom.qB or
                                           atom.m != atom.mB or
                                           atom.atomtype != atom.atomtypeB):
            return True
    return False


def _atomtypes_morphe(atoms):
    for atom in atoms:
        if atom.atomtypeB is not None and atom.atomtype != atom.atomtypeB:
            return True
    return False


def _is_perturbed_residue(residue):
    if _atoms_morphe(residue.atoms):
        return True
    return False


def _last_perturbed_atom(r):
    last_atom = None
    for atom in r.atoms:
        if _atoms_morphe([atom]) and atom.name not in ['N', 'CA', 'C', 'O', 'H']:
            if not atom.atomtype.startswith('DUM') and not atom.atomtypeB.startswith('DUM'):
                last_atom = atom
    if last_atom is None:
        print('Error: Could not find a perturbed atom to put rest charges on !', file=sys.stderr)
        sys.exit(1)
    return last_atom


# -----------------------
# Amber related functions
# -----------------------
def make_amber_residue_names(model):
    """ Function that does this...
    """
    # get a list with all cysteines
    cysl = model.fetch_residues('CYS')

    # we do a simple check. If a HG is there it's CYS, else it's CYS2

    for res in cysl:
        hg = res.fetch_atoms('HG')  # select HG atom from residue
        sg1 = res.fetch_atoms('SG')[0]
        if not hg:  # no hydrogen
            ss_bond = False
            for r in cysl:
                if r != res:
                    sg2 = r.fetch_atoms('SG')[0]
                    d = sg1 - sg2
                    if d < 2.5:
                        ss_bond = True
                        break
            if ss_bond:
                # terminal cys2 is ccyx
                rr = 'CYS2'
                res.set_resname(rr)
            else:
                res.set_resname('CYM')

        else:
            res.set_resname('CYN')
    lysl = model.fetch_residues('LYS')
    for res in lysl:
        at = res.fetch('HZ3')
        at2 = res.fetch('HZ2')
        if at or not at2:
            res.set_resname('LYP')
    # histidine
    hisl = model.fetch_residues('HIS')
    for res in hisl:
        bHE2 = False
        bHD1 = False
        he2 = res.fetch('HE2')
        if he2:
            bHE2 = True  # FIXME:variable not used?
        hd1 = res.fetch('HD1')
        if hd1:
            bHD1 = True  # FIXME:variable not used?
        if hd1 and he2:
            res.set_resname('HIP')
        elif hd1 and not he2:
            res.set_resname('HID')
        elif he2 and not hd1:
            res.set_resname('HIE')
        else:
            res.set_resname('HID')

    aspl = model.fetch_residues('ASP')
    for res in aspl:
        bHD2 = False
        hd2 = res.fetch('HD2')
        if hd2:
            res.set_resname('ASH')

    glul = model.fetch_residues('GLU')
    for res in glul:
        bHD2 = False  # FIXME:variable not used?
        hd2 = res.fetch('HE2')
        if hd2:
            res.set_resname('GLH')
    for chain in model.chains:
        if chain.residues[0].is_protein_residue():
            first = chain.nterminus()
            last = chain.cterminus()
            first.set_resname('N'+first.resname)  # rename e.g. ALA to NALA
            if last.resname == 'CYS2':
                last.set_resname('CCYX')   # rename e.g. ARG to CARG
            else:
                last.set_resname('C'+last.resname)   # rename e.g. ARG to CARG
            try:
                o1, o2 = last.fetchm(['O1', 'O2'])
                o1.name = 'OC1'
                o2.name = 'OC2'
            except:
                try:
                    o1, o2 = last.fetchm(['O', 'OXT'])
                    o1.name = 'OC1'
                    o2.name = 'OC2'
                except:
                    print('pmx_Warning_> No terminal oxygen atoms found in chain %s' % chain.id, file=sys.stderr)


def assign_ffamber99sb_params(m):
    m.get_symbol()
    m.rename_atoms()
    for c in m.chains:
        c.make_residue_tree()

    make_amber_residue_names(m)
    rtp = RTPParser('ffamber99sb.rtp')
    rtp.assign_params(m)
    bo = BondedParser('ffamber99sbbon.itp')
    nb = NBParser('ffamber99sbnb.itp')
    nb.assign_params(m)
    bo.assign_params(m)
    rtp.assign_dihedral_params(m, bo.directives)


# ---------------------------
# functions to get the energy
# ---------------------------
# should these be methods in the Model class?
def bond_energy(m):
    return _p.total_bond_energy(m.bond_list)


def angle_energy(m):
    return _p.total_angle_energy(m.angle_list)


def dihedral_energy(m):
    return _p.total_dihedral_energy(m.dihedral_list)


def improper_energy(m):
    return _p.total_improper_energy(m.improper_list)


def coul14_energy(m):
    return _p.coul14_energy(m.atoms)


def lj14_energy(m):
    return _p.lj14_energy(m.atoms)


def nb_lj_energy(m):
    return _p.nb_lj_energy(m.atoms)


def nb_coul_energy(m):
    return _p.nb_coul_energy(m.atoms)


def nb_energy(m):
    return _p.nb_energy(m.atoms)


def energy(m):
    bond_ene = bond_energy(m)
    angle_ene = angle_energy(m)
    dihedral_ene = dihedral_energy(m)
    improper_ene = improper_energy(m)
    lj14_ene = lj14_energy(m)
    coul14_ene = coul14_energy(m)
    nb_ene = nb_energy(m)

    tot_energy = (bond_ene + angle_ene + dihedral_ene +
                  improper_ene + nb_ene + lj14_ene + coul14_ene)
    return tot_energy


# ==============================================================================
#                                  Functions
# ==============================================================================
def merge_atomtypes(*args):
    '''Given a list containing atomtypes lists, return an atomtype list
    containing a unique set of atomtypes (no duplicates).

    Parameters
    ----------
    *args :
        variable length argument containing atomtypes attributes from
        ``Topology`` objects.

    Returns
    -------
    atypes : list
        merged atomtypes attribute. This is a list of dict, containing a
        unique set of atomtypes.

    Examples
    --------
    >>> top3.atomtypes = merge_atomtypes(top1.atomtypes, top2.atomtypes)
    '''
    # read all atomtypes present in all files
    all_atomtypes = []
    for atomtypes in args:
        all_atomtypes.extend(atomtypes)

    # keep unique set of atomtypes
    unique_atomtypes = [dict(i) for i in set(tuple(x.items())
                        for x in all_atomtypes)]

    return unique_atomtypes
