###################################################################################
#                                                                                 #
# Copyright (C) 2006-2013 by                                                      #
# The Salk Institute for Biological Studies and                                   #
# Pittsburgh Supercomputing Center, Carnegie Mellon University                    #
#                                                                                 #
# This program is free software; you can redistribute it and/or                   #
# modify it under the terms of the GNU General Public License                     #
# as published by the Free Software Foundation; either version 2                  #
# of the License, or (at your option) any later version.                          #
#                                                                                 #
# This program is distributed in the hope that it will be useful,                 #
# but WITHOUT ANY WARRANTY; without even the implied warranty of                  #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                   #
# GNU General Public License for more details.                                    #
#                                                                                 #
# You should have received a copy of the GNU General Public License               #
# along with this program; if not, write to the Free Software                     #
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. #
#                                                                                 #
###################################################################################

import struct

class UnmarshalBuffer(object):
    def __init__(self, data):
        self.__data   = data
        self.__offset = 0
        self.__endian = '<'

    def get_offset(self):
        return self.__offset
    offset = property(get_offset)

    def at_end(self):
        return self.__offset >= len(self.__data)

    def set_big_endian(self, e):
        if e:
            self.__endian = '>'
        else:
            self.__endian = '<'

    def next_byte(self):
        b = ord(self.__data[self.__offset])
        self.__offset += 1
        return b

    def next_vint(self):
        b = self.next_byte()
        accum = b & 0x7f
        while (b & 0x80) != 0:
            b = self.next_byte()
            accum <<= 7
            accum |= (b & 0x7f)
        return accum

    def next_svint(self):
        val = self.next_vint()
        if val & 1:
            return -(val >> 1)
        else:
            return  (val >> 1)
        return accum

    def next_string(self):
        l = self.next_vint()
        return self.next_cstring(l)

    def next_cstring(self, l):
        return self.next_struct('%ds' % l)[0]

    def next_struct(self, tmpl):
        vals = struct.unpack_from(self.__endian + tmpl, self.__data, self.__offset)
        self.__offset += struct.calcsize(tmpl)
        return vals

CMD_CURRENT_TIME      = 1
CMD_CURRENT_ITERATION = 2
CMD_CHKPT_SEQ_NUM     = 3
CMD_RNG_STATE         = 4
CMD_MCELL_VERSION     = 5
CMD_SPECIES_TABLE     = 6
CMD_SCHEDULER_STATE   = 7
CMD_BYTE_ORDER        = 8

def read_current_time(ub):
    time, = ub.next_struct('d')
    return {'cur_time': time}

def read_current_iteration(ub):
    start_time, real_time = ub.next_struct('qd')
    return {'start_time': start_time, 'real_time': real_time}

def read_chkpt_sequence(ub):
    seq, = ub.next_struct('I')
    return {'chkpt_seq': seq}

def read_rng_state(ub):
    seed = ub.next_vint()
    rngtype, = ub.next_struct('c')
    if rngtype == 'I':
        randcnt = ub.next_vint()
        aa, bb, cc = ub.next_struct('QQQ')
        randrsl = ub.next_struct('256Q')
        mm      = ub.next_struct('256Q')
        return {'rng_seed': seed,
                'rng_type': 'ISAAC64',
                'rng_aa':   aa,
                'rng_bb':   bb,
                'rng_cc':   cc,
                'rng_rsl':  randrsl,
                'rng_mm':   mm}
    elif rngtype == 'M':
        a, b, c, d = ub.next_struct('IIII')
        return {'rng_seed': seed,
                'rng_type': 'SimpleRNG',
                'rng_a':  a,
                'rng_b':  b,
                'rng_c':  c,
                'rng_d':  d }
    else:
        raise Exception('Sorry -- this file seems to be malformed.')

def read_byte_order(ub):
    bo, = ub.next_struct('I')
    if bo == 17:
        ub.set_big_endian(False)
        return {'endian': 'little'}
    elif bo == 0x10000000:
        ub.set_big_endian(True)
        return {'endian': 'big'}
    else:
        raise Exception('Unknown byte order %d' % bo)

