package org.ray.devnote.io;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * StraySouls打包解包程序
 * 
 * @author Relay
 */
public class ZlibStraySouls {
	private static String unpackSourceFile;
	private static String unpackTagetDir;

	private static String packTagetFile;
	private static String packSourceDir;

	private static int offset = 0;
	private static int mOffset = 0x10;
	private static int zSize = 0;
	private static int nameSize = 0;

	private static String name;

	private static byte[] inputDatas;
	private static byte[] memoryDatas;
	private static byte[] tempDatas;

	private static Map<Integer, String> foldersMap;
	private static Map<Integer, String> filesMap;
	
	private static void testUnpack() {
		unpackSourceFile = "D:\\CHS\\StraySoulsHD\\StraySoulsPC\\localization";
		unpackTagetDir = "D:\\CHS\\StraySoulsHD\\java_unpack\\";
		unpack();
	}
	
	private static void testPack() {
		unpackSourceFile = "D:\\CHS\\StraySoulsHD\\StraySoulsPC\\localization";
		packTagetFile = "D:\\CHS\\StraySoulsHD\\java_localization";
		packSourceDir = "D:\\CHS\\StraySoulsHD\\java_unpack\\";
		pack();
	}
	
	private static void testPackUnpack() {
		unpackSourceFile = "D:\\CHS\\StraySoulsHD\\java_localization";
		unpackTagetDir = "D:\\CHS\\StraySoulsHD\\java_unpack_test\\";
		unpack();
	}

	public static void main(String[] args) {
//		testUnpack();
//		testPack();
//		testPackUnpack();
		
		execute(args);
	}

	private static void execute(String[] args) {
		if (args == null || args.length == 0) {
			printHelp();
			return;
		}

		String mode = args[0];
		if (!"-u".equals(mode) && !"-z".equals(mode)) {
			printHelp();
			return;
		}

		if ("-u".equals(mode) && args.length != 3) {
			printHelp();
			return;
		}

		if ("-z".equals(mode) && args.length != 4) {
			printHelp();
			return;
		}

		if ("-u".equals(mode)) { // unpack
			unpackSourceFile = args[1];
			unpackTagetDir = args[2];
			if (!unpackTagetDir.endsWith("\\") && !unpackTagetDir.endsWith("/")) {
				unpackTagetDir += "\\";
			}
			unpack();
		} else if ("-z".equals(mode)) {// pack
			unpackSourceFile = args[1];
			packTagetFile = args[2];
			packSourceDir = args[3];
			if (!packSourceDir.endsWith("\\") && !packSourceDir.endsWith("/")) {
				packSourceDir += "\\";
			}
			pack();
		}
	}
	
	private static void printHelp() {
		System.out.println("use -u to unpack file..");
		System.out.println("use -z to pack file..");
		System.out.println("em. java -cp xxx.jar org.ray.note.io.ZlibStraySouls -u {unpackSourceFile} {unpackTagetDir}");
		System.out.println("em. java -cp xxx.jar org.ray.note.io.ZlibStraySouls -z {unpackSourceFile} {packTagetFile} {packSourceDir}");
	}

