# 服务端版本2，接收客户端发来的图像并返回识别结果
# -*- coding=utf-8 -*-
import socket
import threading
import sys
import os
import struct
#import dlib
import csv
import numpy as np
#import cv2
import time


class GlobalVar:
    ip = ''
    port = 8000
    # 候选人名单
    candidate = []
    # 候选人特征向量
    descriptors = []

    name_namelist = ""

    # 候选人脸文件夹
    faces_folder_path = "D:/JetBrains/FaceRecognation/picture/id/"

    mainpage_file = "mainpage.jpg"  # 主页图片

    detector = []
    sp = []
    facerec = []
    descriptors = []


class ServerThread(threading.Thread):

    def __init__(self):
        self._running = True  # 定义线程状态变量
        super().__init__()

    def terminate(self):
        self._running = False

    def run(self) -> None:
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            s.bind((GlobalVar.ip, GlobalVar.port))
            s.listen(10)
            print("Wait for Connection.....................")
        except socket.error as msg:
            print(msg)
            sys.exit(1)

        while self._running:
            try:
                sock, address = s.accept()  # 等待客户端连接
                #deal_image(sock, address)
            except socket.error:
                print("连接失败")


# 接收并处理图片
def deal_image(sock, address):
    global name_namelist
    print("Accept connection from {0}".format(address))  # 查看发送端的ip和端口
    st1 = time.time()
    while True:
        fileinfo_size = struct.calcsize('128sq')  # 申请相同大小的空间存放发送过来的文件名与文件大小信息
        # print('fileinfo_size is', fileinfo_size)
        buf = sock.recv(fileinfo_size)  # 接收图片名
        # 判断是否接收到文件头信息
        if buf:  # 如果接收到了数据就开始接下来的操作
            filename, filesize = struct.unpack('128sq', buf)  # 按照客户端打包的格式进行解包，得到图片的名字和图片大小
            # print('filename :', filename.decode(), 'filesize :', filesize)
            fn = filename.strip(b'\00')
            fn = fn.decode()
            print('file new name is {0}, filesize is {1}'.format(str(fn), filesize))

            new_filename = os.path.join(str('./'), str('new_') + fn)

            # 在服务器端新建图片名(可以不用新建的，直接用原来的也行，只要客户端和服务器不是同一个系统或接收到的图片和原图片不在一个文件夹下)
            recvd_size = 0
            fp = open(new_filename, 'wb')  # 二进制打开文件
            print('start receiving...')

            while not recvd_size == filesize:  # 如果收到的直接总数不等于这个文件的直接总数，那么就继续接受数据
                if filesize - recvd_size > 1024:  # 这个不是最后一次
                    try:
                        data = sock.recv(1024)  # 每次从客户端接受1024个字节
                        recvd_size += len(data)  # 每次都记录好收到的字节数，然后叠加上去
                    except socket.error as msg:
                        print(msg)

                else:  # 这个最后一次数据如果比1024少的话那么这一次读完就OK了，而且这次结束后循环也就结束了
                    try:
                        data = sock.recv(filesize - recvd_size)
                        recvd_size = filesize
                    except socket.error as msg:
                        print(msg)
                #    print('data is', data)  # 输出每一次收到的数据
                fp.write(data)  # 写入图片数据，因为每次都是读取到1024个字节，所以每一次读取都需要把得到的字节进行拼凑
            fp.close()
            print('end receive...')

            analysize(new_filename)

            try:
                sock.send(GlobalVar.name_namelist.encode('utf-8'))
                print("Name is sent back already: " + GlobalVar.name_namelist)
            except socket.error as msg:
                print(msg)

        st2 = time.time()

        if st2 - st1 > 2:
            break

    sock.close()


def analysize(new_filename):
    str1 = str(new_filename).replace("b'./", "")
    str2 = str1.replace("'", "")

    features_cap_arr = []

    image = cv2.imread(str2)
    # cv2.imshow("Server", image)
    cv2.waitKey(1)
    try:
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    except:
        return

    faces2 = GlobalVar.detector(gray, 0)

    if len(faces2) == 0: return  # 相片中不存在人脸，直接返回

    # 获取当前捕获到的图像的人脸特征，存储到 features_cap_arr
    shape = GlobalVar.sp(image, faces2[0])
    features_cap_arr.append(GlobalVar.facerec.compute_face_descriptor(image, shape))

    # 先默认不认识，是 unknown
    GlobalVar.name_namelist = "Unknown person"

    # 对于某张人脸，遍历所有存储的人脸特征
    # for every faces detected, compare the faces in the database
    e_distance_list = []
    for i in range(len(GlobalVar.descriptors)):
        # 如果 person_X 数据不为空
        if str(GlobalVar.descriptors[i][0]) != '0.0':
            # print("with person", str(i + 1), "the e distance: ", end='')
            e_distance_tmp = return_euclidean_distance(features_cap_arr[0], GlobalVar.descriptors[i])
            # print(e_distance_tmp)
            e_distance_list.append(e_distance_tmp)
        else:
            # 空数据 person_X
            e_distance_list.append(999999999)
        # Find the one with minimum e distance
    similar_person_num = e_distance_list.index(min(e_distance_list))
    #      print("Minimum e distance with person:", candidate[int(similar_person_num)])

    if min(e_distance_list) < 0.45:
        GlobalVar.name_namelist = GlobalVar.candidate[int(similar_person_num)]
    print("In Server: The person may be " + GlobalVar.name_namelist)


def init():
    # 加载正脸检测器
   # GlobalVar.detector = dlib.get_frontal_face_detector()

    # 加载人脸关键点检测器
    ##GlobalVar.facerec = dlib.face_recognition_model_v1(
     #   "D:/JetBrains/FaceRecognation/model/dlib_face_recognition_resnet_model_v1.dat")


    # 7. 获取本机IP地址
    hostname = socket.gethostname()
    GlobalVar.ip = socket.gethostbyname(hostname)
    print("本机地址：", GlobalVar.ip)


