import os
import time
import threading

import wave
import pyaudio
import noisereduce
from scipy.io import wavfile
import webrtcvad

from config import cfg


CHUNK = 480  # frame count in buffer. webrtcvad just support 10ms, 20ms, 30ms chunk, so use 30ms( 16000 * 30 / 1000 )
FORMAT = pyaudio.paInt16  # bitsize of sampling
CHANNELS = 1  # voice channel
RATE = 16000  # bitrate of sampling
MAXRECORDTIME =10 # the max record time of one record action
MINSPEECHCOUNT =10 # the min speech chunk count in one sentence
MAXEMPTYCOUNT =60 # the max empty chunk count in one sentence

RECORDTIME =3 # time duration of one record buffer


_recordthreadsinglelock = False

class _recordthread(threading.Thread):
    def __init__(self):
        self.running = False
        self.directory = ""
        self.prename = ""
        self.bufsize = 0
        self.looptime = 0.0
        self.recordtime = 0.0
        
        threading.Thread.__init__(self)
        
    def run(self):
        global _recordthreadsinglelock
        if _recordthreadsinglelock:
            return
        _recordthreadsinglelock = True
        self.running = True
            
        bufcount = 1
        while self.running :
            start_time = time.time()
            
            filename = "%s%d.wav" % (self.prename, bufcount)
            filepath = os.path.join(self.directory, filename)
            hr = Recorder.RecordAudios(filepath, self.recordtime)
            if hr < 0:
                break
            
            end_time = time.time()
            jobduration = end_time - start_time
            (quo, rem) = divmod(jobduration, self.looptime)
            bufcount = (bufcount + quo + 1) % self.bufsize
            if bufcount == 0 :
                bufcount = self.bufsize
            waittime = self.looptime - rem
            if waittime > 0 :
                time.sleep(waittime)
                
        self.running = False      # for break
        _recordthreadsinglelock = False

    
    def IsRunning(self):
        return _recordthreadsinglelock

    
    def StopThread(self):
        self.running = False
        while(_recordthreadsinglelock):
            time.sleep(1)


class __recorder():
    def __init__(self):
        self.pa = None
        self.stream = None
        self.rthread = None
        self.isrecording = False
        self.micindex = cfg.getint("device","micindex")
        self.vad = webrtcvad.Vad(1)
        
    
    def __del__(self):
        if self.stream is not None:
            if self.rec.isOpened():
                self.CloseCamera()

    
    def IsOpened(self):
        if self.stream is None:
            return False
            
        return self.stream.is_active()

    
    def IsRecording(self):
        if self.isrecording:
            return True
        if self.rthread is not None:
            if self.rthread.IsRunning():
                return True
        return False
        
        
    def OpenRecorder(self):
        if self.IsOpened():
            return True
            
        self.pa = pyaudio.PyAudio()
        self.stream = self.pa.open(format=FORMAT,
                    channels=CHANNELS,
                    rate=RATE,
                    input=True,
                    frames_per_buffer=CHUNK,
                    input_device_index=self.micindex)
        if self.IsOpened():
            return True
        else:
            print("Zhi Error: Cann't open mic")
            return False

    
    def CloseRecorder(self):
        if self.IsOpened():
            if self.rthread is not None:
                self.rthread.StopThread()
                self.rthread = None
                
            self.stream.stop_stream()
            self.stream.close()
            self.pa.terminate()
            self.stream = None
            self.pa = None
        

    '''
    humanonly: only record hunman vioce
    filepath: save record file path
    maxtime: the max record time of one sentence (second)
    '''
    def RecordAudio(self, humanonly, filepath, maxtime=MAXRECORDTIME):
        if not self.IsOpened():
            return -1

        if self.IsRecording():
            return -2
        self.isrecording = True
        
        # read data into buffer, and check if it is speech and if this sentence is finish
        start_sentence = False
        continuous_count = 0
        continuous_buf = []
        empty_count = 0
        record_buf = []
        for _ in range(0, int(RATE / CHUNK * maxtime)):
            data = self.stream.read(CHUNK, exception_on_overflow = False)
            
            # if just record human voice, just "is_speach" data can be record. those not human vioce, will be watch as empty data.
            if humanonly :
                if not self.vad.is_speech(data, RATE):
                    empty_count += 1
                    continuous_count = 0
                    if start_sentence:
                        if empty_count > MAXEMPTYCOUNT :        # if empty count in sentence is more than MAXEMPTYCOUNT, that means sentence finish
                            break
                        else:
                            record_buf.append(data)
                    continue
            
            # record data into record buffer
            continuous_count += 1
            if continuous_count == MINSPEECHCOUNT:     # if continous count equire to min speech chunk count, means data in continuous buffer available, move date into record buffer
                start_sentence = True
                record_buf.extend(continuous_buf)
                continuous_buf = []
                empty_count = 0
            elif continuous_count > MINSPEECHCOUNT:    # if coutinous count is more then min speech chunk count, write data into record buffer directly
                record_buf.append(data)
            else:                                         # if coutinous count is less then min speech chunk count, just write data into continuous buffer
                continuous_buf.append(data)
                empty_count += 1                         # empty_count still add 1, because is still un-abailable
                

        if len(record_buf) > 0 :
            # write to file       
            with wave.open(filepath, 'wb') as wf:
                wf.setnchannels(CHANNELS)
                wf.setsampwidth(self.pa.get_sample_size(FORMAT))
                wf.setframerate(RATE)
                wf.writeframes(b''.join(record_buf))
                
            # reduce noise, and rewrite to file
            _, data = wavfile.read(filepath)
            reduced_noise = noisereduce.reduce_noise(y=data, sr=RATE)
            wavfile.write(filepath, RATE, reduced_noise)

        self.isrecording = False
        if len(record_buf) == 0 :
            return -3
        else :
            return 0

    
    '''
    filepath: save record file path
    time: the duration time if record (second)
    '''
    def RecordAudios(self, filepath, time=RECORDTIME):
        if not self.IsOpened():
            return -1
        
        # read data into buffer
        record_buf = []
        for _ in range(0, int(RATE / CHUNK * time)):
            data = self.stream.read(CHUNK, exception_on_overflow = False)
            record_buf.append(data)
            
        # write to file       
        with wave.open(filepath, 'wb') as wf:
            wf.setnchannels(CHANNELS)
            wf.setsampwidth(self.pa.get_sample_size(FORMAT))
            wf.setframerate(RATE)
            wf.writeframes(b''.join(record_buf))
            
        # reduce noise, and rewrite to file
        _, data = wavfile.read(filepath)
        reduced_noise = noisereduce.reduce_noise(y=data, sr=RATE)
        wavfile.write(filepath, RATE, reduced_noise)
        
        return 0

    
    def RecordAudiosStart(self, directory, prename, bufsize, looptime, recordtime):
        if not self.IsOpened():
            return -1

        if self.IsRecording():
            return -2
            
        if self.rthread is None :
            self.rthread=_recordthread()
            self.rthread.directory = directory
            self.rthread.prename = prename
            self.rthread.bufsize = bufsize
            self.rthread.looptime = looptime
            self.rthread.recordtime = recordtime
            self.rthread.start()
            return 0
        else:
            print("Zhi Error: Record is bock when start it")
            return -3

    
    def RecordAudiosStop(self):
        if self.rthread is None:
            print("Zhi Error: Record is not inited when stop it")
            return -1
            
        if not self.rthread.IsRunning():
            print("Zhi Error: Record is not started when stop it")
            return -2
            
        self.rthread.StopThread()
        self.rthread = None
        return 0

Recorder = __recorder()
