# -*- coding: utf-8 -*-
import time
from threading import Lock
from collections import deque

import opuslib

from .constants import *


class SoundQueue:
    """
    Per user storage of received audio frames
    Takes care of the decoding of the received audio
    """
    def __init__(self, mumble_object):
        self.mumble_object = mumble_object
        
        self.queue = deque()
        self.start_sequence = None
        self.start_time = None

        self.receive_sound = True
        
        self.lock = Lock()
        
        # to be sure, create every supported decoders for all users
        # sometime, clients still use a codec for a while after server request another...
        self.decoders = {
                    PYMUMBLE_AUDIO_TYPE_OPUS: opuslib.Decoder(PYMUMBLE_SAMPLERATE, 1)
        }

    def set_receive_sound(self, value):
        """Define if received sounds must be kept or discarded in this specific queue (user)"""
        if value:
            self.receive_sound = True
        else:
            self.receive_sound = False

    def add(self, audio, sequence, type, target):
        """Add a new audio frame to the queue, after decoding"""
        if not self.receive_sound:
            return None
        
        self.lock.acquire()
        
        try:
            pcm = self.decoders[type].decode(audio, PYMUMBLE_READ_BUFFER_SIZE)

            if not self.start_sequence or sequence <= self.start_sequence:
                # New sequence started
                self.start_time = time.time()
                self.start_sequence = sequence
                calculated_time = self.start_time
            else:
                # calculating position in current sequence
                calculated_time = self.start_time + (sequence - self.start_sequence) * PYMUMBLE_SEQUENCE_DURATION

            newsound = SoundChunk(pcm, sequence, len(pcm), calculated_time, type, target)

            if not self.mumble_object.callbacks.get_callback(PYMUMBLE_CLBK_SOUNDRECEIVED):
                self.queue.appendleft(newsound)

                if len(self.queue) > 1 and self.queue[0].time < self.queue[1].time:
                    # sort the audio chunk if it came out of order
                    cpt = 0
                    while cpt < len(self.queue) - 1 and self.queue[cpt].time < self.queue[cpt+1].time:
                        tmp = self.queue[cpt+1]
                        self.queue[cpt+1] = self.queue[cpt]
                        self.queue[cpt] = tmp

            self.lock.release()
            return newsound
        except KeyError:
            self.lock.release()
            self.mumble_object.Log.error("Codec not supported (audio packet type {0})".format(type))
        except Exception as e:
            self.lock.release()
            self.mumble_object.Log.error("error while decoding audio. sequence:{seq}, type:{type}. {error}".format(seq=sequence, type=type, error=str(e)))

    def is_sound(self):
        """Boolean to check if there is a sound frame in the queue"""
        if len(self.queue) > 0:
            return True
        else:
            return False
        
    def get_sound(self, duration=None):
        """Return the first sound of the queue and discard it"""
        self.lock.acquire()

        if len(self.queue) > 0:
            if duration is None or self.first_sound().duration <= duration:
                result = self.queue.pop() 
            else:
                result = self.first_sound().extract_sound(duration)
        else:
            result = None

        self.lock.release()
        return result
    
    def first_sound(self):
        """Return the first sound of the queue, but keep it"""
        if len(self.queue) > 0:
            return self.queue[-1]
        else:
            return None
        

class SoundChunk:
    """
    Object that contains the actual audio frame, in PCM format"""    
    def __init__(self, pcm, sequence, size, calculated_time, type, target, timestamp=time.time()):
        self.timestamp = timestamp  # measured time of arrival of the sound 
        self.time = calculated_time  # calculated time of arrival of the sound (based on sequence)
        self.pcm = pcm  # audio data
        self.sequence = sequence  # sequence of the packet
        self.size = size  # size
        self.duration = float(size) / 2 / PYMUMBLE_SAMPLERATE  # duration in sec
        self.type = type  # type of the audio (codec)
        self.target = target  # target of the audio
        
    def extract_sound(self, duration):
        """Extract part of the chunk, leaving a valid chunk for the remaining part"""
        size = int(duration*2*PYMUMBLE_SAMPLERATE)
        result = SoundChunk(
                        self.pcm[:size],
                        self.sequence,
                        size,
                        self.time,
                        self.type,
                        self.target,
                        self.timestamp
                        )
        
        self.pcm = self.pcm[size:]
        self.duration -= duration
        self.time += duration
        self.size -= size
        
        return result