	/**
	 * 解包
	 */
	private static void unpack() {
		System.out.println("unpack begin...");

		foldersMap = new HashMap<Integer, String>();
		filesMap = new HashMap<Integer, String>();

		try {
			inputDatas = FileUtils.toByteArray3(unpackSourceFile);

			int idSize = 0x4;
			String idstring = ByteUtils.byteArrayToString(inputDatas, 0, idSize);
			System.out.println(idstring);
			offset += idSize;

			nameSize = getInputDataLong();

			name = ByteUtils.byteArrayToString(inputDatas, offset, nameSize);
			offset += nameSize;

			if (!"Zlib".equals(name)) {
				System.out.println("无效待解包文件！");
				return;
			}

			zSize = getInputDataLong();
			zSize -= 0x4;
			int size = getInputDataLong();

			/** 文件目录名称信息数据解压解析 */
			tempDatas = ByteUtils.getByteArray(inputDatas, offset, zSize);
			memoryDatas = ZLibUtils.decompress(tempDatas);

			int mfolders = getMemoryDataLong();
			for (int i = 0; i < mfolders; i++) {
				nameSize = getMemoryDataLong();
				name = ByteUtils.byteArrayToString(memoryDatas, mOffset, nameSize);
				mOffset += nameSize;
				foldersMap.put(i, name);
			}

			int mfiles = getMemoryDataLong();
			for (int i = 0; i < mfiles; i++) {
				nameSize = getMemoryDataLong();
				name = ByteUtils.byteArrayToString(memoryDatas, mOffset, nameSize);
				mOffset += nameSize;
				filesMap.put(i, name);
			}

			offset += zSize;
			int files = getInputDataLong();
			for (int i = 0; i < files; i++) {
				int OFFSET = getInputDataLong();
				int ZSIZE = getInputDataLong();
				int FOLDERID = getInputDataLong();
				int FILEID = getInputDataLong();
				int ZIP = getInputDataLong();

				String FULLNAME = unpackTagetDir + foldersMap.get(FOLDERID) + "\\" + filesMap.get(FILEID);

				if (ZIP == 0) {
					System.out.print(i + " : " + "UNZIP OFFSET=" + toHex(OFFSET, true) + " ZSIZE=" + ZSIZE + " FULLNAME=" + FULLNAME);
					tempDatas = ByteUtils.getByteArray(inputDatas, OFFSET, ZSIZE);
					ByteUtils.byteArrayToFile(tempDatas, FULLNAME);
					System.out.println(" DONE!");
				} else {
					int SIZE = ByteUtils.signed4BytesToInt(inputDatas, OFFSET);
					OFFSET += 0x4;
					ZSIZE -= 0x4;

					System.out.print(i + " : " + "ISZIP OFFSET=" + toHex(OFFSET, true) + " ZSIZE=" + ZSIZE + " FULLNAME=" + FULLNAME);
					tempDatas = ByteUtils.getByteArray(inputDatas, OFFSET, ZSIZE);
					byte[] decompressDatas = ZLibUtils.decompress(tempDatas);
					ByteUtils.byteArrayToFile(decompressDatas, FULLNAME);
					System.out.println(" DONE!");
				}
			}

			System.out.println("unpack completed!");
			System.out.println("file location : " + unpackTagetDir);
		} catch (IOException e) {
			System.out.println("unpack fault!");
			e.printStackTrace();
		}
	}

