import essentia.standard as es
import librosa.display
import madmom.features.beats as mdm
import madmom.evaluation as mde
import madmom.audio as mda
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
import scipy.signal as sps
import aubio as ab
import os
import mir_eval as m
from PIL import ImageTk, Image

#from scipy.io import wavfile
# import wavio


## FUNKCE BEAT TRACKINGU ## 

def beattrack_librosa(audio_in, sr, start_bpm=120, onset_envelope=None, hop_length=512, tightness=100.0, trim=True,
                      bpm=None, units='time'):
    """ Funkce beat trackingu z modulu librosa
    :param audio_in: vstupni data z audiofile
    :param sr: sample rate (vzorkovaci kmitocet)
    :param start_bpm: vychozi hodnota pro odhad tempa, default 120
    :param onset_envelope: onset strength envelope, kterou jsi sam nekde splasila
    :param hop_length: pocet vzorku mezi po sobe jdoucimi hodnotami onset envelope
    :param tightness: jak moc pevne se beaty distribuuji okolo tempa
    :param trim: trim leading/trailing beats with weak onsets
    :param bpm: tempo, pokud ho vis a nechces pocitat
    :param units: format ('frames' (= default), 'samples', 'time')
    :return: tempo a casy uderu v sekundach
    """
    tempo, beat_times = librosa.beat.beat_track(audio_in, sr=sr, onset_envelope=onset_envelope, hop_length=hop_length,
                                                start_bpm=start_bpm, tightness=tightness, trim=trim, bpm=bpm, units=units)
    # print(type(beat_times))
    # print(np.shape(beat_times))
    return tempo, beat_times


def beattrack_aubio(audio_in, win_s=1024):
    """ Funkce beat trackingu z modulu aubio

    """
    beattimes = []
    hop_s = win_s // 2  #pocet vzroku, ktere v jedne iteraci nacita
    samplerate = 0
    a_source = ab.source(audio_in, samplerate, hop_s)
    samplerate = a_source.samplerate        #do promenne samplerate nacte zjistenou vzorkovaci freq
    a_tempo = ab.tempo("default", win_s, hop_s, samplerate=samplerate)
    total_frames = 0
    while True:
        samples, read = a_source()
        is_beat = a_tempo(samples)
        if is_beat:
            this_beat = a_tempo.get_last_s()
            beattimes.append(this_beat)
        total_frames += read
        if read < hop_s:
            break
    return beattimes


def beattrack_essentia(audio_in, max=208, min=40):
    """ Funkce beat trackingu z modulu essentia
    :param audio_in: vstupni data z audiofile
    :return: casy uderu v sekundach, tempo (bpm)
    """
    audio = es.MonoLoader(filename=audio_in)()
    rhythm_extractor = es.RhythmExtractor2013(method="multifeature", minTempo=min, maxTempo=max)
    bpm, beats, beats_confidence, _, beats_intervals = rhythm_extractor(audio)
    return beats, bpm


def beattrack_madmom(audio_in, processor='D', fps=100, min_bpm=55.0, max_bpm=215.0, num_tempi=None, transition_lambda=100, observation_lambda=16,
                     correct=True, threshold=0, online=False, interval_sigma=0.18, use_factors=False, num_intervals=5, factors=np.array([0.5, 0.67, 1., 1.5, 2.]),
                     method='acf', look_ahead=3):
    """ Funkce beat trackingu z modulu madmom
    :param audio_in: vstupni data z audiofile
    :param fps: ramce za sekundu, aby se ukazovaly beaty v sekundach a ne ve framech
    :param processor: ktery procesor chceme vyzit pro vytvoreni aktivacni funkce
    :param min_bpm: minimalni tempo
    :param max_bpm: maximalni tempo
    :param num_tempi: pocet temp, pro ktera se to bude modelovat. Pokud vyplneno,
    omezi se pocet temp a uzije logaritmicky spacing, jinak linearni
    :param transition_lambda: lambda pro exponencialni rozlozeni zmen tempa
    :param observation_lambda: Split one beat period into observation_lambda parts, the first representing beat states and the remaining non-beat states.
    :param correct: opravit beaty, tedy priradit je ke spickam aktivacni funkce
    :param threshold: prah observaci pred Viterbiho dekodovanim
    :param online: pouzit dopredny algoritmus namisto Viterbiho
    :param interval_sigma: povolena odchylka od dominantniho intervalu na beat
    :param use_factors: pouzit dominantni interval nasobeny zlomky namisto intervalu predikovanych predikci tempa
    :param factors: zlomky dominantniho intervalu, ktere chceme pouzit
    :param num_intervals: maximalni mnozstvi intervalu, ktere chceme pouzit
    :param method: metoda pro urceni tempa / acf (autocorrelation), comb (comb filter), dbn (dynamic Bayesian network)
    :return: casy uderu v sekundach

    """
   # elif processor == 'CRF':
    #     proc = mdm.CRFBeatDetectionProcessor(fps=fps, interval_sigma=interval_sigma, use_factors=use_factors, num_intervals=num_intervals, factors=factors)
    if processor == 'T':
        proc = mdm.BeatTrackingProcessor(fps=fps, look_ahead=look_ahead, min_bpm=min_bpm, max_bpm=max_bpm, method=method)
    elif processor == 'D':
        proc = mdm.BeatDetectionProcessor(fps=fps)
    elif processor == 'DBN':
        proc = mdm.DBNBeatTrackingProcessor(fps=fps, min_bpm=min_bpm, max_bpm=max_bpm, num_tempi=num_tempi,
                                            transition_lambda=transition_lambda, observation_lambda=observation_lambda,
                                            correct=correct, threshold=threshold, online=online)

    act = mdm.RNNBeatProcessor(fps=fps)(audio_in)
    beat_times = proc(act)
    #if just_det:
    #    beat_times = mdm.detect_beats(act, 10)

    #beat_times2 = mdm.detect_beats(act, 10)/100
    # beat_times = np.asarray(beat_times)  # zmena z tuple na array
    # beat_times = np.transpose(beat_times)
    # print(type(beat_times))
    # print(np.shape(beat_times))
    return beat_times, #beat_times2

