"""Populate measurement info
"""

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

from time import strptime
from calendar import timegm

import numpy as np

from ...utils import logger, warn
from ...transforms import (apply_trans, _coord_frame_name, invert_transform,
                           combine_transforms)

from ..meas_info import _empty_info
from ..write import get_new_file_id
from ..ctf_comp import _add_kind, _calibrate_comp
from ..constants import FIFF

from .constants import CTF


def _pick_isotrak_and_hpi_coils(res4, coils, t):
    """Pick the HPI coil locations given in device coordinates"""
    if coils is None:
        return list(), list()
    dig = list()
    hpi_result = dict(dig_points=list())
    n_coil_dev = 0
    n_coil_head = 0
    for p in coils:
        if p['valid']:
            if p['coord_frame'] == FIFF.FIFFV_MNE_COORD_CTF_DEVICE:
                if t is None or t['t_ctf_dev_dev'] is None:
                    raise RuntimeError('No coordinate transformation '
                                       'available for HPI coil locations')
                d = dict(kind=FIFF.FIFFV_POINT_HPI, ident=p['kind'],
                         r=apply_trans(t['t_ctf_dev_dev'], p['r']),
                         coord_frame=FIFF.FIFFV_COORD_UNKNOWN)
                hpi_result['dig_points'].append(d)
                n_coil_dev += 1
            elif p['coord_frame'] == FIFF.FIFFV_MNE_COORD_CTF_HEAD:
                if t is None or t['t_ctf_head_head'] is None:
                    raise RuntimeError('No coordinate transformation '
                                       'available for (virtual) Polhemus data')
                d = dict(kind=FIFF.FIFFV_POINT_HPI, ident=p['kind'],
                         r=apply_trans(t['t_ctf_head_head'], p['r']),
                         coord_frame=FIFF.FIFFV_COORD_HEAD)
                dig.append(d)
                n_coil_head += 1
    if n_coil_head > 0:
        logger.info('    Polhemus data for %d HPI coils added' % n_coil_head)
    if n_coil_dev > 0:
        logger.info('    Device coordinate locations for %d HPI coils added'
                    % n_coil_dev)
    return dig, [hpi_result]


def _convert_time(date_str, time_str):
    """Convert date and time strings to float time"""
    for fmt in ("%d/%m/%Y", "%d-%b-%Y", "%a, %b %d, %Y"):
        try:
            date = strptime(date_str, fmt)
        except ValueError:
            pass
        else:
            break
    else:
        raise RuntimeError(
            'Illegal date: %s.\nIf the language of the date does not '
            'correspond to your local machine\'s language try to set the '
            'locale to the language of the date string:\n'
            'locale.setlocale(locale.LC_ALL, "en_US")' % date_str)

    for fmt in ('%H:%M:%S', '%H:%M'):
        try:
            time = strptime(time_str, fmt)
        except ValueError:
            pass
        else:
            break
    else:
        raise RuntimeError('Illegal time: %s' % time_str)
    # MNE-C uses mktime which uses local time, but here we instead decouple
    # conversion location from the process, and instead assume that the
    # acquisiton was in GMT. This will be wrong for most sites, but at least
    # the value we obtain here won't depend on the geographical location
    # that the file was converted.
    res = timegm((date.tm_year, date.tm_mon, date.tm_mday,
                  time.tm_hour, time.tm_min, time.tm_sec,
                  date.tm_wday, date.tm_yday, date.tm_isdst))
    return res


def _get_plane_vectors(ez):
    """Get two orthogonal vectors orthogonal to ez (ez will be modified)"""
    assert ez.shape == (3,)
    ez_len = np.sqrt(np.sum(ez * ez))
    if ez_len == 0:
        raise RuntimeError('Zero length normal. Cannot proceed.')
    if np.abs(ez_len - np.abs(ez[2])) < 1e-5:  # ez already in z-direction
        ex = np.array([1., 0., 0.])
    else:
        ex = np.zeros(3)
        if ez[1] < ez[2]:
            ex[0 if ez[0] < ez[1] else 1] = 1.
        else:
            ex[0 if ez[0] < ez[2] else 2] = 1.
    ez /= ez_len
    ex -= np.dot(ez, ex) * ez
    ex /= np.sqrt(np.sum(ex * ex))
    ey = np.cross(ez, ex)
    return ex, ey


def _at_origin(x):
    """Determine if a vector is at the origin"""
    return (np.sum(x * x) < 1e-8)


def _check_comp_ch(cch, kind, desired=None):
    if 'reference' in kind.lower():
        if cch['grad_order_no'] != 0:
            raise RuntimeError('%s channel with non-zero compensation grade %s'
                               % (kind, cch['grad_order_no']))
    else:
        if desired is None:
            desired = cch['grad_order_no']
        if cch['grad_order_no'] != desired:
            raise RuntimeError('%s channel with inconsistent compensation '
                               'grade %s, should be %s'
                               % (kind, cch['grad_order_no'], desired))
    return desired


