"""Conversion tool from CTF to FIF."""

# Author: Eric Larson <larson.eric.d<gmail.com>
#
# License: BSD (3-clause)

import os
import os.path as op

import numpy as np

from ...utils import verbose, logger, _clean_names, fill_doc, _check_option

from ..base import BaseRaw
from ..utils import _mult_cal_one, _blk_read_lims

from .res4 import _read_res4, _make_ctf_name
from .hc import _read_hc
from .eeg import _read_eeg, _read_pos
from .trans import _make_ctf_coord_trans_set
from .info import _compose_meas_info, _read_bad_chans, _annotate_bad_segments
from .constants import CTF


@fill_doc
def read_raw_ctf(directory, system_clock='truncate', preload=False,
                 clean_names=False, verbose=None):
    """Raw object from CTF directory.

    Parameters
    ----------
    directory : str
        Path to the CTF data (ending in ``'.ds'``).
    system_clock : str
        How to treat the system clock. Use "truncate" (default) to truncate
        the data file when the system clock drops to zero, and use "ignore"
        to ignore the system clock (e.g., if head positions are measured
        multiple times during a recording).
    preload : bool or str (default False)
        Preload data into memory for data manipulation and faster indexing.
        If True, the data will be preloaded into memory (fast, requires
        large amount of memory). If preload is a string, preload is the
        file name of a memory-mapped file which is used to store the data
        on the hard drive (slower, requires less memory).
    clean_names : bool, optional
        If True main channel names and compensation channel names will
        be cleaned from CTF suffixes. The default is False.
    %(verbose)s

    Returns
    -------
    raw : instance of RawCTF
        The raw data.

    See Also
    --------
    mne.io.Raw : Documentation of attribute and methods.

    Notes
    -----
    .. versionadded:: 0.11
    """
    return RawCTF(directory, system_clock, preload=preload,
                  clean_names=clean_names, verbose=verbose)


@fill_doc
class RawCTF(BaseRaw):
    """Raw object from CTF directory.

    Parameters
    ----------
    directory : str
        Path to the CTF data (ending in ``'.ds'``).
    system_clock : str
        How to treat the system clock. Use "truncate" (default) to truncate
        the data file when the system clock drops to zero, and use "ignore"
        to ignore the system clock (e.g., if head positions are measured
        multiple times during a recording).
    preload : bool or str (default False)
        Preload data into memory for data manipulation and faster indexing.
        If True, the data will be preloaded into memory (fast, requires
        large amount of memory). If preload is a string, preload is the
        file name of a memory-mapped file which is used to store the data
        on the hard drive (slower, requires less memory).
    clean_names : bool, optional
        If True main channel names and compensation channel names will
        be cleaned from CTF suffixes. The default is False.
    %(verbose)s

    See Also
    --------
    mne.io.Raw : Documentation of attribute and methods.
    """

    @verbose
    def __init__(self, directory, system_clock='truncate', preload=False,
                 verbose=None, clean_names=False):  # noqa: D102
        # adapted from mne_ctf2fiff.c
        if not isinstance(directory, str) or \
                not directory.endswith('.ds'):
            raise TypeError('directory must be a directory ending with ".ds"')
        if not op.isdir(directory):
            raise ValueError('directory does not exist: "%s"' % directory)
        _check_option('system_clock', system_clock, ['ignore', 'truncate'])
        logger.info('ds directory : %s' % directory)
        res4 = _read_res4(directory)  # Read the magical res4 file
        coils = _read_hc(directory)  # Read the coil locations
        eeg = _read_eeg(directory)  # Read the EEG electrode loc info

        # Investigate the coil location data to get the coordinate trans
        coord_trans = _make_ctf_coord_trans_set(res4, coils)

        digs = _read_pos(directory, coord_trans)

        # Compose a structure which makes fiff writing a piece of cake
        info = _compose_meas_info(res4, coils, coord_trans, eeg)
        info['dig'] += digs
        info['bads'] += _read_bad_chans(directory, info)

        # Determine how our data is distributed across files
        fnames = list()
        last_samps = list()
        raw_extras = list()
        while(True):
            suffix = 'meg4' if len(fnames) == 0 else ('%d_meg4' % len(fnames))
            meg4_name = _make_ctf_name(directory, suffix, raise_error=False)
            if meg4_name is None:
                break
            # check how much data is in the file
            sample_info = _get_sample_info(meg4_name, res4, system_clock)
            if sample_info['n_samp'] == 0:
                break
            if len(fnames) == 0:
                buffer_size_sec = sample_info['block_size'] / info['sfreq']
            else:
                buffer_size_sec = 1.
            fnames.append(meg4_name)
            last_samps.append(sample_info['n_samp'] - 1)
            raw_extras.append(sample_info)
            first_samps = [0] * len(last_samps)
        super(RawCTF, self).__init__(
            info, preload, first_samps=first_samps,
            last_samps=last_samps, filenames=fnames,
            raw_extras=raw_extras, orig_format='int',
            buffer_size_sec=buffer_size_sec, verbose=verbose)

        # Add bad segments as Annotations (correct for start time)
        start_time = -res4['pre_trig_pts'] / float(info['sfreq'])
        self.set_annotations(_annotate_bad_segments(directory, start_time))

        if clean_names:
            self._clean_names()

    @verbose
    def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
        """Read a chunk of raw data."""
        si = self._raw_extras[fi]
        offset = 0
        trial_start_idx, r_lims, d_lims = _blk_read_lims(start, stop,
                                                         int(si['block_size']))
        with open(self._filenames[fi], 'rb') as fid:
            for bi in range(len(r_lims)):
                samp_offset = (bi + trial_start_idx) * si['res4_nsamp']
                n_read = min(si['n_samp_tot'] - samp_offset, si['block_size'])
                # read the chunk of data
                pos = CTF.HEADER_SIZE
                pos += samp_offset * si['n_chan'] * 4
                fid.seek(pos, 0)
                this_data = np.fromfile(fid, '>i4',
                                        count=si['n_chan'] * n_read)
                this_data.shape = (si['n_chan'], n_read)
                this_data = this_data[:, r_lims[bi, 0]:r_lims[bi, 1]]
                data_view = data[:, d_lims[bi, 0]:d_lims[bi, 1]]