class AudioState(object):
    def __init__(self):
        self.playtime = 0
        self.intensity = 5
        self.daytime = 12
        self.samples = {}
        self.lastPlayed = 0
        self.lastTriggered = 0


class AudioTrack(object):
    VERSION = 1

    def __init__(self, filePath):
        self.name = ''
        self.path = filePath
        self.samples = []
        self.state = AudioState()
        self.barTime = 500
        self.barTimer = 0
        self.status = SoundStatus.Stopped
        self.hasSolo = False

    def update(self, frameTime):
        if self.status is not SoundStatus.Playing:
            return
        self.barTimer += frameTime
        for sample in self.samples:
            sample.update(frameTime)

        if self.barTimer >= self.barTime:
            #self.state.intensity = world.getPlayer().intensity
            #self.state.daytime = world.timeCycle.getTime()
            Log.trace("Current playtime: {}".format(self.state.playtime))
            hadSampleStartPlaying = True
            isRecheck = False
            playlist = []
            while hadSampleStartPlaying:
                hadSampleStartPlaying = False
                for sample in self.samples:
                    if sample not in playlist and sample.check(self.state, isRecheck):
                        playlist.append(sample)
                        hadSampleStartPlaying = True
                isRecheck = True
            for sample in playlist:
                Log.trace("Playing sample {}".format(self.name))
                sample.play()
            self.barTimer = 0
            self.state.playtime += 1

    def play(self):
        if self.status is SoundStatus.Stopped:
            self.barTimer = self.barTime
        self.status = SoundStatus.Playing

    def pause(self):
        self.status = SoundStatus.Paused

    def stop(self):
        self.status = SoundStatus.Stopped
        self.state.playtime = 0
        for sample in self.samples:
            sample.stop()

    def setBar(self, time):
        self.barTime = time

    def addSample(self, sample):
        self.samples.append(sample)
        self.state.samples[sample.name] = sample

    def removeSample(self, sample):
        if sample in self.samples:
            del self.samples[self.samples.index(sample)]
        del self.state.samples[sample.name]

    def write(self):
        stream = DataStream()
        stream.writeUint32(self.VERSION)
        stream.writeString(self.name)
        stream.writeUint32(self.barTime)
        stream.writeUint32(len(self.samples))
        for sample in self.samples:
            sample.write(stream)
        File.save(self.path, stream)

    def read(self):
        stream = DataStream()
        File.load(self.path, stream)
        version = stream.readUint32()
        self.name = stream.readString()
        self.barTime = stream.readUint32()
        sampleCount = stream.readUint32()
        for x in xrange(sampleCount):
            sample = AudioSample()
            sample.read(stream)
            self.addSample(sample)

    def setSampleSolo(self, sampleName, solo):
        self.hasSolo = solo
        self.state.samples[sampleName].solo = solo
        if not self.hasSolo:
            for sample in self.samples:
                if sample.solo:
                    self.hasSolo = True
                    break
        for sample in self.samples:
            sample.noVolume = (self.hasSolo and not sample.solo) or sample.mute
            sample.music.setVolume(0 if sample.noVolume else sample.volume)


