#-*- coding: UTF-8 -*- 
#!/usr/bin/python

import os
import sys
import fs
import struct
import json
from collections import OrderedDict
import zlib
import pprint
import hashlib

def str_to_hex(s):
    return ' '.join([hex(ord(c)).replace('0x', '') for c in s])

def hex_to_str(s):
    return ''.join([chr(i) for i in [int(b, 16) for b in s.split(' ')]])

def str_to_bin(s):
    return ' '.join([bin(ord(c)).replace('0b', '') for c in s])

def bin_to_str(s):
    return ''.join([chr(i) for i in [int(b, 2) for b in s.split(' ')]])


class EpkApp(object):

    def __init__(self, appName, appDir, appVersion="1.0", output="epks"):
        super(EpkApp, self).__init__()
        self._appName = appName
        self._appDir = os.path.abspath(appDir)
        self._appVersion = appVersion
        self._appCRCCode = None
        self._files = []
        self._infoPath = os.sep.join([self._appDir, "%s.json" % self._appName])
        self._epksDir = output
        if not os.path.exists(self._epksDir):
            fs.open_fs(os.getcwd()).makedirs(output)
        self._epkName = os.sep.join([self._epksDir, "%s.epk" % self._appName])

    def epkInfo(self):
        epkInfo = OrderedDict({
            "appName": self._appName,
            "appVersion": self._appVersion,
            "files": self.fileinfos(self._appDir),
        })

        infocontent = json.dumps(epkInfo)
        with open(self._infoPath, "w", encoding='utf-8') as f:
            f.write(infocontent)

        return epkInfo
    
    def fileinfos(self, path):
        path = os.path.abspath(path)
        home_fs = fs.open_fs(path)
        files = []
        for jspath in home_fs.glob('*', namespaces=['details']):
            fpath = "C:/%s" % jspath.info.name
            fname = jspath.info.name
            fsize = jspath.info.size
            fbasename = jspath.info.name.split(".")[0]
            fext = jspath.info.name.split(".")[1]

            if fext in ["exe", "dll", "nv", "conf"]:
                continue

            finfo = {
                "path": fpath,
                "name": fname,
                "size": fsize,
                "basename": fbasename,
                "ext": fext
            }
            
            if self._infoPath == os.sep.join([path, fname]):
                files.insert(0, finfo)
            else:
                files.append(finfo)
            
            if fext == "evue":
                self.fileMD5(finfo)

        return files
    
    def header(self, epk_start=0xAAAA, md5_offset=0, file_count=0):

        bytes_header = struct.pack("<HLH", epk_start, md5_offset, file_count)

        return bytes_header

    
    def fileMD5(self, info):
        md5path = os.sep.join([self._appDir, "%s.md5" % info["basename"]])
        fpath = os.sep.join([self._appDir, info["name"]])
        with open(fpath, "rb") as f:
            filecontent = f.read()

        print(fpath)
        newmd5 = self.md5(filecontent)
        print(md5path)
        with open(md5path, "wb") as f:
            f.write(newmd5)

        return newmd5
    
    def sign(self, content):
        ret = b""
        for i in range(int(len(content) / 2 )):
            ret += struct.pack("<B", int("0x%s" % (content[i*2:i*2+2]), 16))
        ret = ret + b'EVM is NB ++!'
        return ret

    def md5(self, filecontent):
        newmd5 = ''
        content = filecontent
        for i in range(3):
            md5 = hashlib.md5()   #获取一个md5加密算法对象
            md5.update(content)  #指定需要加密的字符串
            newmd5 = md5.hexdigest()  #获取加密后的16进制字符串
            print(newmd5)
            content = self.sign(newmd5)

        ret = b""
        for i in range(int(len(newmd5) / 2 )):
            ret += struct.pack("<B", int("0x%s" % (newmd5[i*2:i*2+2]), 16))
        
        return ret

    def packFile(self, info, level=9):
        fname = info["name"]
        fpath = os.sep.join([self._appDir, fname])
        fext = info["ext"]


        fileBytes = b""
        if fext == "md5":
            fileBytes += struct.pack("<B", 1)
        else:
            fileBytes += struct.pack("<B", 2)

        _name = fname + "\0"

        fileBytes += struct.pack("<B", len(_name))
        fileBytes += struct.pack("<%ds" % len(_name), fname.encode("utf-8"))
        with open(fpath, "rb") as fc:
            fileContentBytes = fc.read()

            print(info["name"])
            print(len(fileContentBytes))

            fileBytes += struct.pack("<L", len(fileContentBytes))
        
        if fext == "md5":
            fileCompressBytes = fileContentBytes
        else:
            fileCompressBytes = zlib.compress(fileContentBytes, level)

        print(len(fileCompressBytes))

        fileBytes += struct.pack("<L", len(fileCompressBytes))

        fileBytes += fileCompressBytes

        return fileBytes

    def pack(self, level=9):
        for i in range(10):
            infos = self.epkInfo()
        # infos = self.epkInfo()
        # infos = self.epkInfo()

        result = {}

        epkFileBytes = b""
        epkFileContentBytes = b""
        file_count = len(infos["files"])
        with open(self._epkName, "wb") as f:
            for info in infos["files"]:
                epkFileContentBytes += self.packFile(info)
            

            epkFileContentLength = len(epkFileContentBytes)

            epkFileBytes += self.header(md5_offset= 8 + epkFileContentLength, file_count=file_count)

            epkFileBytes += epkFileContentBytes
            
            epkmd5Bytes = self.md5(epkFileBytes)

            epkFileBytes += struct.pack("<H", len(epkmd5Bytes))

            epkFileBytes += epkmd5Bytes

            crcBytes = zlib.crc32(epkFileBytes)

            epkFileBytes += struct.pack("<L", crcBytes)

            f.write(epkFileBytes)

            ret = {
                "epkfile": self._epkName,
                "epk_filecontent_size": epkFileContentLength,
                "md5_offset": 10 + epkFileContentLength,
                "file_count": file_count,
                "md5_length": len(epkmd5Bytes),
                "md5": epkmd5Bytes,
                "raw_crc": hex(crcBytes),
                "compress_level": level,
                "buff_length": len(epkFileBytes)
            }

            result = ret
        return result

def main(path, appName):
    epk = EpkApp(appName, path)
    epk.pack()


if __name__ == '__main__':
    main(sys.argv[1], sys.argv[2])
