import time

import matplotlib.pyplot as plt
import numpy as np
import sounddevice as sd

from smnp.note.pitch import Tuning
from smnp.type.model import Type

FS = 44100


def pause(value, bpm):
    time.sleep(60 * 4 / value / bpm)


def plot(wave):
    X = np.arange(len(wave))
    plt.plot(X, wave)
    plt.show()


def play(wave):
    sd.play(wave)
    time.sleep(len(wave) / FS)


def compilePolyphony(notes, config):
    tuning = Tuning(config.tuning)
    compiledLines = [1 / len(notes) * compileNotes(line, config, tuning) for line in notes]
    return sum(adjustSize(compiledLines))


def adjustSize(compiledLines):
    maxSize = max(len(line) for line in compiledLines)

    return [np.concatenate([line, np.zeros(maxSize - len(line))]) for line in compiledLines]


def compileNotes(notes, config, tuning):
    dispatcher = {
        Type.NOTE: lambda note, overtones: sineForNote(note.value, config, tuning),
        Type.INTEGER: lambda note, overtones: silenceForPause(note.value, config)
    }

    return np.concatenate([dispatcher[note.type](note, config) for note in notes])


def sineForNote(note, config, tuning):
    frequency = note.toFrequency(tuning)
    duration = 60 * 4 / note.duration / config.bpm
    duration *= 1.5 if note.dot else 1
    return sound(frequency, duration, config)


def sound(frequency, duration, config):
    return attack(decay(sum(overtone * sine((i+1) * frequency, duration) for i, overtone in enumerate(config.overtones) if overtone > 0), config), config)


def decay(wave, config):
    magnitude = np.exp(-config.decay/len(wave) * np.arange(len(wave)))

    return magnitude * wave


def attack(wave, config):
    magnitude = -np.exp(-config.attack / len(wave) * np.arange(len(wave)))+1 \
    if config.attack > 0 \
    else np.ones(len(wave))

    return magnitude * wave


def sine(frequency, duration):
    return (np.sin(2 * np.pi * np.arange(FS * duration) * frequency / FS)).astype(np.float32)


def silenceForPause(value, config):
    duration = 60 * 4 / value / config.bpm
    return np.zeros(int(FS * duration))
