from typing import Iterable
import pytest       # type: ignore
import numpy
from numpy.typing import NDArray
from numpy.testing import assert_allclose

from .. import fdfd
from ..fdmath import vec, unvec, dx_lists_mut
#from .utils import assert_close, assert_fields_close
from .test_fdfd import FDResult
from .conftest import FixtureRequest


def test_pml(sim: FDResult, src_polarity: int) -> None:
    e_sqr = numpy.squeeze((sim.e.conj() * sim.e).sum(axis=0))

#    from matplotlib import pyplot
#    pyplot.figure()
#    pyplot.plot(numpy.squeeze(e_sqr))
#    pyplot.show(block=True)

    e_sqr_tgt = e_sqr[16:19]
    e_sqr_rev = e_sqr[10:13]
    if src_polarity < 0:
        e_sqr_tgt, e_sqr_rev = e_sqr_rev, e_sqr_tgt

    assert_allclose(e_sqr_rev, 0, atol=1e-12)
    assert_allclose(e_sqr_tgt, 1, rtol=3e-6)


#    pyplot.figure()
#    pyplot.plot(numpy.squeeze(sim.e[0].real), label='Ex_real')
#    pyplot.plot(numpy.squeeze(sim.e[0].imag), label='Ex_imag')
#    pyplot.plot(numpy.squeeze(sim.e[1].real), label='Ey_real')
#    pyplot.plot(numpy.squeeze(sim.e[1].imag), label='Ey_imag')
#    pyplot.plot(numpy.squeeze(sim.e[2].real), label='Ez_real')
#    pyplot.plot(numpy.squeeze(sim.e[2].imag), label='Ez_imag')
#    pyplot.legend()
#    pyplot.show(block=True)


#      Test fixtures
# ####################################
# Also see conftest.py

@pytest.fixture(params=[1 / 1500])
def omega(request: FixtureRequest) -> Iterable[float]:
    yield request.param


@pytest.fixture(params=[None])
def pec(request: FixtureRequest) -> Iterable[NDArray[numpy.float64] | None]:
    yield request.param


@pytest.fixture(params=[None])
def pmc(request: FixtureRequest) -> Iterable[NDArray[numpy.float64] | None]:
    yield request.param


@pytest.fixture(params=[(30, 1, 1),
                        (1, 30, 1),
                        (1, 1, 30)])
def shape(request: FixtureRequest) -> Iterable[tuple[int, ...]]:
    yield (3, *request.param)


@pytest.fixture(params=[+1, -1])
def src_polarity(request: FixtureRequest) -> Iterable[int]:
    yield request.param


@pytest.fixture()
def j_distribution(
        request: FixtureRequest,
        shape: tuple[int, ...],
        epsilon: NDArray[numpy.float64],
        dxes: dx_lists_mut,
        omega: float,
        src_polarity: int,
        ) -> Iterable[NDArray[numpy.complex128]]:
    j = numpy.zeros(shape, dtype=complex)

    dim = numpy.where(numpy.array(shape[1:]) > 1)[0][0]    # Propagation axis
    other_dims = [0, 1, 2]
    other_dims.remove(dim)

    dx_prop = (dxes[0][dim][shape[dim + 1] // 2]
             + dxes[1][dim][shape[dim + 1] // 2]) / 2   # noqa: E128   # TODO is this right for nonuniform dxes?

    # Mask only contains components orthogonal to propagation direction
    center_mask = numpy.zeros(shape, dtype=bool)
    center_mask[other_dims, shape[1] // 2, shape[2] // 2, shape[3] // 2] = True
    if (epsilon[center_mask] != epsilon[center_mask][0]).any():
        center_mask[other_dims[1]] = False          # If epsilon is not isotropic, pick only one dimension

    wavenumber = omega * numpy.sqrt(epsilon[center_mask].mean())
    wavenumber_corrected = 2 / dx_prop * numpy.arcsin(wavenumber * dx_prop / 2)

    e = numpy.zeros_like(epsilon, dtype=complex)
    e[center_mask] = 1 / numpy.linalg.norm(center_mask[:])

    slices = [slice(None), slice(None), slice(None)]
    slices[dim] = slice(shape[dim + 1] // 2,
                        shape[dim + 1] // 2 + 1)

    j = fdfd.waveguide_3d.compute_source(E=e, wavenumber=wavenumber_corrected, omega=omega, dxes=dxes,
                                         axis=dim, polarity=src_polarity, slices=slices, epsilon=epsilon)
    yield j


@pytest.fixture()
def epsilon(
        request: FixtureRequest,
        shape: tuple[int, ...],
        epsilon_bg: float,
        epsilon_fg: float,
        ) -> Iterable[NDArray[numpy.float64]]:
    epsilon = numpy.full(shape, epsilon_fg, dtype=float)
    yield epsilon


@pytest.fixture(params=['uniform'])
def dxes(
        request: FixtureRequest,
        shape: tuple[int, ...],
        dx: float,
        omega: float,
        epsilon_fg: float,
        ) -> Iterable[list[list[NDArray[numpy.float64]]]]:
    if request.param == 'uniform':
        dxes = [[numpy.full(s, dx) for s in shape[1:]] for _ in range(2)]
    dim = numpy.where(numpy.array(shape[1:]) > 1)[0][0]    # Propagation axis
    for axis in (dim,):
        for polarity in (-1, 1):
            dxes = fdfd.scpml.stretch_with_scpml(
                dxes,
                axis=axis,
                polarity=polarity,
                omega=omega,
                epsilon_effective=epsilon_fg,
                thickness=10,
                )
    yield dxes


@pytest.fixture()
def sim(
        request: FixtureRequest,
        shape: tuple[int, ...],
        epsilon: NDArray[numpy.float64],
        dxes: dx_lists_mut,
        j_distribution: NDArray[numpy.complex128],
        omega: float,
        pec: NDArray[numpy.float64] | None,
        pmc: NDArray[numpy.float64] | None,
        ) -> FDResult:
    j_vec = vec(j_distribution)
    eps_vec = vec(epsilon)
    e_vec = fdfd.solvers.generic(
        J=j_vec,
        omega=omega,
        dxes=dxes,
        epsilon=eps_vec,
        matrix_solver_opts={'atol': 1e-15, 'tol': 1e-11},
        )
    e = unvec(e_vec, shape[1:])

    sim = FDResult(
        shape=shape,
        dxes=[list(d) for d in dxes],
        epsilon=epsilon,
        j=j_distribution,
        e=e,
        pec=pec,
        pmc=pmc,
        omega=omega,
        )

    return sim
