1# -*- coding: utf-8 -*-
2"""
3Convenience class for handling MSRecord and MSFileparam.
4"""
5from __future__ import (absolute_import, division, print_function,
6                        unicode_literals)
7from future.builtins import *  # NOQA
8
9import ctypes as C  # NOQA
10import os
11
12import numpy as np
13
14from obspy import UTCDateTime
15from obspy.core.compatibility import from_buffer
16from .headers import HPTMODULUS, MS_NOERROR, MSFileParam, MSRecord, clibmseed
17
18
19def _get_ms_file_info(f, real_name):
20    """
21    Takes a Mini-SEED filename as an argument and returns a dictionary
22    with some basic information about the file. Also suitable for Full
23    SEED.
24
25    This is an exact copy of a method of the same name in utils. Due to
26    circular imports this method cannot be import from utils.
27    XXX: Figure out a better way!
28
29    :param f: File pointer of opened file in binary format
30    :param real_name: Realname of the file, needed for calculating size
31    """
32    # get size of file
33    info = {'filesize': os.path.getsize(real_name)}
34    pos = f.tell()
35    f.seek(0)
36    rec_buffer = from_buffer(f.read(512), dtype=np.int8)
37    info['record_length'] = clibmseed.ms_detect(rec_buffer, 512)
38    # Calculate Number of Records
39    info['number_of_records'] = int(info['filesize'] //
40                                    info['record_length'])
41    info['excess_bytes'] = info['filesize'] % info['record_length']
42    f.seek(pos)
43    return info
44
45
46class _MSStruct(object):
47    """
48    Class for handling MSRecord and MSFileparam.
49
50    It consists of a MSRecord and MSFileparam and an attached python file
51    pointer.
52
53    :ivar msr: MSRecord
54    :ivar msf: MSFileparam
55    :ivar file: filename
56    :ivar offset: Current offset
57
58    :param filename: file to attach to
59    :param init_msrmsf: initialize msr and msf structure
60        by a first pass of read. Setting this option to
61        false will result in errors when setting e.g.
62        the offset before a call to read
63    """
64    def __init__(self, filename, init_msrmsf=True):
65        # Initialize MSRecord structure
66        self.msr = clibmseed.msr_init(C.POINTER(MSRecord)())
67        self.msf = C.POINTER(MSFileParam)()  # null pointer
68        self.file = filename
69        # dummy read once, to avoid null pointer in ms.msf for e.g.
70        # ms.offset
71        if init_msrmsf:
72            self.read(-1, 0, 1, 0)
73            self.offset = 0
74
75    def get_end(self):
76        """
77        Return endtime
78        """
79        self.read(-1, 0, 1, 0)
80        dtime = clibmseed.msr_endtime(self.msr)
81        return UTCDateTime(dtime / HPTMODULUS)
82
83    def get_start(self):
84        """
85        Return starttime
86        """
87        self.read(-1, 0, 1, 0)
88        dtime = clibmseed.msr_starttime(self.msr)
89        return UTCDateTime(dtime / HPTMODULUS)
90
91    def file_info(self):
92        """
93        For details see util._get_ms_file_info
94        """
95        fp = open(self.file, 'rb')
96        self.info = _get_ms_file_info(fp, self.file)
97        fp.close()
98        return self.info
99
100    def file_pos_from_rec_num(self, record_number=0):
101        """
102        Return byte position of file given a certain record_number.
103
104        The byte position can be used to seek to certain points in the file
105        """
106        if not hasattr(self, 'info'):
107            self.info = self.file_info()
108        # Calculate offset of the record to be read.
109        if record_number < 0:
110            record_number = self.info['number_of_records'] + record_number
111        if record_number < 0 or \
112           record_number >= self.info['number_of_records']:
113            raise ValueError('Please enter a valid record_number')
114        return record_number * self.info['record_length']
115
116    def read(self, reclen=-1, dataflag=1, skipnotdata=1, verbose=0,
117             raise_flag=True):
118        """
119        Read MSRecord using the ms_readmsr_r function. The following
120        parameters are directly passed to ms_readmsr_r.
121
122        :param ms: _MSStruct (actually consists of a LP_MSRecord,
123            LP_MSFileParam and an attached file pointer).
124            Given an existing ms the function is much faster.
125        :param reclen: If reclen is 0 the length of the first record is auto-
126            detected. All subsequent records are then expected to have the
127            same record length. If reclen is negative the length of every
128            record is automatically detected. Defaults to -1.
129        :param dataflag: Controls whether data samples are unpacked, defaults
130            to 1.
131        :param skipnotdata: If true (not zero) any data chunks read that to do
132            not have valid data record indicators will be skipped. Defaults to
133            True (1).
134        :param verbose: Controls verbosity from 0 to 2. Defaults to None (0).
135        :param record_number: Number of the record to be read. The first record
136            has the number 0. Negative numbers will start counting from the end
137            of the file, e.g. -1 is the last complete record.
138        """
139        errcode = clibmseed.ms_readmsr_r(C.pointer(self.msf),
140                                         C.pointer(self.msr),
141                                         self.file.encode('ascii', 'strict'),
142                                         reclen, None, None,
143                                         skipnotdata, dataflag, verbose)
144        if raise_flag:
145            if errcode != MS_NOERROR:
146                raise Exception("Error %d in ms_readmsr_r" % errcode)
147        return errcode
148
149    def __del__(self):
150        """
151        Method for deallocating MSFileParam and MSRecord structure.
152        """
153        errcode = clibmseed.ms_readmsr_r(C.pointer(self.msf),
154                                         C.pointer(self.msr),
155                                         None, -1, None, None, 0, 0, 0)
156        if errcode != MS_NOERROR:
157            raise Exception("Error %d in ms_readmsr_r" % errcode)
158
159    def set_offset(self, value):
160        self.msf.contents.readoffset = C.c_int(value)
161
162    def get_offset(self):
163        return int(self.msf.contents.readoffset)
164
165    offset = property(get_offset, set_offset)
166