# You need to install pyaudio to run this example
# pip install pyaudio

# When using a microphone, the AudioSource `input` parameter would be
# initialised as a queue. The pyaudio stream would be continuosly adding
# recordings to the queue, and the websocket client would be sending the
# recordings to the speech to text service

import pyaudio
import time
import sys
import sys
import glob
import serial
import random
import MarkovPoemGenerator as mpg
import speech_recognition as sr
from ibm_watson import SpeechToTextV1
from ibm_watson.websocket import RecognizeCallback, AudioSource
from threading import Thread
from ibm_cloud_sdk_core.authenticators import IAMAuthenticator


try:
    from Queue import Queue, Full
except ImportError:
    from queue import Queue, Full

IBM_KEY = open("../keys/ibm-credentials.txt", "r").read().strip()
ARD_PORT = "COM5"
QUESTIONS = open("../testCorpus/mood.txt", "r").read().strip()

def write_qtitle():
    questions = random.choice(QUESTIONS.split('?'))
    print(questions)
    return questions.strip().lower()


def serial_ports():
    if sys.platform.startswith('win'):
        ports = ['COM%s' % (i + 1) for i in range(256)]
    elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
        # this excludes your current terminal "/dev/tty"
        ports = glob.glob('/dev/tty[A-Za-z]*')
    elif sys.platform.startswith('darwin'):
        ports = glob.glob('/dev/tty.*')
    else:
        raise EnvironmentError('Unsupported platform')

    result = []
    for port in ports:
        try:
            s = serial.Serial(port)
            s.close()
            result.append(port)
        except (OSError, serial.SerialException):
            pass
    return result

def writePrinter(string, ser):
    if ser:
        try:
            if not ser.isOpen():
                try:
                    ser.open()
                except Exception as e:
                    print("Error Reopening Port:", e)
            # Check max string length
            num_char = 0
            for i in string:
                ser.write(i.encode(encoding='ascii', error='ignore'))
                num_char = num_char + 1
                time.sleep(.4)
                if i == '\n':
                    num_char = 0
                if num_char == 72:
                    ser.write('\n'.encode(encoding='ascii', error='ignore'))
                    time.sleep(.4)
                    ser.write(' '.encode(encoding='ascii', error='ignore'))
                    time.sleep(.4)
                    ser.write(' '.encode(encoding='ascii', error='ignore'))
                    time.sleep(.4)
                    ser.write(' '.encode(encoding='ascii', error='ignore'))
                    time.sleep(.4)
                    num_char = 0
            ser.write('\n')
        except Exception as e:
            print(e)

###############################################
#### Initalize queue, classes to store the recordings ##
###############################################

CHUNK = 2048
# Note: It will discard if the websocket client can't consumme fast enough
# So, increase the max size as per your choice
BUF_MAX_SIZE = CHUNK * 10
# Buffer to store audio
q = Queue(maxsize=int(round(BUF_MAX_SIZE / CHUNK)))

# Create an instance of AudioSource
audio_source = AudioSource(q, True, True)

mGen = mpg.MarkovPoemGenerator()

try:
    ser = serial.Serial(ARD_PORT)
except Exception as e:
    print('Error: Input port not available')

if serial_ports():
    ser = serial.Serial(serial_ports()[0])
else:
    ser = None
    print('Error: No available ports')

# Initialize Serial

# define callback for the speech to text service
class MyRecognizeCallback(RecognizeCallback):
    def __init__(self):
        RecognizeCallback.__init__(self)

    def on_transcription(self, transcript):
        print(transcript[0]['transcript'])
        mGen.add_to_corpus([x for x in transcript[0]['transcript'].split() if x != '%HESITATION'])
        if len(mGen.corpus_noStop) > 20:
            mGen.make_markov_haiku()
            mGen.print_poem()
            if ser:
                writePrinter(mGen.poem, ser)
                mGen.poem = ""

    def on_connected(self):
        print('Connection was successful')

    def on_error(self, error):
        print('Error received: {}'.format(error))

    def on_inactivity_timeout(self, error):
        print('Inactivity timeout: {}'.format(error))

    def on_listening(self):
        print('Service is listening')
        writePrinter(write_qtitle(), ser)

    def on_hypothesis(self, hypothesis):
        # Interim result
        pass

    def on_data(self, data):
        writePrinter('. ', ser)
        pass

    def on_close(self):
        print("Connection closed")
        
        # TODO: Add Closed
        # TODO: Add Close option from Arduino

###############################################
####### Prepare Speech to Text Service ########
###############################################

# initialize speech to text service
authenticator = IAMAuthenticator(IBM_KEY)
speech_to_text = SpeechToTextV1(authenticator=authenticator)

# this function will initiate the recognize service and pass in the AudioSource
def recognize_using_websocket(*args):
    mcallback = MyRecognizeCallback()
    speech_to_text.recognize_using_websocket(audio=audio_source,
                                             content_type='audio/l16; rate=44100',
                                             recognize_callback=mcallback,
                                             interim_results=False)

###############################################
#### Prepare the for recording using Pyaudio ##
###############################################

# Variables for recording the speech
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100

# define callback for pyaudio to store the recording in queue
def pyaudio_callback(in_data, frame_count, time_info, status):
    try:
        q.put(in_data)
    except Full:
        pass # discard
    return (None, pyaudio.paContinue)

# instantiate pyaudio
audio = pyaudio.PyAudio()

# open stream using callback
stream = audio.open(
    format=FORMAT,
    channels=CHANNELS,
    rate=RATE,
    input=True,
    frames_per_buffer=CHUNK,
    stream_callback=pyaudio_callback,
    start=False
)

#########################################################################
#### Start the recording and start service to recognize the stream ######
#########################################################################

print("Enter CTRL+C to end recording...")
stream.start_stream()

try:
    recognize_thread = Thread(target=recognize_using_websocket, args=())
    recognize_thread.start()

    while True:
        pass

except KeyboardInterrupt:
    # stop recording
    stream.stop_stream()
    stream.close()
    audio.terminate()
    audio_source.completed_recording()
