# -*- coding: utf-8 -*-
#-----------------------------------------------------------------------------
#  OpenModes - An eigenmode solver for open electromagnetic resonantors
#  Copyright (C) 2013 David Powell
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#-----------------------------------------------------------------------------
from __future__ import print_function

import os.path as osp

import numpy as np
from numpy.testing import assert_allclose
import matplotlib.pyplot as plt
import scipy.linalg as la

import openmodes
import openmodes.basis
from openmodes.sources import PlaneWaveSource
from openmodes.constants import c
from openmodes.integration import triangle_centres

from helpers import (read_1d_complex, write_1d_complex,
                     read_2d_real, write_2d_real)

tests_location = osp.split(__file__)[0]
mesh_dir = osp.join(tests_location, 'input', 'test_horseshoe')
reference_dir = osp.join(tests_location, 'reference', 'test_horseshoe')


def assert_allclose_sign(a, b, rtol):
    """Compare two arrays which should be equal, to within a sign ambiguity
    of the whole array (not of each element)"""
    assert(np.all(np.abs(a-b) < rtol*abs(a)) or
           np.all(np.abs(a+b) < rtol*abs(a)))


def test_horseshoe_modes(plot=False, skip_asserts=False,
                         write_reference=False):
    "Modes of horseshoe"
    sim = openmodes.Simulation(name='horseshoe_modes',
                               basis_class=openmodes.basis.LoopStarBasis)
    shoe = sim.load_mesh(osp.join(mesh_dir, 'horseshoe_rect.msh'))
    sim.place_part(shoe)

    s_start = 2j*np.pi*10e9

    estimates = sim.estimate_poles(s_start, modes=3, cauchy_integral=False)
    modes = sim.refine_poles(estimates)
    mode_s = modes.s
    mode_j = modes.vr
    print("Singularities found at", mode_s)

    if write_reference:
        write_1d_complex(osp.join(reference_dir, 'eigenvector_0.txt'),
                         mode_j["J", :, 'modes', 0])
        write_1d_complex(osp.join(reference_dir, 'eigenvector_1.txt'),
                         mode_j["J", :, 'modes', 1])
        write_1d_complex(osp.join(reference_dir, 'eigenvector_2.txt'),
                         mode_j["J", :, 'modes', 2])

    j_0_ref = read_1d_complex(osp.join(reference_dir, 'eigenvector_0.txt'))
    j_1_ref = read_1d_complex(osp.join(reference_dir, 'eigenvector_1.txt'))
    j_2_ref = read_1d_complex(osp.join(reference_dir, 'eigenvector_2.txt'))

    if not skip_asserts:
        assert_allclose(mode_s[0], [-2.585729e+09 + 3.156438e+10j,
                                 -1.887518e+10 + 4.500579e+10j,
                                 -1.991163e+10 + 6.846221e+10j],
                        rtol=1e-3)
        assert_allclose_sign(mode_j["J", :, 'modes', 0], j_0_ref, rtol=1e-2)
        assert_allclose_sign(mode_j["J", :, 'modes', 1], j_1_ref, rtol=1e-2)
        assert_allclose_sign(mode_j["J", :, 'modes', 2], j_2_ref, rtol=1e-2)

    if plot:
        sim.plot_3d(solution=mode_j["J", :, 'modes', 0], output_format='mayavi',
                    compress_scalars=3)
        sim.plot_3d(solution=mode_j["J", :, 'modes', 1], output_format='mayavi',
                    compress_scalars=3)
        sim.plot_3d(solution=mode_j["J", :, 'modes', 2], output_format='mayavi',
                    compress_scalars=3)


def test_surface_normals(plot=False, skip_asserts=False,
                         write_reference=False):
    "Test the surface normals of a horseshoe mesh"
    sim = openmodes.Simulation()
    mesh = sim.load_mesh(osp.join(mesh_dir, 'horseshoe_rect.msh'))
    part = sim.place_part(mesh)
    basis = sim.basis_container[part]

    r, rho = basis.integration_points(mesh.nodes, triangle_centres)
    normals = mesh.surface_normals
    r = r.reshape((-1, 3))

    if write_reference:
        write_2d_real(osp.join(reference_dir, 'surface_r.txt'), r)
        write_2d_real(osp.join(reference_dir, 'surface_normals.txt'), normals)

    r_ref = read_2d_real(osp.join(reference_dir, 'surface_r.txt'))
    normals_ref = read_2d_real(osp.join(reference_dir, 'surface_normals.txt'))

    if not skip_asserts:
        assert_allclose(r, r_ref)
        assert_allclose(normals, normals_ref)

    if plot:
        from mayavi import mlab
        mlab.figure()
        mlab.quiver3d(r[:, 0], r[:, 1], r[:, 2],
                      normals[:, 0], normals[:, 1], normals[:, 2],
                      mode='cone')
        mlab.view(distance='auto')
        mlab.show()


