import base64
import struct
from tkinter import *
import time
from tkinter import filedialog
import pickle
import socket
import threading
import tkinter.messagebox
from algorithms import AESalgorithm, generateKey, RSAalgorithm, hashalg
import json
import sys

# 使用tkinter建立GUI

IP = '127.0.0.1'
PORT = 4396
BUFF = 5120
FIP='127.0.0.1'
FPORT=7932


# CLIENTPUBLICs
# SERVERPUBLICs
# SERVERPRIVATEs
def initKey():
    global SERVERPUBLICs, SERVERPRIVATEs
    (SERVERPRIVATEs, SERVERPUBLICs) = generateKey.generateMyKey("keys/server/server")

def fileDecrypt(data):
    (message,encrykey)=pickle.loads(data)
    onceKey= RSAalgorithm.RsaDecrypt(encrykey, SERVERPRIVATEs)
    print("接收到的密钥",onceKey,type(onceKey))
    message= AESalgorithm.AesDecrypt(message, onceKey.decode('unicode_escape'))
    message=pickle.loads(message)
    content=base64.b64decode(message['Message'])
    print('传送的内容是',content)
    digest=message['digest']
    if RSAalgorithm.VerRsaSignal(content, digest, CLIENTPUBLICs):
        return content

def initFileListen():
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定端口为9001
        s.bind((FIP, FPORT))
        # 设置监听数
        s.listen(10)
    except socket.error as msg:
        print(msg)
        sys.exit(1)
    print('Waiting connection...')

    while True:
        # 等待请求并接受(程序会停留在这一旦收到连接请求即开启接受数据的线程)
        conn, addr = s.accept()
        # 接收数据
        t = threading.Thread(target=deal_data, args=(conn, addr))
        t.start()


def deal_data(conn, addr):
    print('Accept new connection from {0}'.format(addr))
    txtMsgList.insert(END, '文件系统收到一个新的连接，地址来自 {0}'.format(addr), 'greencolor')
    # conn.settimeout(500)
    # 收到请求后的回复
    conn.send('你好,连接建立成功了'.encode('utf-8'))

    while True:
        # 申请相同大小的空间存放发送过来的文件名与文件大小信息
        fileinfo_size = struct.calcsize('128sl')
        # 接收文件名与文件大小信息
        buf = conn.recv(fileinfo_size)
        # 判断是否接收到文件头信息
        if buf:
            # 获取文件名和文件大小
            filename, filesize = struct.unpack('128sl', buf)
            fn = filename.strip(b'\00')
            fn = fn.decode()
            print('file new name is {0}, filesize if {1}'.format(str(fn), filesize))
            txtMsgList.insert(END, '收到的文件名字为 {0}, 文件大小为 {1}'.format(str(fn), filesize), 'greencolor')
            recvd_size = 0  # 定义已接收文件的大小
            # 存储在该脚本所在目录下面
            fp = open('./' + str(fn), 'wb')
            print('start receiving...')
            txtMsgList.insert(END, '开始接受...', 'greencolor')
            # 将分批次传输的二进制流依次写入到文件
            while not recvd_size == filesize:
                if filesize - recvd_size > 1024:
                    lens=conn.recv(1024).decode('utf-8')
                    lens=int(lens)
                    print('该段发送长度为',lens)
                    data = conn.recv(lens)
                    data=fileDecrypt(data)
                    recvd_size += len(data)
                else:
                    lens = conn.recv(1024).decode('utf-8')
                    lens = int(lens)
                    print('该段发送长度为', lens)
                    data = conn.recv(lens)
                    data = fileDecrypt(data)
                    recvd_size = filesize
                conn.send('I have receive the past one'.encode('utf-8'))
                fp.write(data)
            fp.close()
            print('end receive...')
            txtMsgList.insert(END, '接收完毕...', 'greencolor')
        # 传输结束断开连接
        conn.close()
        break


