from __future__ import print_function

import base64
import collections
import json
import os
import os.path
import pickle
import re
import time
import urllib
import platform
import subprocess
from itertools import cycle
from multiprocessing import Process, Pipe, set_start_method
import threading

import click
import cv2
import face_recognition.api as face_recognition
import imutils
import numpy as np
from imutils.video import VideoStream
from scipy.spatial import distance as dist

from gtts import gTTS
import speech_recognition as sr
import apiai

import uuid

import traceback

# import urllib3
# urllib3.disable_warnings()

WINDOW_WIDTH = 600
APIAI_CLIENT_ACCESS_TOKEN = "07cb2d829426432181b624476455f6e9"


# ------------------------------------------------------------------------------
#
# "Listen"
#
# ------------------------------------------------------------------------------

def processRequest(query,req): 
    if req is None:
        return {}
    result = req.get('result')
    if result is None:
        return {}
    print(result)
    parameters = result.get("parameters")

    action = result.get('action')

    if action is None:
        return {}
    else:
        print("APIAI ACTION = " + action + "!")

        if action == "input.unknown":
            # answer = ChatbotManager.callBot(query)
            answer = "Nao entendi o que vc falou, pode repetir?"
            return answer,action

        else:
            fulfillment = result.get("fulfillment")
            messages = fulfillment.get('messages')
            speech = messages[0].get('speech')
            if action == "input.project":
                print("AKI MUDAR O PROJETO OU RETORNAR O VALOR " + parameters.get('Project')[0])
            return speech , action
    return "Invalid! processRequest " , None


def createAnswer(query):
    ai = apiai.ApiAI(APIAI_CLIENT_ACCESS_TOKEN)
    request = ai.text_request()
    request.lang = 'pt-BR'  # optional, default value equal 'en'
    request.session_id = uuid.uuid4().hex
    request.query = query
    response = json.loads(request.getresponse().read().decode("utf-8"))
    answer,action_status = processRequest(query,response)
    return answer,action_status

def get_audio():
     # obtain audio from the microphone
    r = sr.Recognizer()
    with sr.Microphone() as source:
        r.adjust_for_ambient_noise(source)  # listen for 1 second to calibrate the energy threshold for ambient noise levels
        print("Say something!")
        audio = r.listen(source)

    # recognize speech using Google Speech Recognition
    try:
        # for testing purposes, we're just using the default API key
        # to use another API key, use `r.recognize_google(audio, key="GOOGLE_SPEECH_RECOGNITION_API_KEY")`
        # instead of `r.recognize_google(audio)`
        data = r.recognize_google(audio,language='pt-BR')
        return data       
    except sr.UnknownValueError:
        print("Google Speech Recognition could not understand audio")
        return None
    except sr.RequestError as e:
        print("Could not request results from Google Speech Recognition service; {0}".format(e))

def listen_thread(salute):
    print("ENTROU NA THREAD")
    p = Speaker.speak(salute)
    #p.wait()
    words = len(salute.split(' '))
    time.sleep(words * 27/60.0 - 0.75)
    print("ACABOU O WAIT")
    listen() 
    print("SAINDO DA THREAD")

def listen():
    data = get_audio()
    if data is None :
        Speaker.speak("Vou entender isso como um sim ! ")
        return
    print("you said : " + data)
    answer,act_status = createAnswer(data)
    if answer :
        if act_status == "input.unknown":
            salute = data + " ? " + answer
            threading.Thread(target=listen_thread, args=(salute,)).start()
        elif act_status == "input.no":
            threading.Thread(target=listen_thread, args=(answer,)).start()
            # mudar projeto para project
        else:
            Speaker.speak(answer)
              

# ------------------------------------------------------------------------------
class Speaker:
    @staticmethod
    def speak(phrase):
        if platform.platform().startswith('Darwin'):
            return subprocess.Popen(["say", "-v", "Luciana", phrase])


# ------------------------------------------------------------------------------
class VideoPlayer:
    def __init__(self, filename):
        self.frame = cycle(VideoPlayer._load_video(filename))

    def next_frame(self):
        return next(self.frame)

    @staticmethod
    def _load_video(filename):
        frames = []
        cap = cv2.VideoCapture(filename)
        while True:
            ret, frame = cap.read()
            if not ret:
                break
            frames.append(imutils.resize(frame, width=WINDOW_WIDTH))
        return frames


