package me.despawningbone.maicrypt;

import java.awt.GraphicsEnvironment;
import java.io.Console;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipParameters;
import org.apache.commons.compress.utils.IOUtils;

public class Main {

	public static void main(String[] args) throws IOException, InterruptedException {
        Console console = System.console();
        if(console == null && !GraphicsEnvironment.isHeadless()){
            String filename = Main.class.getProtectionDomain().getCodeSource().getLocation().toString().substring(6);
            if(System.getProperty("os.name").contains("Win")) Runtime.getRuntime().exec(new String[]{"cmd", "/c", "start", "cmd", "/c", "java -jar \"" + filename + "\""});
        }else{
        	System.out.println("-= maimai .bin data file decryption tool =-\n");
    		Boolean encrypt = null;
    		while (encrypt == null) {
    			System.out.print("Encrypt/decrypt (e/d)? ");
    			String line = System.console().readLine();
    			if (line.equalsIgnoreCase("e") || line.equalsIgnoreCase("d"))
    				encrypt = line.equalsIgnoreCase("e");
    		}
    		System.out.println("Selected " + (encrypt ? "encrypt" : "decrypt") + " mode.");
    		System.out.println("Please put the files in the same directory as the jar now (including a \"Keyfile.txt\" with only the key in hex form).");
    		String key = Files.readAllLines(new File("Keyfile.txt").toPath()).get(0).replaceAll("\\s+","");
    		String base, out;
    		if(encrypt) {
    			System.out.print("Output file name (the input file name should be the same as output but without file extension): ");
    			out = System.console().readLine();
    			base = out.substring(0, out.lastIndexOf("."));
    			compressGzip(base, base + "_EDITED.gz");
    			processFile(true, new File(base + "_EDITED.gz"), DatatypeConverter.parseHexBinary(key), new File(out));
    		} else {
    			System.out.print("Input file name: ");
    			String in = System.console().readLine();
    			base = in.substring(0, in.lastIndexOf("."));
    			processFile(false, new File(in), DatatypeConverter.parseHexBinary(key),
    					new File(base + ".gz"));
    			decompressGzip(base + ".gz", base);
    			out = base;
    		}
    		System.out.print("Done. Check the directory for the " + base + (encrypt ? "_EDITED" : "") + ".gz archive and the " + out + " file!\nPress any key to exit...");
    		System.console().readLine();
        }
	}

	private static void processFile(boolean encrypt, File input, byte[] byteKey, File output) {
		try (FileInputStream fileInputStream = new FileInputStream(input);
				FileOutputStream fileOutputStream = new FileOutputStream(output)) {
			Key key = new SecretKeySpec(byteKey, "AES");

			byte[] inputBytes = new byte[(int) input.length()];
			fileInputStream.read(inputBytes);
			if(encrypt) {  //append 16 bytes before encrypting
				ByteBuffer buf = ByteBuffer.allocate(inputBytes.length + 16);
				buf.put(new byte[16]);
				buf.put(inputBytes);
				inputBytes = buf.array();
			}

			byte[] iv = Arrays.copyOfRange(inputBytes, 0, 16);
			IvParameterSpec ivspec = new IvParameterSpec(iv);

			Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

			cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key, ivspec);

			byte[] outputBytes = cipher.doFinal(inputBytes);

			if(!encrypt) outputBytes = Arrays.copyOfRange(outputBytes, 16, outputBytes.length);
			
			fileOutputStream.write(outputBytes);

			fileInputStream.close();
			fileOutputStream.close();

		} catch (IOException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
				| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
			e.printStackTrace();
		}
	}

	private static void compressGzip(String file, String gzipFile) {
		GzipParameters param = new GzipParameters();
		param.setFilename(file);
		try (GzipCompressorOutputStream out = new GzipCompressorOutputStream(new FileOutputStream(gzipFile), param); FileInputStream fi = new FileInputStream(file)) {
			IOUtils.copy(fi, out);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private static void decompressGzip(String gzipFile, String file) {
		try (GzipCompressorInputStream in = new GzipCompressorInputStream(new FileInputStream(gzipFile)); FileOutputStream fo = new FileOutputStream(file)) {
			IOUtils.copy(in, fo);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
