#include <vector>
#include <string>
#include <iostream>
#include <iomanip>
#include <algorithm>
//#include <initializer_list>

#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <openssl/aes.h>

typedef unsigned char byte;

enum FILE_TYPE {
	FT_ENC = 0,
	FT_DEC,
	FT_KEY,
	FT_IV
};

template <size_t multiple> size_t round_up(const size_t len)
{
    if (len % multiple == 0) return len;
    else return ((len / multiple) + 1) * multiple;
}

std::ostream &print_buffer_as_hex(std::ostream &o, const unsigned char *buf, size_t size)
{
    o << std::hex << std::setfill('0');
    for( size_t i = 0; i < size; ++i )
    {
        o << std::setw(2) << static_cast<unsigned int>(buf[i]);
    }
    return o << std::dec;
}

inline std::ostream &operator<<(std::ostream &o, const std::vector<byte> &buf)
{
    return print_buffer_as_hex(o, reinterpret_cast<const unsigned char*>(&buf[0]), buf.size());
}

// Make a Key of exactly 32 bytes, truncates or adds values if it's necessary
std::string AES_NormalizeKey(const void *const apBuffer, size_t aSize)
{
    static const unsigned char key32[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
    const char *const Buffer = reinterpret_cast<const char *>(apBuffer);
    std::string Result(reinterpret_cast<const char *>(key32), 32);
    std::copy(Buffer, Buffer + ((aSize < 32)? aSize: 32), Result.begin());
    return Result;
}

// Encrypt using AES cbc
std::string AESEncrypt(const void *const apBuffer, size_t aBufferSize, unsigned char** ppOutBuf, int* pOutLen, const void *const apKey, size_t aKeySize, unsigned char* aIVector)
{
    // Create IVector.
    unsigned char AES_IVector[AES_BLOCK_SIZE] = {0};
	memcpy(AES_IVector, aIVector, AES_BLOCK_SIZE);

    // Create key.
    const std::string Key(AES_NormalizeKey(apKey, aKeySize));
//    std::cout << "(Normalized key: ";
//    print_buffer_as_hex(std::cout, (const unsigned char*)Key.data(), Key.size()) << ")\n";
    AES_KEY EncryptKey;
    AES_set_encrypt_key(reinterpret_cast<const unsigned char *>(Key.data()), 256, &EncryptKey);

    // Encrypt.
    unsigned char AES_Encrypted[4096] = {0};
    AES_cbc_encrypt(static_cast<const unsigned char *>(apBuffer), AES_Encrypted, aBufferSize, &EncryptKey, AES_IVector, AES_ENCRYPT);
    const std::string Encrypted(reinterpret_cast<const char *>(AES_Encrypted), round_up<AES_BLOCK_SIZE>(aBufferSize));
    	
    *pOutLen = round_up<AES_BLOCK_SIZE>(aBufferSize);
    *ppOutBuf = (unsigned char*)malloc(*pOutLen);
    memcpy(*ppOutBuf, AES_Encrypted, *pOutLen);

    // Finish.
    return Encrypted;
};

// Decrypt using AES cbc
std::string AESDecrypt(const void *const apBuffer, size_t aBufferSize, unsigned char** ppOutBuf, int* pOutLen, const void *const apKey, size_t aKeySize, unsigned char* aIVector)
{
    // Read IVector.
    unsigned char AES_IVector[AES_BLOCK_SIZE] = {0};
	memcpy(AES_IVector, aIVector, AES_BLOCK_SIZE);

    // Create Key.
    const std::string Key(AES_NormalizeKey(apKey, aKeySize));
//    std::cout << "(Normalized key: ";
//    print_buffer_as_hex(std::cout, (const unsigned char*)Key.data(), Key.size()) << ")\n";
    AES_KEY DecryptKey;
    AES_set_decrypt_key(reinterpret_cast<const unsigned char *>(Key.data()), 256, &DecryptKey);

    // Decrypt.
    unsigned char AES_Decrypted[4096] = {0};
    AES_cbc_encrypt(static_cast<const unsigned char *>(apBuffer), AES_Decrypted, aBufferSize, &DecryptKey, AES_IVector, AES_DECRYPT);
    const std::string Decrypted(reinterpret_cast<const char *>(AES_Decrypted));
    	
    *ppOutBuf = (unsigned char*)malloc(aBufferSize);
    memcpy(*ppOutBuf, AES_Decrypted, aBufferSize);
    *pOutLen = aBufferSize;
  
    // Finish.
    return Decrypted;
};

int ReadFile(FILE_TYPE ft, char* file, unsigned char** buf, int* len)
{
		FILE* fp = fopen(file, "rb");
		if (fp == NULL) {
			printf("Open file(%s) fail!!!\n", file);
			exit(0);
		}
		
		long fileLen = 0;
		long orgFileLen = 0;
		
		fseek(fp, 0, SEEK_END);
		fileLen = ftell(fp);
		fseek(fp, 0, SEEK_SET);

		if (ft == FT_DEC) {
			fread(&orgFileLen, 1, 4, fp);
//			orgFileLen = htonl(orgFileLen);
			fileLen -= 4;
		}
		
		int bufSize = round_up<AES_BLOCK_SIZE>(fileLen);
		
		*buf = (unsigned char*)malloc(bufSize);
		memset(*buf, 0, bufSize);
		if (*buf == NULL) {
			printf("Allocate memory fail!!!\n");
			fclose(fp);
			exit(0);
		}	
		*len = fread(*buf, 1, fileLen, fp);
		*len = bufSize;
//		printf("file:%s len:%ld rdLen:%d\n", file, fileLen, *len);
//		printf("%s\n", *buf);
		fclose(fp);
		
		return ft == FT_DEC ? orgFileLen:fileLen;
}

bool WriteFile(char* file, char* buf, int len, int orgFileLen)
{
	FILE* fpOut = fopen(file, "w");
	if (fpOut != NULL) {
		if (orgFileLen) fwrite(&orgFileLen, 1, 4, fpOut);
		fwrite(buf, 1, len, fpOut);
		fclose(fpOut);
		return true;
	} else {
		printf("Open outputfile(%s) fail!!!\n", file);
	}
	
	return false;
}

// Entry point
int main(int argc, char **argv)
{
    typedef std::vector<std::string> vs;
    vs a;

	if (argc != 6) {
		printf("Wrong parameters!!!\n");
		printf("Usage: Command.exe [enc|dec]  <outputFile> <inputFile> <keyFile> <ivFile>\n");
		exit(0);
	}

	//input file
	int inputLen = 0;
	unsigned char *input = NULL;
	
	//key file
	int keyLen = 0;
	unsigned char *key = NULL;
	ReadFile(FT_KEY, argv[4], &key, &keyLen);
	a.push_back((const char*)key);

	//iv file
	int ivLen = 0;
//	unsigned char IV[AES_BLOCK_SIZE] = {0x00, };
	unsigned char* iv = NULL;
	ReadFile(FT_IV, argv[5], &iv, &ivLen);

	int outputLen = 0;
	unsigned char *output = NULL;
	
    if (key && iv)
    {
		if (!strncmp(argv[1], "enc", 3)) {
			int inFileLen = ReadFile(FT_ENC, argv[3], &input, &inputLen);
			a.push_back((const char*)input);
			
        	std::string e(AESEncrypt(input, inputLen, &output, &outputLen, key, keyLen, iv));
        	//a.at(0).data(), a.at(0).size(), a.at(1).data(), a.at(1).size(), iv));
#if 0        	
        	std::cout << "Encrypting : " << a.at(0) << "\n"
        					<< "Size       : " << a.at(0).size() << "\n"
                  << "With Key   : " << a.at(1) << "\n"
                  << "Init Vec   : " << iv << "\n"
                  << "Becomes    : " << e << "\n";
#endif                  
			if (WriteFile(argv[2], (char*)output, outputLen, inFileLen)) {
				printf("Encrypt %s success\n", argv[3]);
			}
		} else if (!strncmp(argv[1], "dec", 3)) {
			int inFileLen = ReadFile(FT_DEC, argv[3], &input, &inputLen);
			a.push_back((const char*)input);
	
        	std::string d(AESDecrypt(input, inputLen, &output, &outputLen, key, keyLen, iv));
        	//a.at(0).data(), a.at(0).size(), a.at(1).data(), a.at(1).size(), iv));
#if 0        	
        	std::cout << "Decrypting : " << a.at(0) << "\n"
        					<< "Size       : " << a.at(0).size() << "\n"        	
                  << "With Key   : " << a.at(1) << "\n"
                  << "Init Vec   : " << iv << "\n"
                  << "Becomes    : " << d << "\n";
#endif                  
			if (WriteFile(argv[2], (char*)output, inFileLen, 0)) {
				printf("Decrypt %s success\n", argv[3]);
			}
      } else {
      	printf("Unknown parameter(%s)!!!\n", argv[1]);
      	exit(0);
      }
    
    }
    std::cout.flush();
    	
   	if (input)   free(input);
   	if (output)  free(output);
   	if (key)	 free(key);
   	if (iv)		 free(iv);

    return 0;
}

