# Copyright (c) 2006 Carnegie Mellon University
#
# You may copy and modify this freely under the same terms as
# Sphinx-III

"""Read/write Sphinx-III Gaussian parameter.

This module reads and writes the mean and variance parameter files
used by SphinxTrain, Sphinx-III, and PocketSphinx.
"""

__author__ = "David Huggins-Daines <dhdaines@gmail.com>"
__version__ = "$Revision$"

from struct import unpack, pack
from numpy import reshape, shape, frombuffer
from .s3file import S3File, S3File_write


def open(filename, mode="rb", attr={"version": 1.0}):
    if mode in ("r", "rb"):
        return S3GauFile(filename)
    elif mode in ("w", "wb"):
        return S3GauFile_write(filename, attr=attr)
    else:
        raise Exception("mode must be 'r', 'rb', 'w', or 'wb'")


def open_full(filename, mode="rb", attr={"version": 1.0}):
    if mode in ("r", "rb"):
        return S3FullGauFile(filename)
    elif mode in ("w", "wb"):
        return S3FullGauFile_write(filename, attr=attr)
    else:
        raise Exception("mode must be 'r', 'rb', 'w', or 'wb'")


class S3GauFile(S3File):
    "Read Sphinx-III format Gaussian parameter files"
    def __init__(self, filename, mode="rb"):
        super().__init__(filename=filename, mode=mode)
        self._load()

    def readgauheader(self):
        if self.fileattr["version"] != "1.0":
            raise Exception("Version mismatch: must be 1.0 but is "
                            + self.fileattr["version"])
        self.fh.seek(self.data_start, 0)
        self.n_mgau = unpack(self.swap + "I", self.fh.read(4))[0]
        self.n_feat = unpack(self.swap + "I", self.fh.read(4))[0]
        self.density = unpack(self.swap + "I", self.fh.read(4))[0]
        self.veclen = unpack(self.swap + "I" * self.n_feat,
                             self.fh.read(4 * self.n_feat))
        self.blk = sum(self.veclen)
        self._nfloats = unpack(self.swap + "I", self.fh.read(4))[0]

    def _load(self):
        self.readgauheader()
        if self._nfloats != self.n_mgau * self.density * self.blk:
            raise Exception(("Number of data points %d doesn't match "
                             + "total %d = %d*%d*%d")
                            %
                            (self._nfloats,
                             self.n_mgau * self.density * self.blk,
                             self.n_mgau, self.density, self.blk))
        # First load everything into a really big Numeric array.
        spam = self.fh.read(self._nfloats * 4)
        data = frombuffer(spam, 'f').copy()
        if self.otherend:
            data = data.byteswap()
        # The on-disk layout is bogus so we have to slice and dice it.
        # Since feature streams are not the same dimensionality, we use
        # a two-dimensional outer list of Numeric array slices
        params = []
        r = 0
        for i in range(0, self.n_mgau):
            mgau = []
            params.append(mgau)
            for j in range(0, self.n_feat):
                rnext = r + self.density * self.veclen[j];
                gmm = reshape(data[r:rnext], (self.density, self.veclen[j]))
                mgau.append(gmm)
                r = rnext
        self._params = params


class S3FullGauFile(S3GauFile):
    "Read Sphinx-III format Gaussian full covariance matrix files"
    def _load(self):
        self.readgauheader()
        if self._nfloats != self.n_mgau * self.density * self.blk * self.blk:
            raise Exception(("Number of data points %d doesn't match "
                             + "total %d = %d*%d*%d*%d")
                            %
                            (self._nfloats,
                             self.n_mgau * self.density * self.blk * self.blk,
                             self.n_mgau, self.density, self.blk, self.blk))
        # First load everything into a really big Numeric array.
        # This is inefficient, but in the absence of fromfile()...
        spam = self.fh.read(self._nfloats * 4)
        data = frombuffer(spam, 'f').copy()
        if self.otherend:
            data = data.byteswap()
        # The on-disk layout is bogus so we have to slice and dice it.
        # Since feature streams are not the same dimensionality, we use
        # a two-dimensional outer list of Numeric array slices
        params = []
        r = 0
        for i in range(0, self.n_mgau):
            mgau = []
            params.append(mgau)
            for j in range(0, self.n_feat):
                rnext = r + self.density * self.veclen[j] * self.veclen[j];
                gmm = reshape(data[r:rnext], (self.density,
                                              self.veclen[j],
                                              self.veclen[j]))
                mgau.append(gmm)
                r = rnext
        self._params = params


class S3GauFile_write(S3File_write):
    "Write Sphinx-III format Gaussian parameter files"
    def writeall(self, stuff):
        # Single-stream files are easy
        n_mgau = len(stuff)
        n_feat = len(stuff[0])
        n_density = len(stuff[0][0])
        if n_feat == 1:
            veclen = len(stuff[0][0][0])
            # Write the header
            self.fh.seek(self.data_start, 0)
            self.fh.write(pack("=IIIII",
                               n_mgau, n_feat, n_density, veclen,
                               n_mgau * n_feat * n_density * veclen))
            allgau = reshape(stuff, (n_mgau*n_feat*n_density*veclen,))
            self.fh.write(pack("=" + str(len(allgau)) + "f", *allgau))
        else:
            veclen = [len(x[0]) for x in stuff[0]]
            # Write the header
            self.fh.seek(self.data_start, 0)
            self.fh.write(pack("=III", n_mgau, n_feat, n_density))
            self.fh.write(pack(("=%dI" % len(veclen)), *veclen))
            self.fh.write(pack("=I", n_mgau * n_density * sum(veclen)))
            for m in stuff:
                for f in m:
                    f.ravel().astype('f').tofile(self.fh)


class S3FullGauFile_write(S3GauFile_write):
    "Write Sphinx-III format Gaussian full covariance matrix files"
    def writeall(self, stuff):
        # This will break for multi-stream files
        n_mgau, n_feat, density, veclen, veclen2 = shape(stuff)
        if n_feat != 1:
            raise Exception("Multi-stream files not supported")
        # Write the header
        self.fh.seek(self.data_start, 0)
        self.fh.write(pack("=IIIII",
                           n_mgau, n_feat, density, veclen,
                           n_mgau * n_feat * density * veclen * veclen2))
        allgau = reshape(stuff, (n_mgau*n_feat*density*veclen*veclen2,))
        self.fh.write(pack("=" + str(len(allgau)) + "f", *allgau))
