import logging
import pytest

from types import SimpleNamespace

from ophyd import EpicsMCA, EpicsDXP
from ophyd.mca import add_rois, Mercury1, SoftDXPTrigger

from ophyd.utils import enum, ReadOnlyError

from caproto.tests.conftest import run_example_ioc

MCAMode = enum(PHA='PHA', MCS='MCS', List='List')
DxpPresetMode = enum(No_preset='No preset',
                     Real_time='Real time',
                     Live_time='Live time')

logger = logging.getLogger(__name__)


@pytest.fixture(scope='function')
def mca_test_ioc(prefix, request):
    name = 'test_signal IOC'
    pvs = dict(mca_prefix=f'{prefix}mca',
               dxp_prefix=f'{prefix}dxp:'
               )

    process = run_example_ioc('ophyd.tests.mca_ioc',
                              request=request,
                              pv_to_check=pvs['mca_prefix'],
                              args=('--prefix', prefix, '-v'))
    return SimpleNamespace(process=process, prefix=prefix, name=name, pvs=pvs,
                           type='caproto')


@pytest.fixture(scope='function')
def mca(cleanup, mca_test_ioc):
    mca = EpicsMCA(mca_test_ioc.pvs['mca_prefix'], name='mca')
    mca.wait_for_connection()
    cleanup.add(mca)
    return mca


@pytest.fixture(scope='function')
def dxp(cleanup, mca_test_ioc):
    dxp = EpicsDXP(mca_test_ioc.pvs['dxp_prefix'], name='dxp')
    dxp.wait_for_connection()
    cleanup.add(dxp)
    return dxp


def test_mca_spectrum(mca):
    with pytest.raises(ReadOnlyError):
        mca.spectrum.put(3.14)
    with pytest.raises(ReadOnlyError):
        mca.background.put(3.14)


def test_mca_read_attrs(mca):
    # default read_attrs
    default_normal_kind = ['preset_real_time', 'elapsed_real_time', 'spectrum']
    assert set(default_normal_kind) == set(mca.read_attrs)
    # test passing in custom read_attrs (with dots!)
    r_attrs = ['spectrum', 'rois.roi1.count', 'rois.roi2.count']

    mca.read_attrs = r_attrs
    expected = set(r_attrs + ['rois.roi1', 'rois.roi2', 'rois'])
    assert expected == set(mca.read_attrs)


def test_mca_describe(mca):
    desc = mca.describe()
    d = desc[mca.name + '_spectrum']

    assert d['dtype'] == 'number'
    assert d['shape'] == []


def test_mca_signals(mca):
    mca.mode.put(MCAMode.PHA)
    mca.stage()
    mca.start.put(1)
    mca.stop_signal.put(1)
    mca.preset_real_time.put(3.14)
    mca.preset_live_time.put(3.14)
    mca.erase_start.put(1)
    mca.stop()
    mca.unstage()


def test_rois(mca):
    # iterables only
    with pytest.raises(TypeError):
        add_rois(1)
    # check range
    with pytest.raises(ValueError):
        add_rois([-1, ])
    with pytest.raises(ValueError):
        add_rois([32, ])
    # read-only?
    with pytest.raises(ReadOnlyError):
        mca.rois.roi1.count.put(3.14)
    with pytest.raises(ReadOnlyError):
        mca.rois.roi1.net_count.put(3.14)


def test_dxp_signals(dxp):
    # NOTE: values used below are those currently used at 23id2
    dxp.preset_mode.put(DxpPresetMode.Real_time)
    dxp.stage()
    dxp.unstage()

    dxp.trigger_peaking_time.put(0.2)
    dxp.trigger_threshold.put(0.6)
    dxp.trigger_gap_time.put(0.0)
    dxp.max_width.put(1.0)
    dxp.peaking_time.put(0.25)
    dxp.energy_threshold.put(0.35)
    dxp.gap_time.put(0.05)

    dxp.baseline_cut_percent.put(5.0)
    dxp.baseline_cut_enable.put(1)
    dxp.baseline_filter_length.put(128)
    dxp.baseline_threshold.put(0.20)

    dxp.preamp_gain.put(5.5)
    dxp.detector_polarity.put(1)
    dxp.reset_delay.put(10.0)
    dxp.decay_time.put(50.0)
    dxp.max_energy.put(2.0)
    dxp.adc_percent_rule.put(5.0)

    # read-only
    with pytest.raises(ReadOnlyError):
        dxp.triggers.put(2)
    with pytest.raises(ReadOnlyError):
        dxp.events.put(2)
    with pytest.raises(ReadOnlyError):
        dxp.overflows.put(2)
    with pytest.raises(ReadOnlyError):
        dxp.underflows.put(2)
    with pytest.raises(ReadOnlyError):
        dxp.input_count_rate.put(2)
    with pytest.raises(ReadOnlyError):
        dxp.output_count_rate.put(2)


def test_mixin_signals():
    class Mercury(Mercury1, SoftDXPTrigger):
        pass
    mercury = Mercury('Will_Not_Try_To_connect', name='mercury')
    assert 'count_time' in mercury.component_names
