# -*- coding: utf-8 -*-

from time import time
import struct
import threading

from constants import *
import pycelt
import pyopus
from tools import VarInt

class SoundOutput:
    """
    Class managing the sounds that must be sent to the server (best sent in a multiple of audio_per_packet samples)
    The buffering is the responsability of the caller, any partial sound will be sent without delay
    """
    def __init__(self, mumble_object, audio_per_packet, bandwidth):
        """
        audio_per_packet=packet audio duration in sec
        bandwidth=maximum total outgoing bandwidth
        """
        self.mumble_object = mumble_object
        
        self.Log = self.mumble_object.Log
        
        self.pcm = ""
        self.lock = threading.Lock()
        
        self.codec = None  # codec currently requested by the server
        self.encoder = None  # codec instance currently used to encode
        self.encoder_framesize = None  # size of an audio frame for the current codec (OPUS=audio_per_packet, CELT=0.01s)
        
        self.set_audio_per_packet(audio_per_packet)
        self.set_bandwidth(bandwidth)

        self.codec_type = None  # codec type number to be used in audio packets
        self.target = 0  # target is not implemented yet, so always 0
        
        self.sequence_start_time = 0  # time of sequence 1
        self.sequence_last_time = 0  # time of the last emitted packet
        self.sequence = 0  # current sequence
        
    def send_audio(self):
        """send the available audio to the server, taking care of the timing"""
        if not self.encoder or len(self.pcm) == 0:  # no codec configured or no audio sent
            return()
        
        samples = int(self.encoder_framesize * PYMUMBLE_SAMPLERATE * 2)  # number of samples in an encoder frame
        
        while len(self.pcm) > 0 and self.sequence_last_time + self.audio_per_packet <= time():  # audio to send and time to send it (since last packet)
            current_time = time()
            if self.sequence_last_time + PYMUMBLE_SEQUENCE_RESET_INTERVAL <= current_time:  # waited enough, resetting sequence to 0
                self.sequence = 0
                self.sequence_start_time = current_time
                self.sequence_last_time = current_time
            elif self.sequence_last_time + ( self.audio_per_packet * 2 ) <= current_time:  # give some slack (2*audio_per_frame) before interrupting a continuous sequence
                # calculating sequence after a pause
                self.sequence = int((current_time - self.sequence_start_time) / PYMUMBLE_SEQUENCE_DURATION)
                self.sequence_last_time = self.sequence_start_time + ( self.sequence * PYMUMBLE_SEQUENCE_DURATION )
            else:  # continuous sound
                self.sequence += int(self.audio_per_packet / PYMUMBLE_SEQUENCE_DURATION)
                self.sequence_last_time = self.sequence_start_time + ( self.sequence * PYMUMBLE_SEQUENCE_DURATION )
                
            payload = ""  # content of the whole packet, without tcptunnel header
            audio_encoded = 0  # audio time already in the packet
            
            while len(self.pcm) > 0 and audio_encoded < self.audio_per_packet:  # more audio to be sent and packet not full
                self.lock.acquire()
                to_encode = self.pcm[:samples]  # remove a frame from the input buffer
                self.pcm = self.pcm[samples:]
                self.lock.release()
                
                encoded = self.encoder.encode(to_encode)
                         
                audio_encoded += self.encoder_framesize
                
                # create the audio frame header
                if self.codec_type == PYMUMBLE_AUDIO_TYPE_OPUS:
                    frameheader = VarInt(len(encoded)).encode()
                else:
                    frameheader = len(encoded)
                    if audio_encoded < self.audio_per_packet and len(self.pcm) > 0:  # if not last frame for the packet, set the terminator bit
                        frameheader += ( 1 << 7 )
                    frameheader = struct.pack('!B', frameheader)
                
                payload += frameheader + encoded  # add the frame to the packet
            
            header = self.codec_type << 5  # encapsulate in audio packet
            target = 0
            sequence = VarInt(self.sequence).encode()
            
            udppacket = struct.pack('!B', header | target) + sequence + payload
            
            self.Log.debug("audio packet to send: sequence:{sequence}, type:{type}, length:{len}".format(
                                    sequence=self.sequence,
                                    type=self.codec_type,
                                    len=len(udppacket)
                                    ))
            
            tcppacket = struct.pack("!HL", PYMUMBLE_MSG_TYPES_UDPTUNNEL, len(udppacket)) + udppacket  # encapsulate in tcp tunnel
            
            while len(tcppacket)>0:
                sent=self.mumble_object.control_socket.send(tcppacket)
                if sent < 0:
                    raise socket.error("Server socket error")
                tcppacket=tcppacket[sent:]
        
            
    def get_audio_per_packet(self):
        """return the configured length of a audio packet (in ms)"""
        return(self.audio_per_packet)
    
    def set_audio_per_packet(self, audio_per_packet):
        """set the length of an audio packet (in ms)"""
        self.audio_per_packet = audio_per_packet
        self.create_encoder()
        
    def get_bandwidth(self):
        """get the configured bandwidth for the audio output"""
        return(self.bandwidth)
    
    def set_bandwidth(self, bandwidth):
        """set the bandwidth for the audio output"""
        self.bandwidth = bandwidth
        self._set_bandwidth()
        
    def _set_bandwidth(self):
        """do the calculation of the overhead and configure the actual bitrate for the codec"""
        if self.encoder:
            overhead_per_packet = 20  #IP header in bytes
            overhead_per_packet += ( 3 * int(self.audio_per_packet / self.encoder_framesize) )  # overhead per frame
            if self.mumble_object.udp_active:
                overhead_per_packet += 12  #UDP header
            else:
                overhead_per_packet += 20  #TCP header
                overhead_per_packet += 6  #TCPTunnel encapsulation
                
            overhead_per_second = int(overhead_per_packet*8 / self.audio_per_packet)  # in bits 
            
            self.Log.debug("Bandwidth is {bandwidth}, downgrading to {bitrate} due to the protocol overhead".format(bandwidth=self.bandwidth, bitrate = self.bandwidth-overhead_per_second))
            
            self.encoder.set_bitrate(self.bandwidth-overhead_per_second)
        
    def add_sound(self, pcm):
        """add sound to be sent (in PCM mono 16 bits signed format)"""
        if len(pcm) % 2 != 0:  #check that the data is align on 16 bits
            raise InvalidSoundDataError("pcm data must be mono 16 bits")

        self.lock.acquire()
        self.pcm += pcm
        self.lock.release()
        
    def get_buffer_size(self):
        """return the size of the unsent buffer in sec"""
        return(len(self.pcm)/2/PYMUMBLE_SAMPLERATE)
        
    def set_default_codec(self, codecversion):
        """Set the default codec to be used to send packets"""
        self.codec = codecversion
        self.create_encoder()
        
    def create_encoder(self):
        """create the encoder instance, and set related constants"""
        if not self.codec:
            return()
        
        if self.codec.opus:
            self.encoder = pyopus.OpusEncoder(PYMUMBLE_SAMPLERATE, 1)
            self.encoder.set_vbr(False)
            self.encoder_framesize = self.audio_per_packet
            self.codec_type = PYMUMBLE_AUDIO_TYPE_OPUS
        elif self.codec.prefer_alpha:
            if self.codec.alpha not in pycelt.SUPPORTED_BITSTREAMS:
                raise CodecNotSupportedError("CELT bitstream %i not supported" % codecversion.alpha)
            self.encoder = pycelt.CeltEncoder(PYMUMBLE_SAMPLERATE, 1, pycelt.SUPPORTED_BITSTREAMS[self.codec.alpha])
            self.encoder_framesize = PYMUMBLE_SEQUENCE_DURATION
            self.codec_type = PYMUMBLE_AUDIO_TYPE_CELT_ALPHA
        else:
            if self.codec.beta not in pycelt.SUPPORTED_BITSTREAMS:
                raise CodecNotSupportedError("CELT bitstream %i not supported" % codecversion.beta)
            self.encoder = pycelt.CeltEncoder(PYMUMBLE_SAMPLERATE, 1, pycelt.SUPPORTED_BITSTREAMS[self.codec.beta])
            self.encoder_framesize = PYMUMBLE_SEQUENCE_DURATION 
            self.codec_type = PYMUMBLE_AUDIO_TYPE_CELT_BETA

        self._set_bandwidth()