#ifndef FM_DATA_PROCESS_H
#define FM_DATA_PROCESS_H

#include <string>

#include <openssl/evp.h>
#include <openssl/bio.h>  
#include <openssl/buffer.h>  
#include <openssl/rsa.h>  
#include <openssl/pem.h>  
#include <openssl/err.h> 
#include <openssl/des.h>
#include <sstream>

#include <QJsonObject>

#include <QString>

#include <QStringList>

#include <assert.h>

class DataProcess
{
public:
    static unsigned char ToHex(unsigned char x)
    {
        return  x > 9 ? x + 55 : x + 48;
    }

    static unsigned char FromHex(unsigned char x)
    {
        unsigned char y;
        if (x >= 'A' && x <= 'Z') y = x - 'A' + 10;
        else if (x >= 'a' && x <= 'z') y = x - 'a' + 10;
        else if (x >= '0' && x <= '9') y = x - '0';
        else assert(0);
        return y;
    }

    static std::string UrlEncode(const std::string& str)
    {
        std::string strTemp = "";
        size_t length = str.length();
        for (size_t i = 0; i < length; i++)
        {
            if (isalnum((unsigned char)str[i]) ||
                (str[i] == '-') ||
                (str[i] == '_') ||
                (str[i] == '.') ||
                (str[i] == '~'))
                strTemp += str[i];
            else if (str[i] == ' ')
                strTemp += "+";
            else
            {
                strTemp += '%';
                strTemp += ToHex((unsigned char)str[i] >> 4);
                strTemp += ToHex((unsigned char)str[i] % 16);
            }
        }
        return strTemp;
    }

	static int base64(const char *input, size_t length, char *result, size_t size)  
	{  
		BIO * bmem = NULL;  
		BIO * b64 = NULL;  
		BUF_MEM * bptr = NULL; 
		int len = 0;

		if(input == NULL)
			return 0;
		//assert(NULL != input);  
		b64 = BIO_new(BIO_f_base64());  
		bmem = BIO_new(BIO_s_mem());  
		if (NULL == b64 || NULL == bmem) {  
			perror("BIO_new");  
			return 0;  
		}  
		BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);  

		b64 = BIO_push(b64, bmem);  
		BIO_write(b64, input, length);  
		BIO_flush(b64);  
		BIO_get_mem_ptr(b64, &bptr);  

		if ((unsigned int)(bptr->length + 1) > size) {  
			BIO_free_all(b64);  
			return 0;  
		}  
		memcpy(result, bptr->data, bptr->length);  
		result[bptr->length] = 0;  
		len = bptr->length;

		BIO_free_all(b64);  

