
import pyrelacs.DataLoader as dl
import numpy as np
import matplotlib.pyplot as plt
from IPython import embed
import os
import introduction.old_helper_functions as ohf
from thunderfish.eventdetection import detect_peaks


SAVEPATH = ""


def get_savepath():
    global SAVEPATH
    return SAVEPATH


def set_savepath(new_path):
    global SAVEPATH
    SAVEPATH = new_path


def main():
    for folder in ohf.get_subfolder_paths("data/"):
        filepath = folder + "/basespikes1.dat"
        set_savepath("figures/" + folder.split('/')[1] + "/")

        print("Folder:", folder)

        if not os.path.exists(get_savepath()):
            os.makedirs(get_savepath())

        spiketimes = []

        ran = False
        for metadata, key, data in dl.iload(filepath):
            ran = True
            spikes = data[:, 0]
            spiketimes.append(spikes)  # save for calculation of vector strength
            metadata = metadata[0]
            #print(metadata)
            # print('firing frequency1:', metadata['firing frequency1'])
            # print(mean_firing_rate(spikes))

            # print('Coefficient of Variation (CV):', metadata['CV1'])
            # print(calculate_coefficient_of_variation(spikes))

        if not ran:
            print("------------ DIDN'T RUN")

        isi_histogram(spiketimes)

        times, eods = ohf.get_traces(folder, 2, 'BaselineActivity')
        times, v1s = ohf.get_traces(folder, 1, 'BaselineActivity')

        vs = calculate_vector_strength(times, eods, spiketimes, v1s)

        # print("Calculated vector strength:", vs)


def mean_firing_rate(spiketimes):
    # mean firing rate (number of spikes per time)
    return len(spiketimes)/spiketimes[-1]*1000


def calculate_coefficient_of_variation(spiketimes):
    # CV (stddev of ISI divided by mean ISI (np.diff(spiketimes))
    isi = np.diff(spiketimes)
    std = np.std(isi)
    mean = np.mean(isi)

    return std/mean


def isi_histogram(spiketimes):
    # ISI histogram (play around with binsize! < 1ms)

    isi = []
    for spike_list in spiketimes:
        isi.extend(np.diff(spike_list))
    maximum = max(isi)
    bins = np.arange(0, maximum*1.01, 0.1)

    plt.title('Phase locking of ISI without stimulus')
    plt.xlabel('ISI in ms')
    plt.ylabel('Count')
    plt.hist(isi, bins=bins)
    plt.savefig(get_savepath() + 'phase_locking_without_stimulus.png')
    plt.close()


def calculate_vector_strength(times, eods, spiketimes, v1s):
    # Vectorstaerke (use EOD frequency from header (metadata)) VS > 0.8
    # dl.iload_traces(repro='BaselineActivity')

    relative_spike_times = []
    eod_durations = []

    if len(times) == 0:
        print("-----LENGTH OF TIMES = 0")

    for recording in range(len(times)):

        rel_spikes, eod_durs = eods_around_spikes(times[recording], eods[recording], spiketimes[recording])
        relative_spike_times.extend(rel_spikes)
        eod_durations.extend(eod_durs)

        vs = __vector_strength__(np.array(rel_spikes), eod_durs)
        phases = calculate_phases(rel_spikes, eod_durs)
        plot_polar(phases, "test_phase_locking_" + str(recording) + "_with_vs:" + str(round(vs, 3)) + ".png")

        print("VS of recording", recording, ":", vs)

        plot_phaselocking_testfigures(times[recording], eods[recording], spiketimes[recording], v1s[recording])

    return __vector_strength__(np.array(relative_spike_times), eod_durations)


def eods_around_spikes(time, eod, spiketimes):
    eod_durations = []
    relative_spike_times = []

    for spike in spiketimes:
        index = spike * 20  # time in s given timestamp of spike in ms - recorded at 20kHz -> timestamp/1000*20000 = idx

        if index != np.round(index):
            print("INDEX NOT AN INTEGER in eods_around_spikes! index:", index)
            continue
        index = int(index)

        start_time, end_time = search_eod_start_and_end_times(time, eod, index)

        eod_durations.append(end_time-start_time)
        relative_spike_times.append(spike/1000 - start_time)

    return relative_spike_times, eod_durations


