#ifndef ALGORITHM_PROCESS_H
#define ALGORITHM_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 "preDefine.h"
#include "QsLog.h"
#include <QJsonObject>
#include <QString>
#include <QStringList>

class AlgorithmProcess
{
public:

    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;

        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;

        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 RSASignWithSHA1(const char *text, char *signature, size_t size,const char *private_key)
    {
        unsigned char *sig;
        unsigned int len;
        unsigned int length = 0;
        unsigned char sha1Abstract[21] = { 0 };
        RSA *rsa;
        BIO* mem_bio = NULL;
        char tmpPrivateKey[MAX_RSA_KEY_LEN] = {0};

        // 重新组装 PEM 格式的私钥格式;
        if(GetPEMFormatKey(private_key, strlen(private_key), tmpPrivateKey, MAX_RSA_KEY_LEN, 0) == 0) {
            QLOG_ERROR()<<QString("RSASignWithSHA1 GetPEMKey()  Failed");
            return 0;
        }
        // QLOG_INFO()<<"RSASignWithSHA1 GetPEMFormatKey(): "<< tmpPrivateKey<< "\n";
        QLOG_INFO()<<"RSASignWithSHA1 GetPEMFormatKey(),  Finished ------------------------ ";

        OpenSSL_add_all_algorithms();

        mem_bio = BIO_new_mem_buf((void*)tmpPrivateKey, -1);
        if (mem_bio == NULL) {
            QLOG_ERROR()<<QString("RSASignWithSHA1 BIO_new_mem_buf  Failed");
            return 0;
        }

        rsa = PEM_read_bio_RSAPrivateKey(mem_bio, NULL, NULL, NULL);

        if (mem_bio != NULL)
            BIO_free(mem_bio);

        if (rsa == NULL) {
            QLOG_ERROR()<<QString("RSASignWithSHA1 PEM_read_bio_RSAPrivateKey  Failed");
            return 0;
        }

        if ( NULL == ( sig = (unsigned char*)malloc( RSA_size(rsa) )  )  ) {
            RSA_free(rsa);
            QLOG_ERROR()<<QString("RSASignWithSHA1 Malloc RSA_size(rsa) ) Memeory Failed");
            return 0;
        }
        QLOG_INFO()<<"RSASignWithSHA1  Malloc RSA_size(rsa) ) Memeory Success,  sig size: "<< RSA_size(rsa);

        // 先将待加签的明文 进行摘要算法，生成固定长度摘要(作为待加签固定长度的密文)，便于后续的加签
        // SHA1  摘要出来的 Mod 长度160个Bit 位, 需20个字节存储;  SHA256 摘要的是 256 需32个字节存储;
        SHA1( (const unsigned char *)text, strlen(text), sha1Abstract );
        QLOG_INFO()<<"RSASignWithSHA1  SHA1() Success,  textLen: "<< strlen(text) << " text: "<<QString( QLatin1String(text)  )
                  << "sha1Abstract (Hex): "<< QByteArray( (char *)sha1Abstract ).toHex();

        // 将摘要后的密文进行加签;
        if (1 != RSA_sign( NID_sha1, sha1Abstract, 20, sig, &len, rsa ) ) {
            free(sig);
            RSA_free(rsa);
            QLOG_ERROR()<<QString("RSASignWithSHA1  RSA_sign( NID_sha1 ,*...)  Failed");
            return 0;
        }
        QLOG_INFO()<<"RSASignWithSHA1 RSA_sign() Success,  len: "<< len << "  sig (Hex): "<< QByteArray( (char *)sig ).toHex()
                  << "  sig (base64): "<< QByteArray( (char *)sig ).toBase64();

        // 将加签后的签名, 按 base64 格式 输出 128 个 bit / bytes 长度到 传出参数 signature;
        if ( ( length = base64( (char *)sig, 128, signature, size) ) == 0 ) {
            free(sig);
            RSA_free(rsa);
            printf("RSASignWithSHA1 base64 error.\n");
            QLOG_ERROR()<<QString("RSASignWithSHA1  base64(sig, ...)  Error ");
            return 0;
        }

        free(sig);
        RSA_free(rsa);
        QLOG_INFO()<<"RSASignWithSHA1 All Success, base64() -> length: "<< length << " signature: "<< signature << " size: "<<size;
        return length;
    }


