module Utils
    export secondsToSamples, samplesToSeconds, ∫

    using FFTW
    function ∫(xs, ys)
        ∫ydx = 0
        for i = 1:(length(ys)-1)
            ∫ydx += (ys[i+1] + ys[i]) * (xs[i+1] - xs[i]) / 2
        end
        return ∫ydx
    end

    function spectrum(window)
        S = fft(window)
        S = S[1:Int(length(S)/2)]
        scale = 2 / length(window)
        return [sqrt(real(n)^2 + imag(n)^2) * scale for n in S]
    end

    function hannWindow(window)
        N = length(window)
        return [window[n] * 0.5 * (1 - cos(2π * n / N)) for n = 1:N]
    end

    function secondsToSamples(sec, Fs)
        return Int64(round(sec * Fs))
    end

    function samplesToSeconds(samples, Fs)
        return samples / Fs
    end

    function frequencyToSamples(freq, Fs; roundFunc = round)
        samples = Fs / freq
        return Int(roundFunc(samples))
    end

    function samplesToFrequency(samples, Fs)
        return Fs / samples
    end

    function getWindows(y, Wl; hop_size = Wl)
        num_steps = Int(ceil(length(y) / hop_size - 1))
        windows = [zeros(Wl) for i in 1:num_steps]
        for i in 1:num_steps
            x_start = (i - 1) * hop_size + 1
            copyto!(windows[i], y[x_start:min(x_start + Wl - 1, end)])
        end
        return windows
    end

    function binIdx(freq, bin_size; roundFunc::Function = round)
        return Int(roundFunc(freq / bin_size)) + 1
    end

    function idxToBin(idx, bin_size)
        return (idx - 1) * bin_size
    end

    function closestBin(freq, bins)
        i = 1
        while bins[i] < freq
            i += 1
        end
        if i > 1 && (abs(bins[i] - freq) > abs(bins[i-1] - freq))
            i -= 1
        end
        @assert i >= 1
        return i
    end

    function closestPowerOf2(n)
        i = 0
        while 2^i < n
            i += 1
        end

        if abs(2^i - n) > abs(2^(i - 1) - n)
            return 2^(i - 1)
        else
            return 2^i
        end
    end

    function calculateCents(f1, f2)
        high = max(f1, f2)
        low = min(f1, f2)
        cents = Int(floor(1200 * log(2, high / low)))
        return cents
    end

    function getNoteFromFreq(freq)
        if freq === nothing
            return nothing
        end
        noteNum = 12 * log(2, freq / 440) + 49
        trueNote = Int(floor(noteNum))
        error = calculateCents(getNote(trueNote), freq) / 100

        return trueNote + error
    end

    function getNoteName(noteNum)
        trueNote = Int(floor(noteNum))
        error = noteNum - trueNote

        octave = Int(floor((trueNote + 8) / 12))
        noteName = ((trueNote + 8) % 12) + 1
        if noteName < 1
            noteName += 12
        end
        noteName = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"][noteName]

        str = string(noteName, octave)
        if error != 0
            str = string(str, " + ", Int(round(error * 100)), "c")
        end
        return (str)
    end

    function getNote(noteNum::Integer)
        return 2^((noteNum - 49) // 12) * 440
    end

    function getRange(low_note, high_note)
        i = 0
        while getNote(i) < low_note
            i += 1
        end

        @assert getNote(i) < high_note

        j = i
        while getNote(j) < high_note
            j += 1
        end

        return getNote.(i:j)
    end

    function findpartialsort(v, k; opts...)
        inds = [partialsortperm(v, k; opts...)...]
        return v[[inds...]], inds
    end

    Base.findmax(v::AbstractArray, k::AbstractUnitRange; opts...) = findpartialsort(v, k; opts..., rev = true)
    Base.findmin(v::AbstractArray, k::AbstractUnitRange; opts...) = findpartialsort(v, k; opts...)
end
