import copy
import warnings

import brian2 as br
import matplotlib.pyplot as plt
import numpy as np
from brian2.units import *


def set_parameters_from_dict(neurongroup, dictionary_of_parameters):
    for param_key, param_value in dictionary_of_parameters.items():
        try:
            neurongroup.__setattr__(param_key, param_value)
        except AttributeError as err:
            warnings.warn("{:s} has no parameter {:s}".format(neurongroup.name, param_key))


def get_mean_period(spike_train):
    return (np.max(spike_train) - np.min(spike_train)) / (spike_train.shape[0] - 1)


def get_difference_in_periods(spike_trains_dict, t_isi):
    mean_response_dict = {}
    #     t_isi = get_mean_period(spike_trains_dict[0.0 * ms])
    for delay, spike_train in spike_trains_dict.items():
        mean_response_dict[delay] = get_mean_period(spike_train) - t_isi
    return mean_response_dict


def plot_spiking(spike_trains_dict):
    fig = plt.figure()
    ax = fig.add_subplot(111)
    for key, times in spike_trains_dict.items():
        ax.plot(times / ms, key * np.ones(times.shape), 'b.')
    #     ax.plot(spike_train[0]/ms, np.ones(spike_train[0].shape), 'b|')

    ax.grid(axis='x')
    #     ax.set_ylim(-0.1, 1.1)
    ax.set_xlabel("Time(ms)")


def plot_mean_period(mean_period_dict):
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.plot(list(mean_period_dict.keys()), list(mean_period_dict.values()), 'b')

    ax.grid(axis='x')
    ax.set_xlabel("Delay (ms)")


def plot_mean_response(mean_response_dict):
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.plot(np.array(list(mean_response_dict.keys())), 1e3 * np.array(list(mean_response_dict.values())), 'b')
    ax.plot(list(mean_response_dict.keys()), list(mean_response_dict.keys()), 'k--')

    ax.grid(axis='x')
    ax.set_xlabel("t_inh (ms)")
    ax.set_ylabel("exc_spike_delay (ms)")


def plot_delay_minus_response(mean_response_dict):
    fig = plt.figure()
    ax = fig.add_subplot(111)
    print(np.array(list(mean_response_dict.keys())))
    print(np.array(list(mean_response_dict.values())))
    print(np.array(list(mean_response_dict.keys())) - np.array(list(mean_response_dict.values())))
    ax.plot(list(mean_response_dict.keys()),
            np.array(list(mean_response_dict.keys())) - 1e3 * np.array(list(mean_response_dict.values())), 'b')

    ax.grid(axis='x')
    ax.set_xlabel("Delay (ms)");


def plot_phase_response_curve(mean_response_dict, t_isi):
    phi_inh = np.array(list(mean_response_dict.keys())) * ms / t_isi
    delta_phi = -np.array(list(mean_response_dict.values())) * second / t_isi

    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.plot(phi_inh, delta_phi, 'b')
    ax.plot(phi_inh, -phi_inh, 'k--')

    ax.grid(axis='x')
    ax.set_xlabel("phase of inhibition (ms)")
    ax.set_ylabel("phase shift (ms)");


# Hodgkin Huxley model from Brian2 documentation
area = 20000 * umetre ** 2

hodgkin_huxley_params = {
    "Cm": 1 * ufarad * cm ** -2 * area,
    "gl": 5e-5 * siemens * cm ** -2 * area,
    "El": -65 * mV,
    "EK": -90 * mV,
    "ENa": 50 * mV,
    "g_na": 100 * msiemens * cm ** -2 * area,
    "g_kd": 30 * msiemens * cm ** -2 * area,
    "VT": -63 * mV
}
# The model
hodgkin_huxley_eqs = br.Equations('''
dv/dt = (gl*(El-v) - g_na*(m*m*m)*h*(v-ENa) - g_kd*(n*n*n*n)*(v-EK) + I + ih)/Cm : volt
dm/dt = 0.32*(mV**-1)*(13.*mV-v+VT)/
    (exp((13.*mV-v+VT)/(4.*mV))-1.)/ms*(1-m)-0.28*(mV**-1)*(v-VT-40.*mV)/
    (exp((v-VT-40.*mV)/(5.*mV))-1.)/ms*m : 1
dn/dt = 0.032*(mV**-1)*(15.*mV-v+VT)/
    (exp((15.*mV-v+VT)/(5.*mV))-1.)/ms*(1.-n)-.5*exp((10.*mV-v+VT)/(40.*mV))/ms*n : 1
dh/dt = 0.128*exp((17.*mV-v+VT)/(18.*mV))/ms*(1.-h)-4./(1+exp((40.*mV-v+VT)/(5.*mV)))/ms*h : 1
I : amp
''')