# ------------------------------------------------------------------------------
class FPS:
    def __init__(self):
        self.frame_times = []
        self.start_t = time.time()

    def draw(self, frame):
        cv2.putText(frame, self.get(), (5, frame.shape[0] - 10), cv2.FONT_HERSHEY_DUPLEX, 1.0, (255, 255, 255), 1)

    def get(self):
        return "%.1f fps" % (self._fps(),)

    def _fps(self):
        end_t = time.time()
        time_taken = end_t - self.start_t
        self.start_t = end_t
        self.frame_times.append(time_taken)
        self.frame_times = self.frame_times[-20:]
        return len(self.frame_times) / sum(self.frame_times)


# ------------------------------------------------------------------------------
class Person:
    def __init__(self):
        self.id = None
        self.name = None
        self.project = None
        self.imgFile = None
        self.shape = None
        self.in_the_frame = False
        self.in_the_house = False
        self.time_in_the_house = None
        self.state_history = collections.deque(maxlen=40)

    @classmethod
    def fromfile(cls, filename):
        img = cv2.imread(filename)

        self = cls()
        self.name = os.path.splitext(os.path.basename(filename))[0]
        self.encoding = face_recognition.face_encodings(img)[0]
        self.imgFile = cv2.resize(img, (100, 100))
        self.shape = self.imgFile.shape
        return self

    @classmethod
    def fromjson(cls, obj):
        nparr = np.fromstring(base64.b64decode(obj['picture']['image']), np.uint8)

        self = cls()
        self.id = obj['id']
        self.name = obj['name']
        self.project = obj['project']
        self.encoding = pickle.loads(eval(obj['picture']['features']))
        self.imgFile = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
        self.shape = self.imgFile.shape
        return self

    def draw(self, frame, x, y):
        w, h = self.shape[0], self.shape[1]
        frame[y:y + w, x:x + h] = self.imgFile

    def update_house_state(self):
        self.in_the_house = not (self.in_the_house)
        if self.in_the_house:
            salute = "Olá {0}! Seja bem vindo ao CyberLabs! ".format(self.name)
            if self.project:
                salute += "Você está trabalhando no projeto {0}. Correto?".format(self.project)
            print("escutando")
            # threading.Thread(target=listen_thread, args=(salute,)).start()
            listen_thread(salute)
            #print("parando de escutar")
        else:
            byebye = "Até logo {0}! check aut feito X horas projeto {1}".format(self.name,self.project)
            Speaker.speak(byebye)          

        if self.id:
            activity_url = 'http://localhost:3000/activities.json'
            params = {
                'activity': {
                    'activitable_id': self.id,
                    'activitable_type': 'Visitor',
                    'kind': ('in' if self.in_the_house else 'out')
                }
            }
            params = json.dumps(params).encode('utf8')
            req = urllib.request.Request(activity_url, data=params,
                                         headers={'content-type': 'application/json'})
            response = urllib.request.urlopen(req)
            obj = json.loads(response.read().decode('utf-8'))
            return obj

    def set_state(self, state):
        # print(self.name, time.ctime(), "len =", len(self.state_history), "trues =", self.state_history.count(True), "falses =", self.state_history.count(False))
        self.state_history.append(state)
        if self.state_history.count(False) == self.state_history.maxlen:
            self.in_the_frame = False
        elif self.state_history.count(True) >= self.state_history.maxlen / 4:
            if not self.in_the_frame:
                self.update_house_state()
            self.in_the_frame = True

    def left_the_scene(self):
        self.set_state(False)


# ------------------------------------------------------------------------------
class FaceRecognizer:
    def __init__(self, people):
        self.people = people
        self.known_face_encodings = [person.encoding for person in people]

    def recognize(self, frame, gray):
        locations = face_recognition.face_locations(frame)

        names = []

        if len(locations) > 0:
            # Find all the face encodings in the frame of video
            face_encodings = face_recognition.face_encodings(frame, locations)

            # Loop through each face in this frame of video
            for face_encoding in face_encodings:
                # See if the face is a match for the known face(s)
                matches = face_recognition.compare_faces(self.known_face_encodings, face_encoding, tolerance=0.6)
                for is_match, person in zip(matches, self.people):
                    person.set_state(is_match)
                    if is_match:
                        names.append(person.name)
                if True not in matches:
                    names.append("unknown_person")
        else:
            for person in self.people:
                person.left_the_scene()

        return locations, names


# ------------------------------------------------------------------------------
#
# UTILS
#
# ------------------------------------------------------------------------------
def scan_known_people(known_people_folder):
    imgs = [os.path.join(known_people_folder, f) for f in
            os.listdir(known_people_folder) if re.match(r'.*\.(jpg|jpeg|png)', f, flags=re.I)]
    return [Person.fromfile(file) for file in imgs]