class AudioSample(object):
    def __init__(self):
        self.name = ''
        self.path = ''
        self.music = Music()
        #Number of bars passed since sample stated playing
        self.lastPlayed = 0
        #Number of bars passed since the status changed
        self.lastStatusChange = 0
        self.triggers = []
        self.playCount = 0
        self.playtime = Timer()
        self.status = SoundStatus.Stopped
        self.trigger = None

        self.duration = 0
        self.volume = 100
        self.solo = False
        self.mute = False
        self.noVolume = False

    def setStatus(self, status):
        if self.status is not status:
            self.lastStatusChange = 0
            if status is not SoundStatus.Paused:
                self.lastPlayed = 0
            self.status = status

    def update(self, frameTime):
        if self.status is SoundStatus.Playing:
            self.playtime.update(frameTime)
            if self.playtime.expired():
                self.stop()
            else:
                self.updateVolume()
        self.lastPlayed += 1
        self.lastStatusChange += 1

    def updateVolume(self):
        previous = self.volume
        if self.trigger.fadeIn > 0 and self.lastPlayed > 1:
            progress = float(self.duration - self.playtime.remaining()) / self.trigger.fadeIn
            self.volume = max(0, min(int(progress * 100), 100))
        if self.volume != previous and not self.noVolume:
            self.music.setVolume(self.volume)

    def check(self, state, isRecheck):
        #Check is only called upon bar increment
        Log.trace("Checking sample {} status={}".format(self.name, self.status))

        alreadyPlaying = self.status is SoundStatus.Playing
        if not alreadyPlaying and self.playCount > 0:
            self.playCount -= 1
            self.play()
            return True

        state.lastPlayed = self.lastPlayed
        for trigger in self.triggers:
            met = trigger.check(state, isRecheck)
            if met and (not alreadyPlaying or trigger.forceRestart):
                if alreadyPlaying and trigger.forceRestart:
                    self.music.stop()
                self.trigger = trigger
                self.playCount = trigger.plays - 1
                self.setStatus(SoundStatus.Playing)
        return not alreadyPlaying and self.status == SoundStatus.Playing

    def play(self):
        self.music.play()
        self.playtime.reset(self.duration - startTime)
        self.setStatus(SoundStatus.Playing)
        self.updateVolume()

    def pause(self):
        self.music.pause()
        self.setStatus(SoundStatus.Paused)

    def stop(self):
        self.music.stop()
        self.setStatus(SoundStatus.Stopped)
        self.trigger = None

    def addTrigger(self, trigger):
        self.triggers.append(trigger)

    def removeTrigger(self, trigger):
        if trigger in self.triggers:
            del self.triggers[self.triggers.index(trigger)]

    def setPath(self, path):
        self.path = path
        try:
            self.music.openFromFile(path)
            self.duration = self.music.getDuration().asMilliseconds()
            Log.trace("Opened music file {} that is {}ms".format(path, self.duration))
        except:
            Log.warn("Failed to read file: {}".format(self.path))
            raise

    def setMute(self, mute, trackHasSolo):
        self.mute = mute
        self.noVolume = (trackHasSolo and not self.solo) or mute
        self.music.setVolume(0 if self.noVolume else self.volume)

    def write(self, stream):
        stream.writeString(self.name)
        stream.writeString(self.path)
        stream.writeUint8(len(self.triggers))
        for trigger in self.triggers:
            trigger.write(stream)

    def read(self, stream):
        self.name = stream.readString()
        self.setPath(stream.readString())
        triggerCount = stream.readUint8()
        for x in xrange(triggerCount):
            trigger = AudioTrigger()
            trigger.read(stream)
            self.triggers.append(trigger)

class AudioTrigger(object):
    def __init__(self):
        self.conditions = []
        self.lastTriggered = 9999
        self.forceRestart = False
        self.plays = 1

        #Amount of time to fade in ms when playing sample with this trigger
        #Volume % goes from 0 to 100
        self.fadeIn = 0

    def check(self, state, isRecheck):
        satisfied = True
        state.lastTriggered = self.lastTriggered
        for condition in self.conditions:
            if not isRecheck:
                condition.exp = ""
            if (not isRecheck and not condition.check(state)) or (isRecheck and not condition.recheck(state)):
                satisfied = False
                break
        if satisfied:
            self.lastTriggered = 0
        else:
            self.lastTriggered += 1
        return satisfied

    def addCondition(self, condition):
        self.conditions.append(condition)

    def removeCondition(self, condition):
        if condition in self.conditions:
            del self.conditions[self.conditions.index(condition)]

    def write(self, stream):
        stream.writeUint32(self.fadeIn)
        stream.writeUint8(len(self.conditions))
        for condition in self.conditions:
            Log.trace("Saving condition {}".format(condition.name))
            stream.writeString(condition.name)
            condition.write(stream)


    def read(self, stream):
        self.fadeIn = stream.readUint32()
        conditionCount = stream.readUint8()
        for x in xrange(conditionCount):
            name = stream.readString()
            Log.trace("reading condition {}".format(name))
            condition = AudioCondition.create(name)
            condition.read(stream)
            self.conditions.append(condition)


#Audio Conditions

class ConditionValue(object):
    def __init__(self, value=[0, 1]):
        self.value = value

    def get(self):
        return self.value[0] if self.value[0] == self.value[1] else Random.get(self.value[0], self.value[1])

    def write(self, stream):
        stream.writeUint32(self.value[0])
        stream.writeUint32(self.value[1])

    def read(self, stream):
        self.value = []
        self.value.append(stream.readUint32())
        self.value.append(stream.readUint32())


class AudioCondition(object):
    conditions = {}

    @staticmethod
    def initialize():
        for condition in [IntensityCondition,
                DaytimeCondition,
                PlaytimeCondition,
                SampleStatusCondition,
                SampleStatusChangedCondition,
                LastPlayedCondition,
                LastTriggeredCondition,
                RandomCondition]:
            AudioCondition.conditions[condition.name] = condition

    @staticmethod
    def create(conditionName):
        return AudioCondition.conditions[conditionName]()


class IntensityCondition(AudioCondition):
    name = "Intensity"
    def __init__(self):
        self.exp = ""
        self.value = ConditionValue()
        self.compare = "<"

    def check(self, state):
        self.exp = "{} {} {}".format(state.intensity, self.compare, self.value.get())
        Log.trace("Condition {}: {}".format(self.name, self.exp))
        return eval(self.exp)

    def recheck(self, state):
        return eval(self.exp) if self.exp else self.check(state)

    def write(self, stream):
        self.value.write(stream)
        stream.writeString(self.compare)

    def read(self, stream):
        self.value.read(stream)
        self.compare = stream.readString()