		return len;  
	}  
	static int debase64(const char *input, size_t length, char *result, size_t size)  
	{  
		BIO * b64 = NULL;  
		BIO * bmem = NULL;
		int len;

		if(input == NULL)
			return 0;
		//assert(NULL != input);  
		if (length > size)  
			return 0;  
		memset(result, 0, size);  

		b64 = BIO_new(BIO_f_base64());  
		bmem = BIO_new_mem_buf((void *)input, length);  
		BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);  
		if (NULL == b64 || NULL == bmem) {  
			perror("BIO_new");  
			return 0;  
		}  
		bmem = BIO_push(b64, bmem);  
		len = BIO_read(bmem, result, length);  

		BIO_free_all(b64);  

		return len;  
	}
	static int RSASign(const char *text, char *signature, size_t size,const char *private_key)  
	{  
		RSA *rsa;  
		unsigned char *sig;  
		unsigned int len;
		unsigned int length = 0;
		BIO* in = NULL; 
        //unsigned char sha1[20] = { '\0' };
        unsigned char sha1[32] = { '\0' };

		char tmpprivatekey[MAX_RSA_KEY_LEN] = {0};

		if(GetPEMKey(private_key, strlen(private_key), tmpprivatekey, MAX_RSA_KEY_LEN, 0) == 0)
			return 0;

		OpenSSL_add_all_algorithms(); 

		in = BIO_new_mem_buf((void*)tmpprivatekey, -1);
		if (in == NULL) {  
			perror("read private failed");  
			return 0;  
		}  

		rsa = PEM_read_bio_RSAPrivateKey(in, NULL, NULL, NULL);  
		if (in != NULL)  
			BIO_free(in);  
		if (rsa == NULL) {  
			perror("PEM_read_bio_RSAPrivateKey");  
			return 0;  
		}  
		if (NULL == (sig = (unsigned char*)malloc(RSA_size(rsa)))) {  
			RSA_free(rsa);  
			return 0;  
		}  

        //SHA1((const unsigned char *)text, strlen(text), sha1);
        SHA256((const unsigned char *)text, strlen(text), sha1);
        if (1 != RSA_sign(NID_sha256, sha1, 32, sig, &len, rsa)) {
			free(sig);  
			RSA_free(rsa);  
			printf("RSA_sign error.\n");  
			return 0;  
		}  

		if ((length= base64((char *)sig, 128, signature, size)) == 0) {  
			free(sig);  
			RSA_free(rsa);  
			printf("base64 error.\n");  
			return 0;  
		}  
		free(sig);  
		RSA_free(rsa);  

		return length;  
	}

	static int RSAVerify(const char *text,const char *signature, const char *public_key)  
	{  
		RSA *rsa;  
		BIO* in = NULL;  
		char * sig_debase = NULL;
        //unsigned char sha1[20];
        unsigned char sha1[32];

		char tmppublickey[MAX_RSA_KEY_LEN] = {0};

		if(GetPEMKey(public_key, strlen(public_key), tmppublickey, MAX_RSA_KEY_LEN, 1) == 0)
			return 0;
		
		in = BIO_new_mem_buf((void*)tmppublickey, -1);
		if (NULL == in) {  
			printf("BIO_read_filename error.\n");  
			return 0;  
		}  

		rsa = PEM_read_bio_RSA_PUBKEY(in, NULL, NULL, NULL);  

		if (in != NULL) BIO_free(in);  
		if (rsa == NULL) {  
			printf("PEM_read_bio_RSA_PUBKEY error.\n");  
			return 0;  
		}  

		sig_debase = (char *)malloc(250 * sizeof(char));  
		if (NULL == debase64(signature, strlen((char *)signature), sig_debase, 250)) {  
			RSA_free(rsa);  
			printf("debase64 error.\n");  
			return 0;  
		}  


        //SHA1((const unsigned char *)text, strlen(text), sha1);
        SHA256((const unsigned char *)text, strlen(text), sha1);
        if (1 != RSA_verify(NID_sha256, sha1, 32, (unsigned char *)sig_debase, 128, rsa)) {
			free(sig_debase);  
			RSA_free(rsa);  
			printf("RSA_verify error.\n");  
			return 0;  
		}  
		free(sig_debase);  
		RSA_free(rsa);  

		return 1;  
	}

    static int AESDecode(const unsigned char *key, unsigned char *iv,const unsigned char *in, int inlen, unsigned char *out, int outmaxlen)
    {
        int iOutLen = 0;
        int iTmpLen = 0;
        int length = 0;
        char tmpiv[16] = { 0 };

        memcpy(tmpiv, iv, 16);

        unsigned char *tmp = (unsigned char *)malloc(outmaxlen * sizeof(unsigned char));

        memset(tmp, 0, outmaxlen);

        char sha1[32] = { '\0' };

        SHA256((const unsigned char *)key, strlen((const char *)key), (unsigned char *)sha1);

        qDebug() << sha1;

        if((length = debase64((const char *)in, inlen, (char *)tmp, outmaxlen)) == 0)
        {
            perror("debase64 failed");
            return 0;
        }

        EVP_CIPHER_CTX ctx;
        EVP_CIPHER_CTX_init(&ctx);
        EVP_DecryptInit_ex(&ctx, EVP_aes_256_cbc(), NULL,(const unsigned char *)sha1, (const unsigned char *)tmpiv);
        if(!EVP_DecryptUpdate(&ctx, (unsigned char*)out, &iOutLen, (const unsigned char *)tmp, length))
        {
            EVP_CIPHER_CTX_cleanup(&ctx);
            return 0;
        }
        if(!EVP_DecryptFinal_ex(&ctx, (unsigned char *)(out + iOutLen), &iTmpLen))
        {
            EVP_CIPHER_CTX_cleanup(&ctx);
            return 0;
        }
        iOutLen += iTmpLen;
        EVP_CIPHER_CTX_cleanup(&ctx);
        out[iOutLen] = 0;

        return iOutLen;
    }

    static int AESEncode(const unsigned char *key, unsigned char *iv,const unsigned char *in, int inlen, unsigned char *out, int outmaxlen)
    {
        int iOutLen = 0;
        int iTmpLen = 0;
        int len;

        char tmpiv[16] = { 0 };

        memcpy(tmpiv, iv, 16);

        unsigned char *tmp = (unsigned char *)malloc(outmaxlen * sizeof(unsigned char));

        memset(tmp, 0, outmaxlen);

        EVP_CIPHER_CTX ctx;
        char sha1[32] = { '\0' };

        SHA256((const unsigned char *)key, strlen((const char *)key), (unsigned char *)sha1);

        qDebug() << sha1;

        QByteArray array(sha1);

        qDebug() << array.toBase64();

        EVP_CIPHER_CTX_init(&ctx);
        EVP_EncryptInit_ex(&ctx, EVP_aes_256_cbc(), NULL, (const unsigned char *)sha1, (const unsigned char *)tmpiv);
        if(!EVP_EncryptUpdate(&ctx, (unsigned char*)tmp, &iOutLen, (const unsigned char *)in, inlen))
        {
            EVP_CIPHER_CTX_cleanup(&ctx);
            return 0;
        }
        if(!EVP_EncryptFinal_ex(&ctx, (unsigned char *)(tmp + iOutLen), &iTmpLen))
        {
            EVP_CIPHER_CTX_cleanup(&ctx);
            return 0;
        }
        iOutLen += iTmpLen;
        EVP_CIPHER_CTX_cleanup(&ctx);

        len = base64((char *)tmp, iOutLen, (char *)out, outmaxlen);

        qDebug() << (char *)out;

        free(tmp);
        return len;
//        AES_KEY aes;
//        int len = 0;

//        char sha1[32] = { '\0' };

//        SHA256((const unsigned char *)key, strlen((const char *)key), (unsigned char *)sha1);

//        unsigned char *tmp = (unsigned char *)malloc(outmaxlen * sizeof(unsigned char));
//        memset(tmp, 0, outmaxlen);

//        qDebug() << (char *)key;
//        qDebug() << (char *)iv;

//        QByteArray array((char *)sha1);

//        qDebug() << array.toBase64();
//        qDebug() << array.data();

//        if(AES_set_encrypt_key((unsigned char *)sha1, 256, &aes) < 0)
//            return 0;

//        AES_cbc_encrypt(in, tmp, inlen, &aes, iv, AES_ENCRYPT);

//        len = base64((char *)tmp, strlen((char *)tmp), (char *)out, outmaxlen);

//        free(tmp);

//        return len;
    }

	static int DES3Encode(const unsigned char *key,const char *in, int inlen, unsigned char *out, int outmaxlen)
	{
		int iOutLen = 0;
		int iTmpLen = 0;
		int len;
		char iv[KEY_SIZE] = {0}; 
		unsigned char *tmp = (unsigned char *)malloc(outmaxlen * sizeof(unsigned char));

	    memset(tmp, 0, outmaxlen);

        EVP_CIPHER_CTX ctx;
		EVP_CIPHER_CTX_init(&ctx);    
        EVP_EncryptInit_ex(&ctx, EVP_des_ede3_ecb(), NULL, (const unsigned char *)key, (const unsigned char *)iv);
		if(!EVP_EncryptUpdate(&ctx, (unsigned char*)tmp, &iOutLen, (const unsigned char *)in, inlen))    
		{        
			EVP_CIPHER_CTX_cleanup(&ctx);        
			return 0;    
        }
		if(!EVP_EncryptFinal_ex(&ctx, (unsigned char *)(tmp + iOutLen), &iTmpLen))    
		{        
			EVP_CIPHER_CTX_cleanup(&ctx);        
			return 0;    
		}        
		iOutLen += iTmpLen;      
		EVP_CIPHER_CTX_cleanup(&ctx);
		
		len = base64((char *)tmp, iOutLen, (char *)out, outmaxlen);
		
		free(tmp);
		return len;
	}
	static int DES3Decode(const unsigned char *key,const char * in, int inlen, unsigned char *out, int outmaxlen)
	{
		int iOutLen = 0;
		int iTmpLen = 0; 
		int length = 0;
		char iv[KEY_SIZE] = {0};
		unsigned char *tmp = (unsigned char *)malloc(outmaxlen * sizeof(unsigned char));

		memset(tmp, 0, outmaxlen);
		
		if((length = debase64(in, inlen, (char *)tmp, outmaxlen)) == 0)
		{
			perror("debase64 failed");
			return 0;
		}
		
        EVP_CIPHER_CTX ctx;
		EVP_CIPHER_CTX_init(&ctx);    
		EVP_DecryptInit_ex(&ctx, EVP_des_ede3_ecb(), NULL, (const unsigned char *)key, (const unsigned char *)iv);   
		if(!EVP_DecryptUpdate(&ctx, (unsigned char*)out, &iOutLen, (const unsigned char *)tmp, length))    
		{        
			EVP_CIPHER_CTX_cleanup(&ctx);        
			return 0;    
		}
		if(!EVP_DecryptFinal_ex(&ctx, (unsigned char *)(out + iOutLen), &iTmpLen))    
		{        
			EVP_CIPHER_CTX_cleanup(&ctx);        
			return 0;    
		}        
		iOutLen += iTmpLen;      
		EVP_CIPHER_CTX_cleanup(&ctx);
		out[iOutLen] = 0;

		return iOutLen;

	}
	static int GetPEMKey(const char *key, int keylen, char *outpemkey, int outpemkeymaxlen, int ispublickey)
	{
		std::stringstream sstm;
		std::string str(key, keylen);

		if (keylen > outpemkeymaxlen)
		{
			return 0;
		}

		
		if(ispublickey == 0)
		{
			int i = 0;


			sstm << "-----BEGIN RSA PRIVATE KEY-----\n";

			while(i + 64 < keylen)
			{
				sstm << str.substr(i, 64) << "\n";
				i += 64;
			}
			if(keylen - i > 0)
				sstm << str.substr(i, keylen - i) << "\n";
			sstm << "-----END RSA PRIVATE KEY-----\n";
		}
		else
		{
			int i = 0;
			sstm << "-----BEGIN PUBLIC KEY-----\n";

			while(i + 64 < keylen)
			{
				sstm << str.substr(i, 64) << "\n";
				i += 64;
			}
			if(keylen - i > 0)
				sstm << str.substr(i, keylen - i) << "\n";
			sstm << "-----END PUBLIC KEY-----\n";
		}

		if(strlen(sstm.str().c_str()) > outpemkeymaxlen)
			return 0;

		strcpy(outpemkey, sstm.str().c_str());
		return 1;
	}

    static void GetJsonKeyArray(QStringList &keys, QJsonObject &json)
	{
        QJsonObject::Iterator it;

        keys.clear();

        for(it = json.begin(); it != json.end(); ++it)
        {
            if(it.value().isString() || it.value().isDouble())
                keys.append(it.key());
        }
	}
    static void SortString(QStringList &keys)
    {
        keys.sort();
	}
    static void GetValueFromJson(const QStringList &keys, const QJsonObject &json, QString &values)
	{
        values.clear();

        for(int i = 0; i < keys.length(); ++i)
        {
            if(json[keys[i]].isString())
            {
                values.append(json[keys[i]].toString());
                values.append("|");
            }
            else if(json[keys[i]].isDouble())
            {
                values.append(QString::number(json[keys[i]].toInt()));
                values.append("|");
            }
        }

        values = values.left(values.length() - 1);

        qDebug() << values;
	}
};

#endif