hodgkin_huxley_eqs_with_synaptic_conductance = br.Equations('''
dv/dt = (gl*(El-v) - g_na*(m*m*m)*h*(v-ENa) - g_kd*(n*n*n*n)*(v-EK) + I + ih - g_syn*(v-E_i))/Cm : volt
dm/dt = 0.32*(mV**-1)*(13.*mV-v+VT)/
    (exp((13.*mV-v+VT)/(4.*mV))-1.)/ms*(1-m)-0.28*(mV**-1)*(v-VT-40.*mV)/
    (exp((v-VT-40.*mV)/(5.*mV))-1.)/ms*m : 1
dn/dt = 0.032*(mV**-1)*(15.*mV-v+VT)/
    (exp((15.*mV-v+VT)/(5.*mV))-1.)/ms*(1.-n)-.5*exp((10.*mV-v+VT)/(40.*mV))/ms*n : 1
dh/dt = 0.128*exp((17.*mV-v+VT)/(18.*mV))/ms*(1.-h)-4./(1+exp((40.*mV-v+VT)/(5.*mV)))/ms*h : 1
I : amp
g_syn: siemens
''')

# # First H-current from Izhikevich p. 48
# ghbar = 40. * nS #Other values would be 0.5, 2, 3.5, 20 depending on neuron type (Rothman, Manis)
# Eh = -43*mV
# V_half_h = -75.0
# k_h = -5.5
# V_max_h = -75.
# sigma_h = 15.
# C_amp_h = 1000.
# C_base_h = 100.
# eqs_ih = """
# ih = ghbar*r*(Eh-v) : amp
# dr/dt= (rinf-r)/rtau : 1
# rinf = 1. / (1+exp((V_half_h - v/mV) / k_h)) : 1
# rtau = (C_base_h + C_amp_h * exp(-(V_max_h-v/mV)**2./sigma_h**2)) * ms : second
# """
# Second H-current from Izhikevich p. 48


ih_params = {
    "ghbar": 60. * nS,  # Other values would be 0.5, 2, 3.5, 20 depending on neuron type (Rothman, Manis)
    "Eh": -1. * mV,
    "V_half_h": -98.0,
    "k_h": -5.5,
    "V_max_h": -75.,
    "sigma_h": 25.,
    "C_amp_h": 60.,
    "C_base_h": 40.,
}

eqs_ih = """
ih = ghbar*r*(Eh-v) : amp
dr/dt= (rinf-r)/rtau : 1
rinf = 1. / (1+exp((V_half_h - v/mV) / k_h)) : 1
rtau = 0.1*(C_base_h + C_amp_h * exp(-(V_max_h-v/mV)**2./sigma_h**2)) * ms : second
"""

# eqs_ih = """
# ih = ghbar*r*(Eh-v) : amp
# dr/dt=(rinf-r)/rtau : 1
# rinf = 1. / (1+exp((v/mV + 90.) / 7.)) : 1
# # rtau = ((100000. / (237.*exp((v/mV+60.) / 12.) + 17.*exp(-(v/mV+60.) / 14.))) + 25.)*ms : second
# rtau = 0.01*(100. + 1000. * exp(-(-76.-v/mV)**2./15.**2)) * ms : second #From Izhikevich
# """


delta_synapse_model = 'synaptic_strength: volt'
delta_synapse_on_pre = 'v+=synaptic_strength'
delta_synapse_param = {}

exponential_synapse = """
dg/dt = -g/tau_syn : siemens
g_syn_post = g :siemens (summed)
tau_syn : second
synaptic_strength : siemens
"""
exponential_synapse_on_pre = "g+=synaptic_strength"
exponential_synapse_params = {
    "tau_syn": 5 * ms
}

spike_threshold = +40 * mV

## With voltage delta synapse
neuron_eqs = hodgkin_huxley_eqs+eqs_ih

synapse_model = delta_synapse_model
synapse_on_pre = delta_synapse_on_pre
synapse_params = delta_synapse_param

neuron_params = hodgkin_huxley_params
neuron_params.update(ih_params)

inhibition_off = 0.0 * mV
inhibition_on = -30*mV

# ### With conductance based delta synapse
# neuron_eqs = hodgkin_huxley_eqs_with_synaptic_conductance + eqs_ih
#
# synapse_model = exponential_synapse
# synapse_on_pre = exponential_synapse_on_pre
# synapse_params = exponential_synapse_params
#
# neuron_params = hodgkin_huxley_params
# neuron_params.update(ih_params)
# neuron_params.update({"E_i": -80 * mV})
#
# inhibition_off = 0.0 * nS
# inhibition_on = 100 * nS