class DaytimeCondition(AudioCondition):
    name = "Daytime"
    def __init__(self):
        self.exp = ""
        self.value = ConditionValue()
        self.compare = "<"

    def check(self, state):
        self.exp = "{} {} {}".format(state.daytime, self.compare, self.value.get())
        Log.trace("Condition {}: {}".format(self.name, self.exp))
        return eval(self.exp)

    def recheck(self, state):
        return eval(self.exp) if self.exp else self.check(state)

    def write(self, stream):
        self.value.write(stream)
        stream.writeString(self.compare)

    def read(self, stream):
        self.value.read(stream)
        self.compare = stream.readString()


class PlaytimeCondition(AudioCondition):
    name = "Playtime"
    def __init__(self):
        self.exp = ""
        self.value = ConditionValue()
        self.compare = "<"

    def check(self, state):
        self.exp = "{} {} {}".format(state.playtime, self.compare, self.value.get())
        Log.trace("Condition {}: {}".format(self.name, self.exp))
        return eval(self.exp)

    def recheck(self, state):
        return eval(self.exp) if self.exp else self.check(state)

    def write(self, stream):
        self.value.write(stream)
        stream.writeString(self.compare)

    def read(self, stream):
        self.value.read(stream)
        self.compare = stream.readString()


class SampleStatusCondition(AudioCondition):
    name = "Sample Status"
    def __init__(self):
        self.exp = ""
        self.sample = ""
        self.status = SoundStatus.Playing

    def check(self, state):
        sample = state.samples[self.sample]
        return sample and sample.status == self.status

    def recheck(self, state):
        return self.check(state)

    def write(self, stream):
        stream.writeString(self.sample)
        stream.writeUint8(self.status)

    def read(self, stream):
        self.sample = stream.readString()
        self.status = stream.readUint8()


class SampleStatusChangedCondition(AudioCondition):
    name = "Status Changed"
    def __init__(self):
        self.current = 0
        self.sample = ""
        self.value = ConditionValue()
        self.compare = "<"

    def check(self, state):
        self.current = self.value.get()
        return self.recheck(state)

    def recheck(self, state):
        sample = state.samples[self.sample]
        self.exp = "{} {} {}".format(sample.lastStatusChange, self.compare, self.current)
        Log.trace("Condition {}: {}".format(self.name, self.exp))
        return eval(self.exp)

    def write(self, stream):
        stream.writeString(self.sample)
        self.value.write(stream)
        stream.writeString(self.compare)

    def read(self, stream):
        self.sample = stream.readString()
        self.value.read(stream)
        self.compare = stream.readString()


class LastPlayedCondition(AudioCondition):
    name = "Last Played"
    def __init__(self):
        self.exp = ""
        self.value = ConditionValue()
        self.compare = "<"

    def check(self, state):
        self.exp = "{} {} {}".format(state.lastPlayed, self.compare, self.value.get())
        Log.trace("Condition {}: {}".format(self.name, self.exp))
        return eval(self.exp)

    def recheck(self, state):
        return eval(self.exp) if self.exp else self.check(state)

    def write(self, stream):
        self.value.write(stream)
        stream.writeString(self.compare)

    def read(self, stream):
        self.value.read(stream)
        self.compare = stream.readString()


class LastTriggeredCondition(AudioCondition):
    name = "Last Triggered"
    def __init__(self):
        self.exp = ""
        self.value = ConditionValue()
        self.compare = "<"

    def check(self, state):
        self.exp = "{} {} {}".format(state.lastTriggered, self.compare, self.value.get())
        Log.trace("Condition {}: {}".format(self.name, self.exp))
        return eval(self.exp)

    def recheck(self, state):
        return eval(self.exp) if self.exp else self.check(state)

    def write(self, stream):
        self.value.write(stream)
        stream.writeString(self.compare)

    def read(self, stream):
        self.value.read(stream)
        self.compare = stream.readString()


class RandomCondition(AudioCondition):
    name = "Randomness"
    def __init__(self):
        self.exp = ""
        self.value = ConditionValue([0, 0])
        self.compare = "<"
        self.other = ConditionValue([1, 1])

    def check(self, state):
        self.exp = "{} {} {}".format(self.value.get(), self.compare, self.other.get())
        Log.trace("Condition {}: {}".format(self.name, self.exp))
        return eval(self.exp)

    def recheck(self, state):
        return eval(self.exp) if self.exp else self.check(state)

    def write(self, stream):
        self.value.write(stream)
        stream.writeString(self.compare)
        self.other.write(stream)

    def read(self, stream):
        self.value.read(stream)
        self.compare = stream.readString()
        self.other = stream.readUint32()

#Initialize the audio conditions list
AudioCondition.initialize()