def read_mcell_version(ub):
    ver_len, = ub.next_struct('I')
    ver = ub.next_cstring(ver_len)
    return {'mcell_version': ver}

def read_species(ub):
    nsp = ub.next_vint()
    species = {}
    for i in range(nsp):
        species_name = ub.next_string()
        species_id   = ub.next_vint()
        species[species_id] = species_name
    return {'species': species}

def read_scheduler(ub, spec):
    num_molecules = ub.next_vint()
    molecules = []
    for i in range(num_molecules):
        species = ub.next_vint()
        newbie = ub.next_byte()
        t, t2, bday, x, y, z = ub.next_struct('dddddd')
        orient = ub.next_svint()
        cmplx  = ub.next_vint()
        m = {'species':  spec[species],
             'newbie':   newbie != 0,
             't':        t,
             't2':       t2,
             'birthday': bday,
             'pos':      (x, y, z),
             'orient':   orient}
        if cmplx != 0:
            suidx = ub.next_vint()
            m['cx_idx'] = cmplx
            m['cx_sub'] = suidx
            if suidx != 0:
                nunits = ub.next_vint()
                m['cx_cnt'] = nunits
        molecules.append(m)
    return {'molecules': molecules}

def read_file(fname):
    ub = UnmarshalBuffer(open(fname, 'rb').read())
    data = {}
    while not ub.at_end():
        cmd = ub.next_byte()
        if cmd == CMD_CURRENT_TIME:
            d = read_current_time(ub)
        elif cmd == CMD_CURRENT_ITERATION:
            d = read_current_iteration(ub)
        elif cmd == CMD_CHKPT_SEQ_NUM:
            d = read_chkpt_sequence(ub)
        elif cmd == CMD_RNG_STATE:
            d = read_rng_state(ub)
        elif cmd == CMD_MCELL_VERSION:
            d = read_mcell_version(ub)
        elif cmd == CMD_SPECIES_TABLE:
            d = read_species(ub)
        elif cmd == CMD_SCHEDULER_STATE:
            d = read_scheduler(ub, data['species'])
        elif cmd == CMD_BYTE_ORDER:
            d = read_byte_order(ub)
        else:
            raise Exception('Unknown command %02x in file.  Perhaps the file is malformed.' % cmd)
        data.update(d)
    return data

def dump_data(data):
    ORIENTS = ['-', '_', '+']
    print '  MCell version:     %s'    % data['mcell_version']
    print '  File endianness:   %s'    % data['endian']
    print '  Cur time:          %.15g' % data['cur_time']
    print '  Start iteration:   %ld'   % data['start_time']
    print '  Real time:         %.15g' % data['real_time']
    print '  Sequence:          %d'    % data['chkpt_seq']
    rng_keys = [k for k in data.keys() if k.startswith('rng_')]
    rng_keys.sort()
    for d in rng_keys:
        print '  %s: %*s         %s'    % (d, 8-len(d), '', str(data[d]))
    print '  Species:'

    species_table = data['species']
    species_keys = species_table.keys()
    species_keys.sort()
    for d in species_keys:
        name = species_table[d]
        print '      %3d: %s' % (d, name)
        for m in data['molecules']:
            if m['species'] != name:
                continue
            print ('           %c %18.15g %18.15g %18.15g (%18.15g, %18.15g, %18.15g) %c' %
                    ('N' if m['newbie'] else '_',
                     m['t'],
                     m['t2'],
                     m['birthday'],
                     m['pos'][0],
                     m['pos'][1],
                     m['pos'][2],
                     ORIENTS[m['orient'] + 1])),
            if m.has_key('cx_idx'):
                if m.has_key('cx_cnt'):
                    print ' %5d[%d/%d]' % (m['cx_idx'], m['cx_sub'], m['cx_cnt'])
                else:
                    print ' %5d[MASTER]' % (m['cx_idx'])
            else:
                print ''

if __name__ == '__main__':
    try:
        import psyco
        psyco.full()
    except ImportError:
        pass

    import sys

    for i in sys.argv[1:]:
        print '%s:' % i
        data = read_file(i)
        dump_data(data)

