#include <fc/pke.hpp>
#include <fc/error.hpp>
#include <fc/exception.hpp>
#include <fc/raw.hpp>
#include <fc/value.hpp>
#include <fc/value_cast.hpp>
#include <fc/base58.hpp>
#include <iostream>
#include <fc/sha1.hpp>
#include <openssl/rsa.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <openssl/pem.h>
#include <openssl/err.h>

namespace fc {
    void pack( fc::value& v, const fc::public_key_t& s ) {
        fc::vector<char> ve = fc::raw::pack( s );
        v = to_base58( ve.data(), size_t(ve.size()) );
    }
    void unpack( const fc::value& v, fc::public_key_t& s ) {
      try {
        auto ve = from_base58(fc::value_cast<fc::string>(v));
        s = fc::raw::unpack<public_key_t>(ve);
      } catch ( ... ) {
        wlog( "error unpacking signature" );
      }
    }

    void pack( fc::value& v, const fc::private_key_t& s ) {
        fc::vector<char> ve = fc::raw::pack( s );
        v = to_base58( ve.data(), ve.size() );
    }
    void unpack( const fc::value& v, fc::private_key_t& s ) {
      try {
        auto ve = from_base58(fc::value_cast<fc::string>(v));
        s = fc::raw::unpack<private_key_t>(ve);
      } catch ( ... ) {
        wlog( "error unpacking private_key" );
      }
    }
    void pack( fc::value& v, const fc::signature_t& s ) {
        fc::vector<char> ve = fc::raw::pack( s );
        v = to_base58( ve.data(), ve.size() );
    }
    void unpack( const fc::value& v, fc::signature_t& s ) {
      try {
        auto ve = from_base58(fc::value_cast<fc::string>(v));
        s = fc::raw::unpack<signature_t>(ve);
      } catch ( ... ) {
        wlog( "error unpacking signature" );
      }
    }

    RSA* get_pub( const char* key, uint32_t key_size, uint32_t pe )
    {
        RSA* rsa = RSA_new();
        rsa->n = BN_bin2bn( (unsigned char*)key, key_size, NULL );
        rsa->e = BN_new();
        BN_set_word(rsa->e, pe );
        return rsa;
    }
    RSA* get_priv( const fc::vector<char>& d, uint32_t /*key_size*/, uint32_t /*pe*/ )
    {
        BIO* mem = (BIO*)BIO_new_mem_buf( (void*)&d.front(), d.size() );
        RSA* rsa = PEM_read_bio_RSAPrivateKey(mem, NULL, NULL, NULL );
        BIO_free(mem);
        return rsa;
    }

    bool verify_data( const char* key, uint32_t key_size, uint32_t pe, const sha1& digest, const char* sig )
    {
        RSA* pub = get_pub( key,key_size,pe);
        auto v = RSA_verify( NID_sha1, (const uint8_t*)digest.data(), 20, (uint8_t*)sig, key_size, pub );
        RSA_free(pub);
        return 0 != v;
    }
    bool sign_data( const fc::vector<char>& key, uint32_t key_size, uint32_t pe, const sha1& digest, char* sig )
    {
        RSA* priv = get_priv( key,key_size,pe);
        if( !priv ) {
            generic_exception g(fc::generic_exception("Error loading private key:  " +  fc::string(ERR_error_string( ERR_get_error(),NULL))) );
		        FC_THROW(g);
        }
        uint32_t slen = 0;
        if( 1 != RSA_sign( NID_sha1, (uint8_t*)digest.data(), sizeof(digest), (unsigned char*)sig, &slen, priv ) )
        {
            RSA_free(priv);
            generic_exception g(fc::generic_exception("Error signing data: " +  fc::string(ERR_error_string( ERR_get_error(),NULL))) );
		        FC_THROW(g);
                                                
        }
        RSA_free(priv);
        return true;
    }

    bool public_encrypt( const char* key, uint32_t key_size, uint32_t pe, const fc::vector<char>& in, fc::vector<char>& out )
    {
        RSA* pub = get_pub( key,key_size/8,pe);
        out.resize(RSA_size(pub));
        int rtn = RSA_public_encrypt( in.size(), (unsigned char*)&in.front(), (unsigned char*)&out.front(), pub, RSA_PKCS1_OAEP_PADDING );
        RSA_free(pub);
        if( rtn >= 0 )
        {
            out.resize(rtn);
            return true;
        }
        out.resize(0);
        FC_THROW( fc::generic_exception( ERR_error_string( ERR_get_error(), NULL ) ) );
        return false;
    }
    bool public_decrypt( const char* key, uint32_t key_size, uint32_t pe, const fc::vector<char>& in, fc::vector<char>& out )
    {
        RSA* pub = get_pub( key,key_size/8,pe);
        out.resize(RSA_size(pub));
        int rtn = RSA_public_decrypt( RSA_size(pub), (unsigned char*)&in.front(), (unsigned char*)&out.front(), pub, RSA_PKCS1_OAEP_PADDING );
        RSA_free(pub);
        if( rtn >= 0 )
        {
            out.resize(rtn);
            return true;
        }
        out.resize(0);
        FC_THROW( fc::generic_exception( ERR_error_string( ERR_get_error(), NULL ) ) );
        return false;;
    }
    bool private_encrypt( const fc::vector<char>& key, uint32_t key_size, uint32_t pe, const fc::vector<char>& in, fc::vector<char>& out )
    {
        RSA* priv = get_priv( key,key_size/8,pe);
        int rtn = RSA_private_encrypt( in.size(), (unsigned char*)&in.front(), (unsigned char*)&out.front(), priv, RSA_PKCS1_OAEP_PADDING );
        RSA_free(priv);
        if( rtn >= 0 )
        {
            out.resize(rtn);
            return true;
        }
        out.resize(0);
        return false;;
    }
    bool private_decrypt( const fc::vector<char>& key, uint32_t key_size, uint32_t pe, const fc::vector<char>& in, fc::vector<char>& out )
    {
        
        RSA* priv = get_priv( key,key_size/8,pe);
        out.resize(RSA_size(priv));
        int rtn = RSA_private_decrypt( in.size(), (unsigned char*)&in.front(), (unsigned char*)&out.front(), priv, RSA_PKCS1_OAEP_PADDING );
        RSA_free(priv);
        if( rtn >= 0 )
        {
            out.resize(rtn);
            return true;
        }
        out.resize(0);
        FC_THROW( fc::generic_exception( ERR_error_string( ERR_get_error(), NULL ) ) );
        return false;
    }

    bool generate_keys( char* pubkey, fc::vector<char>& privkey, uint32_t key_size, uint32_t pe )
    {
        static bool init = true;
        if( init ) { ERR_load_crypto_strings(); init = false; }

        RSA* rsa = RSA_generate_key( key_size, pe, NULL, NULL );
        BN_bn2bin( rsa->n, (unsigned char*)pubkey );

        BIO *mem = BIO_new(BIO_s_mem());
        int e = PEM_write_bio_RSAPrivateKey(mem, rsa,  NULL, NULL, 0, NULL, NULL ); 
        if( e != 1 )
        {
            BIO_free(mem);
            RSA_free(rsa);
		        FC_THROW(generic_exception("Error writing PrivateKey") );
        }

        char* dat;
        uint32_t l = BIO_get_mem_data( mem, &dat );
        privkey.resize(l);
        memcpy( &privkey.front(), dat, l );

        BIO_free(mem);
        RSA_free(rsa);
        return true;
    }
}