def load_csv():
    with open('D:/JetBrains/FaceRecognation/feature.csv', 'r') as file:
        csv_reader = csv.reader(file)
        for row in csv_reader:
            # 转换为numpy array
            v = np.array(row)
            v_data = [float(x) for x in v]  # 将数据从string形式转换为float形式
            GlobalVar.descriptors.append(v_data)

        file.close()


def load_names():
    with open('D:/JetBrains/FaceRecognation/names.txt', 'r', encoding="utf-8") as namesfile:
        line = namesfile.readline()
        while line:
            line = line.replace('\n', '')
            GlobalVar.candidate.append(line)
            line = namesfile.readline()

        namesfile.close()


def show_mainpage():
    #mp = cv2.imread(GlobalVar.mainpage_file)
    (h, w) = mp.shape[:2]
    #resized = cv2.resize(mp, (w // 2, h // 2), interpolation=cv2.INTER_LINEAR)
    #cv2.imshow("MainPage", resized)
    #cv2.waitKey(10)


def predict(descriptors, img):
    # 对需识别人脸进行同样处理
    # 提取描述子

    dets = GlobalVar.detector(img, 1)

    dist = []
    for k, d in enumerate(dets):
        shape = GlobalVar.sp(img, d)
        face_descriptor = GlobalVar.facerec.compute_face_descriptor(img, shape)
        d_test = np.array(face_descriptor)

        # 计算欧式距离
        for i in descriptors:
            dist_ = np.linalg.norm(i - d_test)
            dist.append(dist_)
    return dist


def return_euclidean_distance(feature_1, feature_2):
    feature_1 = np.array(feature_1)
    feature_2 = np.array(feature_2)
    dist = np.sqrt(np.sum(np.square(feature_1 - feature_2)))
    return dist


def Save():
    #cap = cv2.VideoCapture(0)  # 从摄像头获取图像
    flag = 0
    time1 = time.time()  # 计时开始
    while flag != 2:
        ret, frame = cap.read()  # if frame is read correctly ret is True
        if not ret:
            print("Can't receive frame (stream end?). Exiting ...")
            exit()
        #cv2.imshow("MainPage", frame)
        #cv2.waitKey(10)

        #frame_gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
        #faces = GlobalVar.detector(frame_gray, 0)

        if (len(faces) == 1):  # 检测到有且仅有一张人脸，并且保持静止1秒
            if (flag == 0):
                flag = 1
                frame_gray_old = frame_gray
                start = time.time()  # 开始计时
            else:
                if (moving_detect(frame_gray_old, frame_gray) and flag == 1):  # 画面静止
                    end = time.time()  # 计算时间是否达到1秒
                    if (end - start >= 1):  # 计时1秒，时间到
                        cv2.destroyWindow("MainPage")
                        SavePic(frame)  # 保存图像
                        flag = 2  # 退出while循环

                else:  # 画面有变动
                    flag = 0
        else:  # 无人脸或有多张人脸
            flag = 0
            if (len(faces) > 1):
                print("Error: More than 1 person！！！")

        time2 = time.time()
        if (time2 - time1 > 10):  # 超时退出
            break

    cap.release()


def SavePic(img):
    filename = input("Please input your name (in English)：")  # 输入姓名（拼音）

    confirm = input("Your name is " + filename + ". Please confirm: Y/N:")

    if (filename != "" and (confirm == "Y" or confirm == "y")):
        # 保存图片
        save_path = os.path.join(GlobalVar.faces_folder_path, filename + ".jpg")  # 构建保存路径
        cv2.imwrite(save_path, img)
        Add2CSV(img)  # 更新人脸特征CSV文件
        Add2TXT(filename)  # 更新候选人名单
        load_names()  # 重新加载候选人名单
        load_csv()  # 重新加载全部人脸特征


def Add2CSV(img):
    with open('feature.csv', "a", newline="") as csvfile:
        writer = csv.writer(csvfile)
        # 1.人脸检测
        dets = GlobalVar.detector(img, 1)

        for k, d in enumerate(dets):
            # 2.关键点检测
            shape = GlobalVar.sp(img, d)

            # 3.描述子提取，128D向量
            face_descriptor = GlobalVar.facerec.compute_face_descriptor(img, shape)

            # 转换为numpy array
            v = np.array(face_descriptor)

            writer.writerow(v)  # 按行写入到Csv文件中

        csvfile.close()


def Add2TXT(filename):
    with open('names.txt', "a", newline="", encoding="utf-8") as namesfile:
        namesfile.write(filename + '\n')

        namesfile.close()


def moving_detect(img1, img2):  # 比较两幅画面，判断是否静止
    grey_diff = cv2.absdiff(img1, img2)  # 计算两幅图的像素差
    change = np.average(grey_diff)
    if (change > 10):
        return False  # 画面有明显变动，返回False
    else:
        return True  # 画面无明显变动，返回True


def Reset():
    cv2.destroyAllWindows()
    init()


if __name__ == '__main__':
    init()
    # 开启线程
    t = ServerThread()
    t.start()
'''
    while True:
        if cv2.waitKey(1) == ord('s') or cv2.waitKey(1) == ord('S'):
            Save()  # 保存图像
            show_mainpage()
        if cv2.waitKey(1) == ord('r') or cv2.waitKey(1) == ord('R'):
            Reset()  # 重新启动服务器
        if cv2.waitKey(1) == ord('q') or cv2.waitKey(1) == ord('Q'):
            # 释放窗口
            cv2.destroyAllWindows()
            t.terminate()  # 关闭子线程
            break
            '''
