package cn.edu.whu.boot.common.utils.allocation;

import java.io.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

/**
 * 加密 TPK 文件
 */
public class TpkEncryptHelper {
    private static final String BundlxFileSuffix = ".bundlx";

    /**
     * 加密 .tpk 文件，生成 tpkx 文件
     *
     * @param tpkUrl   原始 tpk 文件的 URL
     * @param tpkxRoot tpkx 文件的根目录
     * @throws IOException
     */
    public static void encryptTpk(String tpkUrl, String tpkxRoot) throws IOException {
        File file = new File(tpkUrl);
        String suffix = ".tpkx";
        String fileNameWithExtension = file.getName();
        String fileNameWithoutExtension = fileNameWithExtension.substring(0, fileNameWithExtension.indexOf('.'));
        if (!file.exists()) {
            throw new FileNotFoundException();
        }

        try (FileOutputStream fos = new FileOutputStream(tpkxRoot + fileNameWithoutExtension + suffix);
             ZipOutputStream zipOutStream = new ZipOutputStream(fos);
             BufferedOutputStream bufferOutStream = new BufferedOutputStream(zipOutStream);
             ZipFile zf = new ZipFile(file, Charset.forName("GBK"))) {
            Enumeration entries = zf.entries();
            List<ZipEntry> bundlx = new ArrayList<>();
            while (entries.hasMoreElements()) {
                ZipEntry ele = (ZipEntry) entries.nextElement();
                if (ele.getName().contains(BundlxFileSuffix)) {
                    bundlx.add(ele);
                } else if (!ele.isDirectory()) {
                    ZipEntry zipEntry = new ZipEntry(ele.getName());
                    zipOutStream.putNextEntry(zipEntry);
                    try (InputStream is = zf.getInputStream(ele)) {
                        byte[] data = new byte[4096];
                        int length;
                        while ((length = is.read(data)) != -1) {
                            zipOutStream.write(data, 0, length);
                        }
                        zipOutStream.flush();//刷新输出流
                    }
                }
            }
            for (ZipEntry entry : bundlx) {
                try (InputStream bundlxInputStream = zf.getInputStream(entry);
                     BufferedInputStream bundlxInput = new BufferedInputStream(bundlxInputStream)) {
                    ZipEntry zipEntry = new ZipEntry(entry.getName());
                    zipOutStream.putNextEntry(zipEntry);
                    byte[] header = new byte[16];
                    int len = bundlxInput.read(header);
                    bufferOutStream.write(header, 0, len);

                    byte[] data = new byte[5];
                    int length;
                    int count = 0;
                    while ((length = bundlxInput.read(data)) != -1 && count < 128 * 128) {
                        int[] tmp = byte2int(data, length);
                        int[] pwd = PiEncryptUtils.encrypt(tmp);
                        bufferOutStream.write(int2byte(pwd, length), 0, length * 4);
                        count++;
                    }

                    byte[] footer = new byte[16];
                    int fLen = bundlxInput.read(header);
                    bufferOutStream.write(footer, 0, fLen);
                    bufferOutStream.flush();//刷新输出流
                }
            }
        }
    }

    // 为了配合圆周率加密，将原来的byte[5]改为byte[16]
    // 只是把每一个byte转为double
    private static int[] byte2int(byte[] data, int n) {
        int[] result = new int[n];
        for (int i = 0; i < n; i++) {
            result[i] = data[i];
        }
        return result;
    }

    private static byte[] int2byte(int[] data, int n) {
        byte[] result = new byte[n * 4];
        for (int i = 0; i < n; i++) {
            byte[] tmp = int2ByteArray(data[i]);
            result[i * 4] = tmp[0];
            result[i * 4 + 1] = tmp[1];
            result[i * 4 + 2] = tmp[2];
            result[i * 4 + 3] = tmp[3];
        }
        return result;
    }

    private static byte[] int2ByteArray(int i) {
        byte[] result = new byte[4];
        result[0] = (byte) ((i >> 24) & 0xFF);
        result[1] = (byte) ((i >> 16) & 0xFF);
        result[2] = (byte) ((i >> 8) & 0xFF);
        result[3] = (byte) (i & 0xFF);
        return result;
    }
}
