# Source code for MDAnalysis.coordinates.GMS

    # -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; -*-
    # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
    #
    # MDAnalysis --- https://www.mdanalysis.org
    # Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors
    # (see the file AUTHORS for the full list of names)
    #
    # Released under the Lesser GNU Public Licence, v2.1 or any higher version
    #
    # Please cite your use of MDAnalysis in published work:
    #
    # R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler,
    # D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein.
    # MDAnalysis: A Python package for the rapid analysis of molecular dynamics
    # simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th
    # Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy.
    # doi: 10.25080/majora-629e541a-00e
    #
    # N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein.
    # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations.
    # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787
    #
    #

    """GAMESS trajectory reader --- :mod:`MDAnalysis.coordinates.GMS`
    =================================================================

    Resources: the GMS output format is a common output format for different
    GAMESS distributions: US-GAMESS, Firefly (PC-GAMESS) and GAMESS-UK.

    Current version was approbated with US-GAMESS & Firefly only.

    There appears to be no rigid format definition so it is likely users
    will need to tweak the :class:`GMSReader`.

    .. autoclass:: GMSReader
       :members:

    """
    import os
    import errno
    import re

    from . import base
    import MDAnalysis.lib.util as util
    from MDAnalysis.lib.util import store_init_arguments

    class GMSReader(base.ReaderBase):
        """Reads from an GAMESS output file

        :Data:
            ts
              Timestep object containing coordinates of current frame

        :Methods:
            ``len(out)``
              return number of frames in out
            ``for ts in out:``
              iterate through trajectory

        Note
        ----
        :class:`GMSReader` can read both uncompressed (``foo.out``) and
        compressed (``foo.out.bz2`` or ``foo.out.gz``) files;
        decompression is handled on the fly

        .. versionchanged:: 0.11.0
           Frames now 0-based instead of 1-based.
           Added `dt` and `time_offset` keywords (passed to :class:`Timestep`).

        """

        format = "GMS"

        # these are assumed!
        units = {"time": "ps", "length": "Angstrom"}

        @store_init_arguments
        def __init__(self, outfilename, **kwargs):
            super(GMSReader, self).__init__(outfilename, **kwargs)

            # the filename has been parsed to be either b(g)zipped or not
            self.outfile = util.anyopen(self.filename)

            # note that, like for xtc and trr files, _n_atoms and _n_frames are used quasi-private variables
            # to prevent the properties being recalculated
            # this is because there is no indexing so the way it measures the number of frames is to read the whole file!
            self._n_atoms = None
            self._n_frames = None
            self._runtyp = None

            self.ts = self._Timestep(0)  # need for properties initial calculations

            # update runtyp property
            self.runtyp
            if not self.runtyp in ["optimize", "surface"]:
                raise AttributeError("Wrong RUNTYP= " + self.runtyp)

            self.ts = self._Timestep(self.n_atoms, **self._ts_kwargs)
            # update n_frames property
            self.n_frames

            # Read in the first timestep
            self._read_next_timestep()

        @property
        def runtyp(self):
            """RUNTYP property of the GAMESS run"""
            if self._runtyp is not None:  # return cached value
                return self._runtyp
            try:
                self._runtyp = self._determine_runtyp()
            except IOError:
                return 0
            else:
                return self._runtyp

        def _determine_runtyp(self):
            with util.openany(self.filename) as out:
                for line in out:
                    m = re.match(r"^.*RUNTYP=([A-Z]+)\s+.*", line)
                    if m is not None:
                        res = m.group(1).lower()
                        break

            return res

        @property
        def n_atoms(self):
            """number of atoms in a frame"""
            if self._n_atoms is not None:  # return cached value
                return self._n_atoms
            try:
                self._n_atoms = self._read_out_natoms()
            except IOError:
                return 0
            else:
                return self._n_atoms

        def _read_out_natoms(self):
            with util.openany(self.filename) as out:
                for line in out:
                    m = re.match(
                        r"\s*TOTAL NUMBER OF ATOMS\s*=\s*([0-9]+)\s*", line
                    )
                    if m is not None:
                        res = int(m.group(1))
                        break

            return res

        @property
        def n_frames(self):
            if self._n_frames is not None:  # return cached value
                return self._n_frames
            try:
                self._n_frames = self._read_out_n_frames()
            except IOError:
                return 0
            else:
                return self._n_frames

        def _read_out_n_frames(self):
            if self.runtyp == "optimize":
                trigger = re.compile(b"^.NSERCH=.*")
            elif self.runtyp == "surface":
                trigger = re.compile(b"^.COORD 1=.*")

            self._offsets = offsets = []
            with util.openany(self.filename, "rb") as out:
                line = True
                while not line == b"":  # while not EOF
                    line = out.readline()
                    if re.match(trigger, line):
                        offsets.append(out.tell() - len(line))

            return len(offsets)

        def _read_frame(self, frame):
            self.outfile.seek(self._offsets[frame])
            self.ts.frame = frame - 1  # gets +1'd in _read_next
            return self._read_next_timestep()

        def _read_next_timestep(self, ts=None):
            # check that the timestep object exists
            if ts is None:
                ts = self.ts
            # check that the outfile object exists; if not reopen the trajectory
            if self.outfile is None:
                self.open_trajectory()
            x = []
            y = []
            z = []

            flag = 0
            counter = 0

            for line in self.outfile:
                if self.runtyp == "optimize":
                    if (flag == 0) and (
                        re.match(r"^.NSERCH=.*", line) is not None
                    ):
                        flag = 1
                        continue
                    if (flag == 1) and (
                        re.match(r"^ COORDINATES OF ALL ATOMS ARE ", line)
                        is not None
                    ):
                        flag = 2
                        continue
                    if (flag == 2) and (re.match(r"^\s*-+\s*", line) is not None):
                        flag = 3
                        continue
                    if flag == 3 and counter < self.n_atoms:
                        words = line.split()
                        x.append(float(words[2]))
                        y.append(float(words[3]))
                        z.append(float(words[4]))
                        counter += 1

                elif self.runtyp == "surface":
                    if (flag == 0) and (
                        re.match(r"^.COORD 1=\s*([-]?[0-9]+\.[0-9]+).*", line)
                        is not None
                    ):
                        flag = 1
                        continue
                    if (flag == 1) and (
                        re.match(
                            r"^\s*HAS ENERGY VALUE\s*([-]?[0-9]+\.[0-9]+)\s*", line
                        )
                        is not None
                    ):
                        flag = 3
                        continue
                    if flag == 3 and counter < self.n_atoms:
                        words = line.split()
                        x.append(float(words[1]))
                        y.append(float(words[2]))
                        z.append(float(words[3]))
                        counter += 1

                # stop when the cursor has reached the end of that block
                if counter == self._n_atoms:
                    ts._x[:] = (
                        x  # more efficient to do it this way to avoid re-creating the numpy arrays
                    )
                    ts._y[:] = y
                    ts._z[:] = z
                    # print ts.frame
                    ts.frame += 1
                    return ts

            raise EOFError

        def _reopen(self):
            self.close()
            self.open_trajectory()

        def open_trajectory(self):
            if self.outfile is not None:
                raise IOError(
                    errno.EALREADY, "GMS file already opened", self.filename
                )
            if not os.path.exists(self.filename):
                # must check; otherwise might segmentation fault
                raise IOError(errno.ENOENT, "GMS file not found", self.filename)

            self.outfile = util.anyopen(self.filename)

            # reset ts
            ts = self.ts
            ts.frame = -1
            return self.outfile

        def close(self):
            """Close out trajectory file if it was open."""
            if self.outfile is None:
                return
            self.outfile.close()
            self.outfile = None