def mainPage():
    def sendMsg(Sock):  # 发送消息
        strMsg = "我:" + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + '\n'
        txtMsgList.insert(END, strMsg, 'greencolor')
        Mes = txtMsg.get('0.0', END)
        txtMsgList.insert(END, Mes)
        print(Mes)
        onceKey = AESalgorithm.genKey()  # 一次一密 密钥
        print("oncekey", onceKey)
        digest = RSAalgorithm.RsaSignal(Mes, SERVERPRIVATEs)  # 先hash再签名# 生成消息摘要
        message = {'Message': Mes, 'digest': digest.decode("utf-8")}  # 把消息和摘要打包
        message = json.dumps(message)  # 转成json字符串
        message = AESalgorithm.AesEncrypt(message, onceKey)  # 合并加密
        encrykey = RSAalgorithm.RsaEncrypt(onceKey, CLIENTPUBLICs)  # 用服务器公钥加密一次密钥
        txtMsg.delete('0.0', END)
        Message = pickle.dumps([message, encrykey.decode('utf-8')])  # 序列化消息，用于传输
        Sock.send(Message)

    def RecvMsg(Sock, test):
        global CLIENTPUBLICs
        while True:
            Message = Sock.recv(BUFF)  # 收到文件
            (message, encrykey) = pickle.loads(Message)
            mykey = RSAalgorithm.RsaDecrypt(encrykey, SERVERPRIVATEs)  # 用私钥解密获得一次密钥
            decryMes = AESalgorithm.AesDecrypt(message, mykey.decode('utf-8'))  # 用一次密钥解密消息，获得包含消息内容和摘要的json
            decryMes = json.loads(decryMes)  # 将json转换为python字典
            content = decryMes['Message']
            digest = decryMes['digest'].encode('utf-8')
            if RSAalgorithm.VerRsaSignal(content, digest, CLIENTPUBLICs):
                strMsg = "对方:" + time.strftime("%Y-%m-%d %H:%M:%S",
                                               time.localtime()) + "通过数字签名认证,本次密钥为" + mykey.decode('utf-8') + '\n'
                txtMsgList.insert(END, strMsg, 'greencolor')
                txtMsgList.insert(END, content + '\n')


    def cancelMsg():  # 取消信息
        txtMsg.delete('0.0', END)

    def sendMsgEvent(event, Sock):  # 发送消息事件
        if event.keysym == 'Up':
            sendMsg(Sock)

    def UploadAction(event=None):
        filename = filedialog.askopenfilename()
        print('Selected:', filename)

    def addSysTip(mes):
        global txtMsgList
        txtMsgList.insert(END, "系统消息：" + mes)

    def exchangePublicKey(dir):
        global ConSock, txtMsgList
        with open(dir, 'rb') as fi:
            publicKey = fi.read()
        # print(publicKey)
        has = hashalg.hash_sha256(publicKey)
        Message = pickle.dumps([publicKey, has])
        try:
            ConSock.send(Message)
            txtMsgList.insert(END, "发送公钥成功\n")
        except:
            txtMsgList.insert(END, "密钥发送失败，正在尝试重新发送...\n")
            exchangePublicKey(dir)

    def verifyKey(Sock):
        global txtMsgList, CLIENTPUBLICs
        while True:
            Message = Sock.recv(BUFF)
            # print("shoudao:",Message)
            (publickey, hash_256) = pickle.loads(Message)
            if hash_256 == hashalg.hash_sha256(publickey):
                txtMsgList.insert(END, "公钥完整性验证完成，可以开始传输文件\n")
                CLIENTPUBLICs = publickey
                txtMsgList.insert(END, "收到公钥\n" + CLIENTPUBLICs.decode('utf-8') + "\n")
                # print("publicc:", CLIENTPUBLICs)
                break
            else:
                txtMsgList.insert(END, "验证失败\n")

    def cnct():
        global txtMsgList, ConSock
        HOSTIP = '127.0.0.1'
        ServerSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        ServerSock.bind((HOSTIP, PORT))
        ServerSock.listen(8)
        print("本机IP地址为", HOSTIP, "端口号为", PORT, ",正在监听中")
        txtMsgList.insert(END, "系统消息：" + "本机IP地址为" + HOSTIP + "端口号为" + str(PORT) + ",正在监听中\n")
        ConSock, addr = ServerSock.accept()
        print('连接成功')
        txtMsgList.insert(END, "系统消息：连接成功\n")
        exchangePublicKey("keys/server/serverpublic.pem")
        verifyKey(ConSock)
        thread_rev = threading.Thread(target=RecvMsg, args=(ConSock, None))
        thread_rev.start()
        return ConSock

    def setIpWindows():
        def setNewIP(newip, newport):
            print(newip, newport)
            global IP
            IP = str(newip)
            global PORT
            PORT = int(newport)
            set.destroy()
            try:
                cnct()
            except:
                addSysTip("连接异常，ip或端口不可访问")
                tkinter.messagebox.showwarning('连接失败', '连接异常，ip或端口不可访问\n')
                print("连接异常，ip或端口不可访问\n")

        set = Tk()
        set.title('设置ip地址和端口号')
        set.geometry('350x200')
        set.resizable(0, 0)
        # ip
        Label(set, text='IP地址：').place(x=10, y=10)
        ent1 = Entry(set)
        ent1.place(x=150, y=10)
        # port
        Label(set, text='端口号：').place(x=10, y=50)
        ent2 = Entry(set)
        ent2.place(x=150, y=50)
        bt_connect = Button(set, text='连接', command=lambda: setNewIP(ent1.get(), ent2.get()))
        bt_connect.place(x=150, y=130)
        set.mainloop()

    def start():
        global app, frmLT, frmLC, frmLB, txtMsgList, txtMsg, btnSend, btnCancel, btnFile, btnSet
        # 创建窗口
        app = Tk()
        app.title('Server')
        app.resizable(0, 0)

        # 创建frame容器
        frmLT = Frame(width=500, height=320, bg='white')
        frmLC = Frame(width=500, height=150, bg='white')
        frmLB = Frame(width=500, height=30)
        # frmRT = Frame(width = 200, height = 500)

        # 创建控件
        txtMsgList = Text(frmLT)
        txtMsgList.tag_config('greencolor', foreground='#008C00')  # 创建tag
        txtMsg = Text(frmLC)
        txtMsg.bind("<KeyPress-Up>", sendMsgEvent)
        btnSend = Button(frmLB, text='发送', width=8, command=lambda: sendMsg(ServerSocket))
        btnCancel = Button(frmLB, text='取消', width=8, command=cancelMsg)
        btnFile = Button(frmLB, text='上传文件', width=8, command=UploadAction)
        btnSet = Button(frmLB, text='设置ip', width=8, command=setIpWindows)
        # btnFile.pack()
        # imgInfo = PhotoImage(file = "timg-2.gif")
        # lblImage = Label(frmRT, image = imgInfo)
        # lblImage.image = imgInfo

        # 窗口布局
        frmLT.grid(row=0, column=0, columnspan=2, padx=1, pady=3)
        frmLC.grid(row=1, column=0, columnspan=2, padx=1, pady=3)
        frmLB.grid(row=2, column=0, columnspan=2)
        # frmRT.grid(row = 0, column = 2, rowspan = 3, padx =2, pady = 3)

        # 固定大小
        frmLT.grid_propagate(0)
        frmLC.grid_propagate(0)
        frmLB.grid_propagate(0)
        # frmRT.grid_propagate(0)

        btnSend.grid(row=2, column=0)
        btnCancel.grid(row=2, column=1)
        btnFile.grid(row=2, column=2)
        btnSet.grid(row=2, column=3)
        # lblImage.grid()
        txtMsgList.grid()
        txtMsg.grid()
        # 主事件循环
        app.mainloop()

    thread_gui = threading.Thread(target=start)
    thread_gui.start()

    ServerSocket = cnct()
    # try:
    #     ServerSocket=cnct()
    # except:
    #     addSysTip("连接异常，ip或端口不可访问")
    #     tkinter.messagebox.showwarning('连接失败', '连接异常，ip或端口不可访问，点击设置按钮重新设置\n')
    #     print("连接异常，ip或端口不可访问\n")


def main():
    initKey()
    thread_1=threading.Thread(target=initFileListen)
    # thread_2=threading.Thread(target=mainPage)
    mainPage()
    thread_1.start()
    # thread_2.start()


if __name__ == "__main__":
    main()