def download_known_people():
    people_url = 'http://localhost:3000/visitors.json'
    response = urllib.request.urlopen(people_url)
    str_response = response.read().decode('utf-8')
    obj = json.loads(str_response)
    return [Person.fromjson(o) for o in obj]


# ------------------------------------------------------------------------------
#
# DRAWING
#
# ------------------------------------------------------------------------------
def draw_names_and_rectangles(frame, face_locations, face_names):
    red = (0, 0, 255)
    green = (0, 255, 0)
    colors = [green, red]

    # Display the results
    for (top, right, bottom, left), name in zip(face_locations, face_names):
        # Scale back up face locations since the frame we detected in was scaled to 1/4 size
        (top, right, bottom, left) = (top << 2, right << 2, bottom << 2, left << 2)

        # Draw a box around the face
        cv2.rectangle(frame, (left, top), (right, bottom), colors[name == "unknown_person"], 2)
        cv2.putText(frame, name, (left + 6, top - 6), cv2.FONT_HERSHEY_DUPLEX, 1.0, (255, 255, 255), 1)


def draw_online_people_picture(frame, people):
    x_offset = y_offset = 10
    for person in people:
        person.draw(frame, x_offset, y_offset)
        x_offset += person.shape[0] + 5


# ------------------------------------------------------------------------------
#
# "Threads"
#
# ------------------------------------------------------------------------------
def face_finder_thread(people, command_pipe, webcam_pipe):
    face_recognizer = FaceRecognizer(people)

    while True:
        command_pipe.send(['current_frame', ''])
        frame = webcam_pipe.recv()
        small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        gray = cv2.equalizeHist(gray)

        face_locations, face_names = face_recognizer.recognize(small_frame, gray)

        if face_locations:
            command_pipe.send(['feed', 'live'])
        else:
            no_one_known_in_frame = not any(person.in_the_frame for person in people)
            if no_one_known_in_frame:
                command_pipe.send(['feed', 'idle'])

        command_pipe.send(['faces', [face_locations, face_names,
                                     [person for person in people if person.in_the_frame]]])


def feed_thread(video_stream_pipe, face_finder_webcam_pipe, command_pipe):
    print('Live streaming...')
    idle_video_player = VideoPlayer('./images/eyes2.mp4')

    rectangles = names = people = []
    current_feed = 'idle'
    video_capture = VideoStream(src=0, usePiCamera=False).start()

    time.sleep(2.0)
    while True:
        try:
            frame = imutils.resize(video_capture.read(), width=WINDOW_WIDTH)

            if command_pipe.poll():
                cmd, args = command_pipe.recv()
                if cmd == 'feed':
                    current_feed = args
                elif cmd == 'faces':
                    rectangles, names, people = args
                elif cmd == 'current_frame':
                    face_finder_webcam_pipe.send(frame)

            if current_feed == 'live':
                draw_names_and_rectangles(frame, rectangles, names)
                draw_online_people_picture(frame, people)
            else:  # current_feed == 'idle'
                rectangles = names = people = []
                frame = idle_video_player.next_frame()
            video_stream_pipe.send(frame)
        except :
            var = traceback.format_exc()
            print(" Problem with  :"+ var)
           


# ------------------------------------------------------------------------------
#
# MAIN
#
# ------------------------------------------------------------------------------
@click.command()
@click.argument('known_people_folder', nargs=-1, type=click.Path(exists=True))
def main(known_people_folder):
    video_stream_parent, video_stream_child = Pipe()
    face_recognizer_parent, face_recognizer_child = Pipe()
    webcam_parent, webcam_child = Pipe()

    print('Loading people...')
    people = (scan_known_people(known_people_folder[0])
              if known_people_folder
              else download_known_people())
    print('done.')

    feed_process = Process(target=feed_thread,
                           args=(video_stream_child, webcam_parent, face_recognizer_child))
    feed_process.daemon = True
    feed_process.start()

    face_finder_process = Process(target=face_finder_thread,
                                  args=(people, face_recognizer_parent, webcam_child))
    face_finder_process.daemon = True
    face_finder_process.start()

    fps = FPS()
    cv2.namedWindow('Video', cv2.WINDOW_OPENGL)
    # cv2.setWindowProperty('Video', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
    while True:
        img = video_stream_parent.recv()
        fps.draw(img)
        cv2.imshow('Video', img)

        # Hit 'q' on the keyboard to quit!
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    cv2.destroyAllWindows()


# ------------------------------------------------------------------------------
if __name__ == "__main__":
    set_start_method('spawn')  # for multiprocess cv2 to work
    main()