    static int RSASignWithSHA256(const char *text, char *signature, size_t size,const char *private_key)
    {
        // 拷贝UppClient ,内容待调整， 2021-06-02  wuyang.zou;
        RSA *rsa = RSA_new();
        unsigned char *sig;
        unsigned int len;
        unsigned int length = 0;
        BIO* in = NULL;
        unsigned char sha256Abstract[33] = { '\0' };
        char tmpprivatekey[MAX_RSA_KEY_LEN] = {0};

        if(GetPEMFormatKey(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, &rsa, 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;
        }

        SHA256( (const unsigned char *)text, strlen(text), sha256Abstract );

        if (1 != RSA_sign( NID_sha256, sha256Abstract, 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 RSAVerifyWithSHA1(const char *text,const char *signature, const char *public_key)
    {
        RSA *rsa;
        BIO* in = NULL;
        char * sig_debase = NULL;
        unsigned char sha1Abstract[21] = {0};
        char tmppublickey[MAX_RSA_KEY_LEN] = {0};

        if(GetPEMFormatKey(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), sha1Abstract);

        if (1 != RSA_verify(NID_sha1, sha1Abstract, 20, (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 RSAVerifyWithSHA256(const char *text,const char *signature, const char *public_key)
    {
        // 拷贝UppClient ,内容待调整， 2021-06-02  wuyang.zou;
        RSA *rsa;
        BIO* in = NULL;
        char * sig_debase = NULL;
        unsigned char sha256Abstract[33] = {0};
        char tmppublickey[MAX_RSA_KEY_LEN] = {0};

        if(GetPEMFormatKey(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;
        }

        SHA256((const unsigned char *)text, strlen(text), sha256Abstract);

        if (1 != RSA_verify(NID_sha256, sha256Abstract, 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 *secretKey, unsigned char *secretIv,const unsigned char *inBytes, int inLen, unsigned char *outBytes, int outMaxlen)
    {
        int iOutLen = 0;
        int tmpDecryptLen = 0;
        int base64Length = 0;

        // 拷贝秘钥 key , 使用新内存地址;
        char tmpSecretKey[25] = { 0 };  // 最后一位\0 进行字符数组截断;
        memcpy(tmpSecretKey, secretKey, 24);

        // 拷贝秘钥偏移量  Ivp , 使用新内存地址;
        char tmpSecretIv[17] = { 0 };  // 最后一位\0 进行字符数组截断;
        memcpy(tmpSecretIv, secretIv, 16);

        // 准备存储解密结果的 Buf;
        unsigned char *tmpRst = (unsigned char *)malloc(outMaxlen * sizeof(unsigned char) );
        memset(tmpRst, 0, outMaxlen);

        /*
        //哈希算法(加盐算法) :将明文秘钥进行一次哈希摘要算法，将摘要结果充当 真正的秘钥;
        char sha1[32] = { '\0' };
        SHA256((const unsigned char *)secretKey, strlen((const char *)secretKey), (unsigned char *)sha1);
        */

        // Get 密文 debase64 的长度, 同时 临时存储解密前报文;
        if( ( base64Length = debase64( (const char *)inBytes, inLen, (char *)tmpRst, outMaxlen) ) == 0) {
            perror("debase64 failed");
            QLOG_ERROR()<< " AESDecode debase64() Failed   inLen: "<< inLen << " tmpRstLen: " << strlen( (char *)tmpRst ) << "  outMaxlen: "<<outMaxlen;
            return 0;
        }
        QLOG_INFO()<< " AESDecode debase64() Success   inLen: "<< inLen << "  tmpRstLen: " << strlen( (char *)tmpRst ) << "  outMaxlen: "<<outMaxlen
                   << "  base64Length: " <<base64Length << "  inBytes: "<<QString(QLatin1String( (char *)inBytes))
                   <<" tmpRst (Hex): "<< QByteArray( (char *)tmpRst ).toHex(); // <<" tmpRst (Base64): "<< QByteArray( (char *)tmpRst ).toBase64();

        // 初始化......
        EVP_CIPHER_CTX ctx;
        EVP_CIPHER_CTX_init(&ctx);

        //使用 秘钥 + 秘钥偏移量 进行**解密初始化
        // 24 长度秘钥 对应的算法是  ->  EVP_aes_192_cbc()  [提示偏移量最少24字节长度];   32 长度秘钥 对应的算法是 -> EVP_aes_256_cbc
        // 16 长度秘钥 对应的算法是  ->  EVP_aes_128_cbc
        int decryptInitExRet = EVP_DecryptInit_ex(&ctx, EVP_aes_192_cbc(), NULL, (const unsigned char *)tmpSecretKey, (const unsigned char *)tmpSecretIv);
        if ( 0 == decryptInitExRet ) {
            QLOG_ERROR()<< " AESDecode  EVP_DecryptInit_ex()  Failed, base64Length: " <<base64Length;
            return 0;
        }
        QLOG_INFO()<< " AESDecode EVP_DecryptInit_ex()  Success   secretKey: "<< tmpSecretKey << " SecretIv: " << tmpSecretIv;

        // 解密操作......
        if(!EVP_DecryptUpdate(&ctx, (unsigned char*)outBytes, &iOutLen, tmpRst, base64Length))
        {
            EVP_CIPHER_CTX_cleanup(&ctx);
            QLOG_ERROR()<< " AESDecode  EVP_DecryptUpdate()  Failed ,  iOutLen: "<<iOutLen << "  base64Length: "<< base64Length;
            return 0;
        }
        QLOG_INFO()<< " AESDecode EVP_DecryptUpdate()  Success " << " iOutLen: " << iOutLen; // <<" outBytes: "<<QString(QLatin1String( (char *)outBytes));

        // 完成解密......
        if(!EVP_DecryptFinal_ex(&ctx, (unsigned char *)(outBytes + iOutLen), &tmpDecryptLen))
        {
            EVP_CIPHER_CTX_cleanup(&ctx);
            QLOG_ERROR()<< " AESDecode  EVP_DecryptFinal_ex()  Failed ,outBytes+iOutLen: "<< (outBytes+iOutLen) <<"  tmpDecryptLen: "
                        << tmpDecryptLen << " base64Length: " << base64Length << " iOutLen: " << iOutLen;
            return 0;
        }
        QLOG_INFO()<< " AESDecode EVP_DecryptFinal_ex() Success   outBytes+iOutLen: "<< QString( (char*)(outBytes+iOutLen) )
                   << " tmpDecryptLen: " << tmpDecryptLen; // << " outBytes: " <<QString(QLatin1String( (char *)outBytes));

        EVP_CIPHER_CTX_cleanup(&ctx);

        iOutLen += tmpDecryptLen;
        outBytes[iOutLen] = 0;

        return iOutLen;
    }


    static int AESEncode(const unsigned char *secretKey, unsigned char *secretIv,const unsigned char *inBytes, int inLen, unsigned char *outBytes, int outMaxlen)
    {
        int iOutLen = 0;
        int tmpEncryptLen = 0;
        int base64Length;

        // 拷贝秘钥偏移量  Ivp , 使用新内存地址;
        char tmpSecretIv[17] = { 0 };  // 最后一位\0 进行字符数组截断;
        memcpy(tmpSecretIv, secretIv, 16);

        // 准备存储加密结果的 Buf;
        unsigned char *tmpRst = (unsigned char *)malloc(outMaxlen * sizeof(unsigned char) );
        memset(tmpRst, 0, outMaxlen);

        /*
        //哈希算法(加盐算法) :将明文秘钥进行一次哈希摘要算法，将摘要结果充当 真正的秘钥;
        char sha1[32] = { '\0' };
        SHA256((const unsigned char *)key, strlen((const char *)key), (unsigned char *)sha1);
        qDebug() << sha1;
        */

        // 初始化......
        EVP_CIPHER_CTX ctx;
        EVP_CIPHER_CTX_init(&ctx);

        //使用 秘钥 + 秘钥偏移量 进行**加密初始化
        // 24 长度秘钥 对应的算法是  ->  EVP_aes_192_cbc()  [提示偏移量最少24字节长度];   32 长度秘钥 对应的算法是 -> EVP_aes_256_cbc
        // 16 长度秘钥 对应的算法是  ->  EVP_aes_128_cbc
        int encryptInitExRet = EVP_EncryptInit_ex(&ctx, EVP_aes_192_cbc(), NULL, secretKey, secretIv);
        if ( 0 == encryptInitExRet ) {
            QLOG_ERROR()<< " AESEncode  EVP_EncryptInit_ex  Failed ";
            return 0;
        }

        // 加密操作......
        if(!EVP_EncryptUpdate(&ctx, (unsigned char*)tmpRst, &iOutLen, (const unsigned char *)inBytes, inLen))
        {
            EVP_CIPHER_CTX_cleanup(&ctx);
            QLOG_ERROR()<< " AESEncode  EVP_EncryptUpdate  Failed ,  iOutLen: "<<iOutLen;
            return 0;
        }

        // 完成加密(处理尾部padding)......
        if(!EVP_EncryptFinal_ex(&ctx, (unsigned char *)(tmpRst + iOutLen), &tmpEncryptLen))
        {
            EVP_CIPHER_CTX_cleanup(&ctx);
            QLOG_ERROR()<< " AESEncode  EVP_EncryptFinal_ex  Failed , tmpDecryptLen: "<< tmpEncryptLen;
            return 0;
        }

        iOutLen += tmpEncryptLen;
        EVP_CIPHER_CTX_cleanup(&ctx);

        // 生成密文 debase64 的长度, 同时 临时存储加密后报文;
        if ( base64Length = base64((char *)tmpRst, iOutLen, (char *)outBytes, outMaxlen) ) {
            perror("base64 failed");
            QLOG_ERROR()<< " AESEncode base64 Failed   inLen: "<< inLen << " tmpRstLen: " << sizeof(tmpRst) << "  outMaxlen: "<<outMaxlen;
            return 0;
        }
        qDebug() << (char *)outBytes;

        free(tmpRst);
        return base64Length;
    }


    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[DES3_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[DES3_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 GetPEMFormatKey(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 + 16 < keylen) {
                sstm << str.substr(i, 16) << "\n";
                i += 16;
            }

            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 + 16 < keylen) {
                sstm << str.substr(i, 16) << "\n";
                i += 16;
            }

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

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

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


    static void GetKeyListFromJson(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) {
        if( keys.contains(JSON_TCP_SIGN) ) {
            keys.removeOne(JSON_KEY_SIGN);
        }
        keys.sort();
    }


    static void GetValueListFromJson(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;
    }


    static void GetKeyValueListFromJson(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( keys[i] );
                values.append( "=" );
                values.append(json[keys[i]].toString());
                values.append(":");

            } else if ( json[keys[i]].isDouble() ) {

                values.append( keys[i] );
                values.append( "=" );
                values.append( QString::number( json[keys[i]].toInt() ) );
                values.append(":");
            }
        }

        values = values.left(values.length() - 1);
        qDebug() << values;
    }

};

#endif  //ALGORITHM_PROCESS_H