def test_extinction(plot_extinction=False, skip_asserts=False,
                    write_reference=False):
    "Test extinction of a horseshoe"
    sim = openmodes.Simulation(name='horseshoe_extinction',
                               basis_class=openmodes.basis.LoopStarBasis)

    shoe = sim.load_mesh(osp.join(mesh_dir, 'horseshoe_rect.msh'))
    sim.place_part(shoe)

    num_freqs = 101
    freqs = np.linspace(1e8, 20e9, num_freqs)

    extinction = np.empty(num_freqs, np.complex128)

    e_inc = np.array([1, 0, 0], dtype=np.complex128)
    k_hat = np.array([0, 0, 1], dtype=np.complex128)
    pw = PlaneWaveSource(e_inc, k_hat)

    for freq_count, s in sim.iter_freqs(freqs):
        Z = sim.impedance(s)
        V = sim.source_vector(pw, s)
        extinction[freq_count] = np.vdot(V, Z.solve(V))

    if write_reference:
        # generate the reference extinction solution
        write_1d_complex(osp.join(reference_dir, 'extinction.txt'), extinction)

    extinction_ref = read_1d_complex(osp.join(reference_dir, 'extinction.txt'))

    if not skip_asserts:
        assert_allclose(extinction, extinction_ref, rtol=1e-3)

    if plot_extinction:
        # to plot the generated and reference solutions
        plt.figure()
        plt.plot(freqs*1e-9, extinction.real)
        plt.plot(freqs*1e-9, extinction_ref.real, '--')
        plt.plot(freqs*1e-9, extinction.imag)
        plt.plot(freqs*1e-9, extinction_ref.imag, '--')
        plt.xlabel('f (GHz)')
        plt.show()


def horseshoe_extinction_modes():
    sim = openmodes.Simulation(name='horseshoe_extinction_modes',
                               basis_class=openmodes.basis.LoopStarBasis)
    shoe = sim.load_mesh(osp.join('input', 'test_horseshoe',
                                  'horseshoe_rect.msh'))
    sim.place_part(shoe)

    s_start = 2j*np.pi*10e9

    num_modes = 5
    mode_s, mode_j = sim.part_singularities(s_start, num_modes)

    models = sim.construct_models(mode_s, mode_j)[0]

    num_freqs = 101
    freqs = np.linspace(1e8, 20e9, num_freqs)

    extinction = np.empty(num_freqs, np.complex128)
    extinction_sem = np.empty((num_freqs, num_modes), np.complex128)
    extinction_eem = np.empty((num_freqs, num_modes), np.complex128)

    e_inc = np.array([1, 0, 0], dtype=np.complex128)
    k_hat = np.array([0, 0, 1], dtype=np.complex128)

    z_sem = np.empty((num_freqs, num_modes), np.complex128)
    z_eem = np.empty((num_freqs, num_modes), np.complex128)
    z_eem_direct = np.empty((num_freqs, num_modes), np.complex128)

    for freq_count, s in sim.iter_freqs(freqs):
        Z = sim.impedance(s)[0][0]
        V = sim.source_plane_wave(e_inc, s/c*k_hat)[0]

        extinction[freq_count] = np.vdot(V, la.solve(Z[:], V))

        z_sem[freq_count] = [model.scalar_impedance(s) for model in models]
        extinction_sem[freq_count] = [np.vdot(V, model.solve(s, V)) for model in models]

        z_eem_direct[freq_count], _ = Z.eigenmodes(num_modes, use_gram=False)

        z_eem[freq_count], j_eem = Z.eigenmodes(start_j = mode_j[0], use_gram=True)
        extinction_eem[freq_count] = [np.vdot(V, j_eem[:, mode])*np.dot(V, j_eem[:, mode])/z_eem[freq_count, mode] for mode in range(num_modes)]

    plt.figure(figsize=(10, 5))
    plt.subplot(121)
    plt.plot(freqs*1e-9, extinction.real)
    plt.plot(freqs*1e-9, np.sum(extinction_sem.real, axis=1), '--')
    plt.plot(freqs*1e-9, np.sum(extinction_eem.real, axis=1), '-.')
    plt.xlabel('f (GHz)')
    plt.subplot(122)
    plt.plot(freqs*1e-9, extinction.imag)
    plt.plot(freqs*1e-9, np.sum(extinction_sem.imag, axis=1), '--')
    plt.plot(freqs*1e-9, np.sum(extinction_eem.imag, axis=1), '-.')
    plt.suptitle("Extinction")
    plt.show()

    plt.figure(figsize=(10, 5))
    plt.subplot(121)
    plt.plot(freqs*1e-9, z_eem_direct.real)
    #plt.ylim(0, 80)
    plt.xlabel('f (GHz)')
    plt.subplot(122)
    plt.plot(freqs*1e-9, z_eem_direct.imag)
    plt.plot([freqs[0]*1e-9, freqs[-1]*1e-9], [0, 0], 'k')
    plt.suptitle("EEM impedance without Gram matrix")
    plt.show()

    plt.figure(figsize=(10, 5))
    plt.subplot(121)
    plt.plot(freqs*1e-9, z_eem.real)
    plt.plot(freqs*1e-9, z_sem.real, '--')
    #plt.ylim(0, 80)
    plt.xlabel('f (GHz)')
    plt.subplot(122)
    plt.plot(freqs*1e-9, z_eem.imag)
    plt.plot(freqs*1e-9, z_sem.imag, '--')
    plt.ylim(-100, 100)
#    plt.semilogy(freqs*1e-9, abs(z_eem.imag))
#    plt.semilogy(freqs*1e-9, abs(z_sem.imag), '--')
    plt.suptitle("SEM and EEM impedance")
    plt.show()

    y_sem = 1/z_sem
    y_eem = 1/z_eem

    plt.figure(figsize=(10, 5))
    plt.subplot(121)
    plt.plot(freqs*1e-9, y_eem.real)
    plt.plot(freqs*1e-9, y_sem.real, '--')
    plt.xlabel('f (GHz)')
    plt.subplot(122)
    plt.plot(freqs*1e-9, y_eem.imag)
    plt.plot(freqs*1e-9, y_sem.imag, '--')
    plt.xlabel('f (GHz)')
    plt.suptitle("SEM and EEM admittance")
    plt.show()

if __name__ == "__main__":
    #test_extinction_modes()
    test_horseshoe_modes(plot=True, skip_asserts=True)
    test_extinction(plot_extinction=True, skip_asserts=True)
    test_surface_normals(plot=True, skip_asserts=True)