def search_eod_start_and_end_times(time, eod, index):
    # TODO might break if a spike is in the cut off first or last eod!

    # search start_time:
    previous = index
    working_idx = index-1
    while True:
        if eod[working_idx] < 0 < eod[previous]:
            first_value = eod[working_idx]
            second_value = eod[previous]

            dif = second_value - first_value
            part = np.abs(first_value/dif)

            time_dif = np.abs(time[previous] - time[working_idx])
            start_time = time[working_idx] + time_dif*part

            break

        previous = working_idx
        working_idx -= 1

    # search end_time
    previous = index
    working_idx = index + 1
    while True:
        if eod[previous] < 0 < eod[working_idx]:
            first_value = eod[previous]
            second_value = eod[working_idx]

            dif = second_value - first_value
            part = np.abs(first_value / dif)

            time_dif = np.abs(time[previous] - time[working_idx])
            end_time = time[working_idx] + time_dif * part

            break

        previous = working_idx
        working_idx += 1

    return start_time, end_time


def search_closest_index(array, value, start=0, end=-1):
    # searches the array to find the closest value in the array to the given value and returns its index.
    # expects sorted array!
    # start hast to be smaller than end

    if end == -1:
        end = len(array)-1

    while True:
        if end-start <= 1:
            return end if np.abs(array[end]-value) < np.abs(array[start]-value) else start

        middle = int(np.floor((end-start)/2)+start)
        if array[middle] == value:
            return middle
        elif array[middle] > value:
            end = middle
            continue
        else:
            start = middle
            continue


def __vector_strength__(relative_spike_times, eod_durations):
    # adapted from Ramona

    n = len(relative_spike_times)
    if n == 0:
        return 0

    phase_times = (relative_spike_times / eod_durations) * 2 * np.pi
    vs = np.sqrt((1 / n * sum(np.cos(phase_times))) ** 2 + (1 / n * sum(np.sin(phase_times))) ** 2)

    return vs


def calculate_phases(relative_spike_times, eod_durations):
    phase_times = np.zeros(len(relative_spike_times))

    for i in range(len(relative_spike_times)):
        phase_times[i] = (relative_spike_times[i] / eod_durations[i]) * 2 * np.pi

    return phase_times


def plot_polar(phases, name=""):
    fig = plt.figure()
    ax = fig.add_subplot(111, polar=True)
    # r = np.arange(0, 1, 0.001)
    # theta = 2 * 2 * np.pi * r
    # line, = ax.plot(theta, r, color='#ee8d18', lw=3)
    bins = np.arange(0, np.pi*2, 0.05)
    ax.hist(phases, bins=bins)
    if name == "":
        plt.show()
    else:
        plt.savefig(get_savepath() + name)
        plt.close()


def plot_phaselocking_testfigures(time, eod, spiketimes, v1):
    eod_start_times = []
    eod_end_times = []

    for spike in spiketimes:
        index = spike * 20  # time in s given timestamp of spike in ms - recorded at 20kHz -> timestamp/1000*20000 = idx

        if index != np.round(index):
            print("INDEX NOT AN INTEGER in eods_around_spikes! index:", index)
            continue
        index = int(index)

        start_time, end_time = search_eod_start_and_end_times(time, eod, index)

        eod_start_times.append(start_time)
        eod_end_times.append(end_time)

    cutoff_in_sec = 2
    sampling = 20000
    max_idx = cutoff_in_sec*sampling
    spikes_part = [x/1000 for x in spiketimes if x/1000 < cutoff_in_sec]
    count_spikes = len(spikes_part)
    print(spiketimes)
    print(len(spikes_part))

    x_axis = time[0:max_idx]
    plt.plot(spikes_part, np.ones(len(spikes_part))*-20, 'o')
    plt.plot(x_axis, v1[0:max_idx])
    plt.plot(eod_start_times[: count_spikes], np.zeros(count_spikes), 'o')
    plt.plot(eod_end_times[: count_spikes], np.zeros(count_spikes), 'o')

    plt.show()
    plt.close()


if __name__ == '__main__':
    main()