	/**
	 * 打包
	 */
	private static void pack() {
		System.out.println("pack begin...");
		foldersMap = new HashMap<Integer, String>();
		filesMap = new HashMap<Integer, String>();
		
		try {
			File file = new File(packTagetFile);
			File dir = new File(file.getParent());
			if (!dir.exists()) {
				dir.mkdirs();
			}
			FileOutputStream fos = new FileOutputStream(file);
		
			inputDatas = FileUtils.toByteArray3(unpackSourceFile);

			int idSize = 0x4;
			String idstring = ByteUtils.byteArrayToString(inputDatas, 0, idSize);
			offset += idSize;
			ByteUtils.writeBytes(idstring.getBytes(), fos);

			nameSize = getInputDataLong();
			ByteUtils.writeBytes(ByteUtils.int2byte(nameSize), fos);

			name = ByteUtils.byteArrayToString(inputDatas, offset, nameSize);
			offset += nameSize;
			ByteUtils.writeBytes(name.getBytes(), fos);

			zSize = getInputDataLong();
			ByteUtils.writeBytes(ByteUtils.int2byte(zSize), fos);
			zSize -= 0x4;
			int size = getInputDataLong();
			ByteUtils.writeBytes(ByteUtils.int2byte(size), fos);

			/** 文件目录名称信息数据解压解析 */
			tempDatas = ByteUtils.getByteArray(inputDatas, offset, zSize);
			ByteUtils.writeBytes(tempDatas, fos);
			memoryDatas = ZLibUtils.decompress(tempDatas);

			int mfolders = getMemoryDataLong();
			for (int i = 0; i < mfolders; i++) {
				nameSize = getMemoryDataLong();
				name = ByteUtils.byteArrayToString(memoryDatas, mOffset, nameSize);
				mOffset += nameSize;
				foldersMap.put(i, name);
			}

			int mfiles = getMemoryDataLong();
			for (int i = 0; i < mfiles; i++) {
				nameSize = getMemoryDataLong();
				name = ByteUtils.byteArrayToString(memoryDatas, mOffset, nameSize);
				mOffset += nameSize;
				filesMap.put(i, name);
			}

			offset += zSize;
			System.out.println("pack memoryDatas offset : " + toHex(offset, true));

			int files = getInputDataLong();
			int infoOffset = offset;
			ByteUtils.writeBytes(ByteUtils.int2byte(files), fos);

			int pos = files * 5 * 4; // 文件数量 * 5个数据项 * 4个字节
			int headerOffset = offset + pos; // 文件信息表结束位置
			for (int i = 0; i < files; i++) {
				// 读取原始文件表信息
				int OFFSET = getInputDataLong();
				int ZSIZE = getInputDataLong();
				int FOLDERID = getInputDataLong();
				int FILEID = getInputDataLong();
				int ZIP = getInputDataLong();

				String FULLNAME = packSourceDir + foldersMap.get(FOLDERID) + "\\" + filesMap.get(FILEID);
				tempDatas = FileUtils.toByteArray3(FULLNAME);

				byte[] compressDatas = null;
				if (ZIP == 0) {
					compressDatas = tempDatas;
				} else {
					compressDatas = ZLibUtils.compress(tempDatas);
				}

				// 新的压缩文件大小
				ZSIZE = compressDatas.length;
				if (ZIP == 1) {
					// 如果需要压缩，则增加4个字节，与解压对应
					ZSIZE += 4;
				}
				// 写入新文件表信息
				ByteUtils.writeBytes(ByteUtils.int2byte(headerOffset), fos);
				ByteUtils.writeBytes(ByteUtils.int2byte(ZSIZE), fos);
				ByteUtils.writeBytes(ByteUtils.int2byte(FOLDERID), fos);
				ByteUtils.writeBytes(ByteUtils.int2byte(FILEID), fos);
				ByteUtils.writeBytes(ByteUtils.int2byte(ZIP), fos);
				headerOffset += ZSIZE; // 加上当前文件大小后等于下个文件起始位置
			}
			System.out.println("pack headerOffset offset : " + toHex(headerOffset, true));

			offset = infoOffset; // 还原到信息位置，以便重新读取文件表信息
			for (int i = 0; i < files; i++) {
				// 读取原始文件表信息，获取文件路径，以便找到修改后的文件
				int OFFSET = getInputDataLong();
				int ZSIZE = getInputDataLong();
				int FOLDERID = getInputDataLong();
				int FILEID = getInputDataLong();
				int ZIP = getInputDataLong();

				// 待压缩文件
				String FULLNAME = packSourceDir + foldersMap.get(FOLDERID) + "\\" + filesMap.get(FILEID);
				tempDatas = FileUtils.toByteArray3(FULLNAME);

				byte[] compressDatas = null;
				if (ZIP == 0) { // 不压缩
					compressDatas = tempDatas;
				} else { // 压缩
					int SIZE = tempDatas.length; // 原始文件长度
					ByteUtils.writeBytes(ByteUtils.int2byte(SIZE), fos);
					compressDatas = ZLibUtils.compress(tempDatas);
				}

				// 写入文件内容
				ByteUtils.writeBytes(compressDatas, fos);
				System.out.println("pack file : " + FULLNAME);
			}

			// 输出文件，打包
			fos.flush();
			fos.close();
			System.out.println("pack completed!");
			System.out.println("file location : " + packTagetFile);
		} catch (IOException e) {
			System.out.println("pack fault!");
			e.printStackTrace();
		}
	}

	/**
	 * 获取memoryDatas当前偏移位置的大小数据，并将偏移位置向后移动4个字节
	 * 
	 * @return
	 */
	private static int getMemoryDataLong() {
		int value = ByteUtils.signed4BytesToInt(memoryDatas, mOffset);
		mOffset += 0x4;
		return value;
	}

	/**
	 * 获取inputDatas当前偏移位置的大小数据，并将偏移位置向后移动4个字节
	 * 
	 * @return
	 */
	private static int getInputDataLong() {
		int value = ByteUtils.signed4BytesToInt(inputDatas, offset);
		offset += 0x4;
		return value;
	}

	public static String toHex(int num, boolean format) {
		String hex = Integer.toHexString(num).toUpperCase();
		if (hex.length() == 1) {
			hex = '0' + hex;
		}
		if (format) {
			hex = "0x" + hex;
		}
		return hex;
	}
}