############################################################################

#def zapis_souboru(seznam_jmen_souboru, nazev_souboru_ukladaneho):
#    """ Funkce zapise dany seznam string do souboru 'nazev_souboru_ukladaneho'.txt"""
#    with open(nazev_souboru_ukladaneho, 'w') as f:
#       f.write("%s\n" % 'Tyto soubory byly testovany: \n')
#        for item in seznam_jmen_souboru:
#           f.write("%s\n" % item)


def graf_modul_cas(y, bttimes, name, lib):
    # funkce pro vykresleni grafu zavislosti urovne signalu na case s detekovanymi dobami
    plt.figure(figsize=(12, 5.5))
    librosa.display.waveplot(y, alpha=0.6)
    plt.vlines(bttimes, -1, 1, color='r')
    plt.ylim(-1, 1)
    jmeno = ''.join((name, ', ', lib))
    plt.title(jmeno, fontsize=26)
    plt.ylabel('$\it{Ln}$ [-]', fontsize=18)
    plt.xticks(fontsize=16)
    plt.xlabel('$\it{t}$ [s]', fontsize=18)
    plt.yticks(fontsize=16)
    plt.savefig('modulcas.png', format='png')
    #plt.show(block=True)


#def graf_melovskyspect(y, sr, bttimes):
#    librosa.feature.melspectrogram(y=y, sr=sr)
#    plt.figure(figsize=(14, 5))
#    librosa.display.specshow(librosa.power_to_db(y, ref=np.max), y_axis='mel', fmax=8000, x_axis='time')
#    plt.colorbar(format='%+2.0f dB')
#    plt.vlines(bttimes, -1, 1, color='r')
#    plt.title('Mel spectrogram')
#    plt.tight_layout()
#    plt.show(block=True)


#def graf_melspect(y, sr, bttimes, n_fft=2048, hop_length=512, n_mels=138):
#    sig = mda.signal.Signal(y, num_channels=1)
#    signal = sig.__array__()
#    signalDat = [float(item) for item in signal]
#    signalData = np.array(signalDat)
#    plt.figure(figsize=(12, 5.5))
#    s = librosa.feature.melspectrogram(y=signalData, power=2.0, sr=sr, n_fft=n_fft, hop_length=hop_length, n_mels=n_mels)
#    librosa.display.specshow(librosa.power_to_db(s, ref=np.max), x_axis='time', y_axis='mel')
#    plt.colorbar(format='%+2.0f dB')
#    plt.title('Mel spectrogram')
#    plt.vlines(bttimes, -1, 1, color='r')
#    plt.savefig('melspecgr.png', format='png')
#    plt.show()