def _convert_channel_info(res4, t, use_eeg_pos):
    """Convert CTF channel information to fif format"""
    nmeg = neeg = nstim = nmisc = nref = 0
    chs = list()
    this_comp = None
    for k, cch in enumerate(res4['chs']):
        cal = float(1. / (cch['proper_gain'] * cch['qgain']))
        ch = dict(scanno=k + 1, range=1., cal=cal, loc=np.zeros(12),
                  unit_mul=FIFF.FIFF_UNITM_NONE, ch_name=cch['ch_name'][:15],
                  coil_type=FIFF.FIFFV_COIL_NONE)
        del k
        chs.append(ch)
        # Create the channel position information
        pos = dict(r0=ch['loc'][:3], ex=ch['loc'][3:6], ey=ch['loc'][6:9],
                   ez=ch['loc'][9:12])
        if cch['sensor_type_index'] in (CTF.CTFV_REF_MAG_CH,
                                        CTF.CTFV_REF_GRAD_CH,
                                        CTF.CTFV_MEG_CH):
            # Extra check for a valid MEG channel
            if np.sum(cch['coil']['pos'][0] ** 2) < 1e-6 or \
                    np.sum(cch['coil']['norm'][0] ** 2) < 1e-6:
                nmisc += 1
                ch.update(logno=nmisc, coord_frame=FIFF.FIFFV_COORD_UNKNOWN,
                          kind=FIFF.FIFFV_MISC_CH, unit=FIFF.FIFF_UNIT_V)
                text = 'MEG'
                if cch['sensor_type_index'] != CTF.CTFV_MEG_CH:
                    text += ' ref'
                warn('%s channel %s did not have position assigned, so '
                     'it was changed to a MISC channel'
                     % (text, ch['ch_name']))
                continue
            ch['unit'] = FIFF.FIFF_UNIT_T
            # Set up the local coordinate frame
            pos['r0'][:] = cch['coil']['pos'][0]
            pos['ez'][:] = cch['coil']['norm'][0]
            # It turns out that positive proper_gain requires swapping
            # of the normal direction
            if cch['proper_gain'] > 0.0:
                pos['ez'] *= -1
            # Check how the other vectors should be defined
            off_diag = False
            # Default: ex and ey are arbitrary in the plane normal to ez
            if cch['sensor_type_index'] == CTF.CTFV_REF_GRAD_CH:
                # The off-diagonal gradiometers are an exception:
                #
                # We use the same convention for ex as for Neuromag planar
                # gradiometers: ex pointing in the positive gradient direction
                diff = cch['coil']['pos'][0] - cch['coil']['pos'][1]
                size = np.sqrt(np.sum(diff * diff))
                if size > 0.:
                    diff /= size
                # Is ez normal to the line joining the coils?
                if np.abs(np.dot(diff, pos['ez'])) < 1e-3:
                    off_diag = True
                    # Handle the off-diagonal gradiometer coordinate system
                    pos['r0'] -= size * diff / 2.0
                    pos['ex'][:] = diff
                    pos['ey'][:] = np.cross(pos['ez'], pos['ex'])
                else:
                    pos['ex'][:], pos['ey'][:] = _get_plane_vectors(pos['ez'])
            else:
                pos['ex'][:], pos['ey'][:] = _get_plane_vectors(pos['ez'])
            # Transform into a Neuromag-like device coordinate system
            pos['r0'][:] = apply_trans(t['t_ctf_dev_dev'], pos['r0'])
            for key in ('ex', 'ey', 'ez'):
                pos[key][:] = apply_trans(t['t_ctf_dev_dev'], pos[key],
                                          move=False)
            # Set the coil type
            if cch['sensor_type_index'] == CTF.CTFV_REF_MAG_CH:
                ch['kind'] = FIFF.FIFFV_REF_MEG_CH
                _check_comp_ch(cch, 'Reference magnetometer')
                ch['coil_type'] = FIFF.FIFFV_COIL_CTF_REF_MAG
                nref += 1
                ch['logno'] = nref
            elif cch['sensor_type_index'] == CTF.CTFV_REF_GRAD_CH:
                ch['kind'] = FIFF.FIFFV_REF_MEG_CH
                if off_diag:
                    _check_comp_ch(cch, 'Reference off-diagonal gradiometer')
                    ch['coil_type'] = FIFF.FIFFV_COIL_CTF_OFFDIAG_REF_GRAD
                else:
                    _check_comp_ch(cch, 'Reference gradiometer')
                    ch['coil_type'] = FIFF.FIFFV_COIL_CTF_REF_GRAD
                nref += 1
                ch['logno'] = nref
            else:
                this_comp = _check_comp_ch(cch, 'Gradiometer', this_comp)
                ch['kind'] = FIFF.FIFFV_MEG_CH
                ch['coil_type'] = FIFF.FIFFV_COIL_CTF_GRAD
                nmeg += 1
                ch['logno'] = nmeg
            # Encode the software gradiometer order
            ch['coil_type'] = ch['coil_type'] | (cch['grad_order_no'] << 16)
            ch['coord_frame'] = FIFF.FIFFV_COORD_DEVICE
        elif cch['sensor_type_index'] == CTF.CTFV_EEG_CH:
            coord_frame = FIFF.FIFFV_COORD_HEAD
            if use_eeg_pos:
                # EEG electrode coordinates may be present but in the
                # CTF head frame
                pos['r0'][:] = cch['coil']['pos'][0]