network_params = copy.deepcopy(neuron_params)
network_params.update(synapse_params)

record_variables = ['v', 'ih']
integration_method = 'exponential_euler'

initial_states = {
    "v": hodgkin_huxley_params["El"]
}

threshold_eqs = """
v_threshold: volt
"""

neuron = br.NeuronGroup(N=1, \
                        model=neuron_eqs + threshold_eqs, \
                        threshold='v > v_threshold', \
                        refractory='v > v_threshold', \
                        method=integration_method)

neuron.v_threshold = spike_threshold
set_parameters_from_dict(neuron, initial_states)
neuron.I = 0.5 * nA

spike_recorder = br.SpikeMonitor(source=neuron)
neuron_state_recorder = br.StateMonitor(neuron, record_variables, record=True)

auto_synapse = br.Synapses(source=neuron, target=neuron, model=synapse_model, on_pre=synapse_on_pre,
                           delay=0.0 * ms)
auto_synapse.connect()
set_parameters_from_dict(auto_synapse, synapse_params)
net = br.Network(neuron)
net.add(auto_synapse)
net.add(spike_recorder)
net.add(neuron_state_recorder)
net.store()


def run_sim(delay=0.0, pulse_strength=0.0, record_states=False, duration=50 * ms):
    net.restore()
    neuron_state_recorder.record = record_states
    auto_synapse.delay = delay
    auto_synapse.synaptic_strength = pulse_strength

    net.run(duration=duration, namespace=network_params)


run_sim(delay=3 * ms, record_states=True, pulse_strength=inhibition_off, duration=200 * ms)

t_state = neuron_state_recorder.t
v_state = neuron_state_recorder[1].v
ih_state = neuron_state_recorder[1].ih
spike_train = spike_recorder.spike_trains()[0]

fig, ax1 = plt.subplots()
ax1.plot(t_state / ms, v_state / ms, 'C2')

plt.show()

period_unperturbed = get_mean_period(spike_train)
print(period_unperturbed)

n_simulations = 40
inhibitory_delay = np.linspace(1 * ms, period_unperturbed - 1 * ms, n_simulations)

spike_trains_dict = {}
periods = []


for delay in inhibitory_delay:
    run_sim(delay, inhibition_on, record_states=False, duration=100 * ms)
    spike_train = spike_recorder.spike_trains()[0]
    #     print(spike_train)
    spike_trains_dict[delay / ms] = spike_train  # Careful, delay is not a Quantity anymore because in must be hashable
    periods.append(get_mean_period(spike_train))

periods = np.array(periods)

plt.plot(inhibitory_delay/ms, periods/ms)
plt.hlines(period_unperturbed/ms, inhibitory_delay[0]/ms, inhibitory_delay[-1]/ms, label="No inhibition")
plt.xlabel("Delay spike to inhibition (ms)")
plt.ylabel("Period (ms")
plt.legend()


t_diff_dict = get_difference_in_periods(spike_trains_dict, period_unperturbed)


phases = np.array([delay / period_unperturbed for delay in inhibitory_delay])
phase_diff = np.array([-t_diff_dict[delay / ms] / period_unperturbed for delay in inhibitory_delay])

optimal_inhibitory_delay_idx = np.argmax(phase_diff)
optimal_inhibitory_delay = list(t_diff_dict.keys())[optimal_inhibitory_delay_idx]  # assumes a single maximum
optimal_inhibitory_delay_phase = phases[optimal_inhibitory_delay_idx]  # assumes a single maximum



plt.figure()
plt.plot(phases, phase_diff)
plt.vlines(optimal_inhibitory_delay, -1, 1)
plt.title("Phase response curve")
plt.ylim(-1, 1)

plt.figure()

if optimal_inhibitory_delay_idx == 0:
    delta_i = phases
else:
    delta_i = phases[:-optimal_inhibitory_delay_idx]

print(delta_i.shape)

delta_iplus1 = delta_i + 1 + phase_diff[optimal_inhibitory_delay_idx:] - phase_diff[optimal_inhibitory_delay_idx]


plt.plot(delta_i, delta_iplus1)
plt.plot(delta_i, delta_i, '--')
plt.hlines(1, 0, 1)
plt.xlabel("Delta i")
plt.ylabel("Delta i+1")
plt.title("Iterative map")
plt.ylim(0, 1.2)
plt.xlim(0, 1)

# plot_mean_response(t_diff_dict)


# plot_phase_response_curve(t_diff_dict, t_isi)


# plot_spiking(spike_trains_dict)

plt.show()