def spektrogram_w_lines(file, bttimes, name, lib):
    # funkce pro vykresleni spektrogamu s detekovanymi dobami

    # plt.subplot(211)
    # plt.title('Spectrogram of a wav file with piano music')
    # plt.plot(signalData)
    # plt.xlabel('Sample')
    # plt.ylabel('Amplitude')
    # plt.subplot(212)

    #signalData, samplingFrequency = mda.load_wave_file(file, num_channels=1)
    sig = mda.signal.Signal(file, num_channels=1)
    signalData = sig.__array__()
    samplingFrequency = sig.sample_rate
    plt.figure(figsize=(12, 5))
    #signalData = [(i[0]+i[1])/2 for i in signalData]
    plt.specgram(signalData, Fs=samplingFrequency)
    plt.ylim(20, 20000)
    plt.vlines(bttimes, 20, 20000, color='r')

    jmeno = ''.join((name, ', ', lib))
    c = plt.colorbar()
    c.set_label('$\it{L}$ [dB]', fontsize=18)
    c.ax.tick_params(labelsize=16)
    plt.title(jmeno, fontsize=26)
    plt.xlabel('$\it{t}$ [s]', fontsize=18)
    plt.xticks(fontsize=16)
    plt.ylabel('$\it{f}$ [Hz]', fontsize=18)
    plt.yticks(fontsize=16)
    plt.savefig('specgr.png', format='png')


#def resample (x, old_sr):
#    new_sr = 44100
#    samples = round(len(x)*float(new_sr)/old_sr)
#    new_x = sps.resample(x, samples)
#    return new_x, new_sr


def ulozto (kam, name, format, BT):
    # funkce pro ukladani souboru s casy dob
    if not os.path.exists(kam):
        os.makedirs(kam)
    name = name.split('.')
    filename = '/'.join((kam, name[0]))
    file = ''.join((filename, format))  # vytvoreni nazvu vysledneho souboru
    np.savetxt(file, BT, fmt='%.4f')  # vytvoreni souboru

#def graf_eval(y, bttimesDet, bttimesAn, name):
#    plt.figure(figsize=(14, 5))
#    librosa.display.waveplot(y, alpha=0.6)
#    plt.vlines(bttimesDet, -1, 1, color='r')
#    plt.vlines(bttimesAn, -1, 1, color='g')
#    plt.ylim(-1, 1)
#    plt.title(name)
#    plt.show(block=True)

#def evaluate(detected, annotated):
#    c = mde.beats.find_closest_matches(detected, annotated)
#    r = mde.beats.calc_relative_errors(detected, annotated)
#    return c, r

#def vse(det, ann, th):
    #score = m.beat.f_measure(ann, det, th)
    #return score

def vseOnset(det, ann, win=0.05):
    # funkce pro vypocet F-score, Recall a Precision
    score = m.onset.f_measure(ann, det, window=win)
    #score = [i * 100 for i in score]
    return score

def cemgil(det, ann, sig=0.04):
    # funkce pro vypocet Cemgil score
    score = m.beat.cemgil(ann, det, cemgil_sigma=sig)
    #score = [i * 100 for i in score]
    return score

def goto(det, ann, th=0.35, mu=0.2, sig=0.2):
    # funkce pro vypocet Goto score
    score = m.beat.goto(ann, det, goto_mu=mu, goto_threshold=th, goto_sigma=sig)
    return score

def pscore(det, ann, th=0.2):
    # funkce pro vypocet P-score
    score = m.beat.p_score(ann, det, p_score_threshold=th)
    #score = score * 100
    return score

def continuity(det, ann, cpht=0.175, cpt=0.175):
    # funkce pro vypocet skore kontinuity
    score = m.beat.continuity(det, ann, continuity_phase_threshold=cpht, continuity_period_threshold=cpt)
    return score

def bargraf(values, labels, type):
    # funkce vykreslujici sloupcovy graf
    popisky = []
    for word in labels:
	# vytvoreni popisku - z prvnich pismen nazvu knihoven, ev. prvnich 2-3 pismen
        char = word[0]
        if char == 'M' and 'D' in word:
            char = char + 'D'
        if char == 'M' and 'T' in word:
            char = char + 'T'
        if char == 'D':
            char = 'DBN'
        popisky.append(char)
    fig, ax = plt.subplots(figsize=(9, 5))
    y_pos = popisky
    ax.barh(y_pos, values, color=['red', 'green', 'blue', 'yellow','orange', 'grey'], align='center')
    ax.set_yticks(y_pos)
    ax.set_yticklabels(popisky, fontsize=12)
    ax.invert_yaxis()  # labels read top-to-bottom
    ax.set_xlabel(type + ' [%]', fontsize=14)
    plt.setp(ax.get_xticklabels(), fontsize=14)
    plt.xlim([0,100])
    ax.set_title(type + ' pro jednotlive systemy', fontsize = 16)
    plt.savefig('results/'+ type+'.png', format='png') # ulozeni grafu jako .png obrazku
    #plt.show()


def eval_subplot(means, libs, names):
    # vytvoreni sady grafu
    for i in range(len(means)):
        bargraf(means[i], libs, names[i])

def loadpic(file):
    # funkce pro nacteni obrazku
    pict = Image.open('results/'+file)
    pictres = pict.resize((450, 250))
    pic = ImageTk.PhotoImage(pictres)
    return pic
