from smnp.error.runtime import RuntimeException
from smnp.function.model import Function, CombinedFunction
from smnp.function.signature import varargSignature
from smnp.module.synth.lib.wave import compilePolyphony

from smnp.type.model import Type
from smnp.type.signature.matcher.list import listOf
from smnp.type.signature.matcher.type import ofTypes, ofType


DEFAULT_BPM = 120
DEFAULT_OVERTONES = [0.4, 0.3, 0.1, 0.1, 0.1]
DEFAULT_DECAY = 4
DEFAULT_ATTACK = 100
DEFAULT_TUNING = 440


def getBpm(config):
    key = Type.string("bpm")
    if key in config.value:
        bpm = config.value[key]
        if bpm.type != Type.INTEGER or bpm.value <= 0:
            raise RuntimeException("The 'bpm' property must be positive integer", None)

        return bpm.value

    return DEFAULT_BPM


def getOvertones(config):
    key = Type.string("overtones")
    if key in config.value:
        overtones = config.value[key]
        rawOvertones = [overtone.value for overtone in overtones.value]
        if overtones.type != Type.LIST or not all(overtone.type in [Type.FLOAT, Type.INTEGER] for overtone in overtones.value):
            raise RuntimeException("The 'overtones' property must be list of floats", None)

        if len(rawOvertones) < 1:
            raise RuntimeException("The 'overtones' property must contain one overtone at least", None)

        if any(overtone < 0 for overtone in rawOvertones):
            raise RuntimeException("The 'overtones' property mustn't contain negative values", None)

        if sum(rawOvertones) > 1.0:
            raise RuntimeException("The 'overtones' property must contain overtones which sum is not greater than 1.0", None)

        return rawOvertones

    return DEFAULT_OVERTONES


def getDecay(config):
    key = Type.string("decay")
    if key in config.value:
        decay = config.value[key]
        if not decay.type in [Type.INTEGER, Type.FLOAT] or decay.value < 0:
            raise RuntimeException("The 'decay' property must be non-negative integer or float", None)

        return decay.value

    return DEFAULT_DECAY


def getAttack(config):
    key = Type.string("attack")
    if key in config.value:
        attack = config.value[key]
        if not attack.type in [Type.INTEGER, Type.FLOAT] or attack.value < 0:
            raise RuntimeException("The 'attack' property must be non-negative integer or float", None)

        return attack.value

    return DEFAULT_ATTACK


def getTuning(config):
    key = Type.string("tuning")
    if key in config.value:
        tuning = config.value[key]
        if not tuning.type in [Type.INTEGER, Type.FLOAT] or tuning.value < 0:
            raise RuntimeException("The 'tuning' property must be non-negative integer or float", None)

        return tuning.value

    return DEFAULT_TUNING


class Config:
    def __init__(self, bpm, overtones, decay, attack, tuning):
        self.bpm = bpm
        self.overtones = overtones
        self.decay = decay
        self.attack = attack
        self.tuning = tuning

    @staticmethod
    def default():
        return Config(DEFAULT_BPM, DEFAULT_OVERTONES, DEFAULT_DECAY, DEFAULT_ATTACK, DEFAULT_TUNING)


_signature1 = varargSignature(listOf(Type.NOTE, Type.INTEGER))
def _function1(env, notes):
    return Type.list([Type.float(float(m)) for m in __function1(notes)])


def __function1(notes):
    return compilePolyphony([note.value for note in notes], Config.default())


_signature2 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER))
def _function2(env, notes):
    return Type.list([Type.float(float(m)) for m in __function2(notes)])


def __function2(notes):
    return compilePolyphony([ notes ], Config.default())


_signature3 = varargSignature(listOf(Type.NOTE, Type.INTEGER), ofType(Type.MAP))
def _function3(env, config, notes):
    return Type.list([ Type.float(float(m)) for m in __function3(config, notes) ])


def __function3(config, notes):
    rawNotes = [note.value for note in notes]

    bpm = getBpm(config)
    overtones = getOvertones(config)
    decay = getDecay(config)
    attack = getAttack(config)
    tuning = getTuning(config)

    return compilePolyphony(rawNotes, Config(bpm, overtones, decay, attack, tuning))


_signature4 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER), ofType(Type.MAP))
def _function4(env, config, notes):
    return Type.list([ Type.float(float(m)) for m in __function4(config, notes) ])


def __function4(config, notes):
    bpm = getBpm(config)
    overtones = getOvertones(config)
    decay = getDecay(config)
    attack = getAttack(config)
    tuning = getTuning(config)

    return compilePolyphony([ notes ], Config(bpm, overtones, decay, attack, tuning))


function = CombinedFunction(
    'wave',
    Function(_signature1, _function1),
    Function(_signature2, _function2),
    Function(_signature3, _function3),
    Function(_signature4, _function4),
)