// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

// SEALNet
#include "seal/c/ciphertext.h"
#include "seal/c/utilities.h"

// SEAL
#include "seal/ciphertext.h"

using namespace std;
using namespace seal;
using namespace seal::c;

// Enables access to private members of seal::Ciphertext.
using ph = struct seal::Ciphertext::CiphertextPrivateHelper
{
    static void resize(Ciphertext *ciphertext, size_t size, size_t poly_modulus_degree, size_t coeff_modulus_size)
    {
        ciphertext->resize_internal(size, poly_modulus_degree, coeff_modulus_size);
    }

    static void set_ntt_form(Ciphertext *ciphertext, bool is_ntt_form)
    {
        ciphertext->is_ntt_form_ = is_ntt_form;
    }
};

SEAL_C_FUNC Ciphertext_Create1(void *memoryPoolHandle, void **ciphertext)
{
    IfNullRet(ciphertext, E_POINTER);
    unique_ptr<MemoryPoolHandle> handle = MemHandleFromVoid(memoryPoolHandle);

    try
    {
        Ciphertext *cipher = new Ciphertext(*handle);
        *ciphertext = cipher;
        return S_OK;
    }
    catch (const invalid_argument &)
    {
        return E_INVALIDARG;
    }
}

SEAL_C_FUNC Ciphertext_Create2(void *copy, void **ciphertext)
{
    Ciphertext *copyptr = FromVoid<Ciphertext>(copy);
    IfNullRet(copyptr, E_POINTER);
    IfNullRet(ciphertext, E_POINTER);

    Ciphertext *cipher = new Ciphertext(*copyptr);
    *ciphertext = cipher;
    return S_OK;
}

SEAL_C_FUNC Ciphertext_Create3(void *context, void *pool, void **ciphertext)
{
    const SEALContext *ctx = FromVoid<SEALContext>(context);
    IfNullRet(ctx, E_POINTER);
    IfNullRet(ciphertext, E_POINTER);
    unique_ptr<MemoryPoolHandle> pool_ptr = MemHandleFromVoid(pool);

    try
    {
        Ciphertext *cipher = new Ciphertext(*ctx, *pool_ptr);
        *ciphertext = cipher;
        return S_OK;
    }
    catch (const invalid_argument &)
    {
        return E_INVALIDARG;
    }
}

SEAL_C_FUNC Ciphertext_Create4(void *context, uint64_t *parms_id, void *pool, void **ciphertext)
{
    const SEALContext *ctx = FromVoid<SEALContext>(context);
    IfNullRet(ctx, E_POINTER);
    IfNullRet(parms_id, E_POINTER);
    IfNullRet(ciphertext, E_POINTER);
    unique_ptr<MemoryPoolHandle> pool_ptr = MemHandleFromVoid(pool);

    try
    {
        parms_id_type parmsid;
        CopyParmsId(parms_id, parmsid);

        Ciphertext *cipher = new Ciphertext(*ctx, parmsid, *pool_ptr);
        *ciphertext = cipher;
        return S_OK;
    }
    catch (const invalid_argument &)
    {
        return E_INVALIDARG;
    }
}

SEAL_C_FUNC Ciphertext_Create5(void *context, uint64_t *parms_id, uint64_t capacity, void *pool, void **ciphertext)
{
    const SEALContext *ctx = FromVoid<SEALContext>(context);
    IfNullRet(ctx, E_POINTER);
    IfNullRet(parms_id, E_POINTER);
    IfNullRet(ciphertext, E_POINTER);
    unique_ptr<MemoryPoolHandle> pool_ptr = MemHandleFromVoid(pool);

    try
    {
        parms_id_type parmsid;
        CopyParmsId(parms_id, parmsid);

        Ciphertext *cipher = new Ciphertext(*ctx, parmsid, capacity, *pool_ptr);
        *ciphertext = cipher;
        return S_OK;
    }
    catch (const invalid_argument &)
    {
        return E_INVALIDARG;
    }
}

SEAL_C_FUNC Ciphertext_Reserve1(void *thisptr, void *context, uint64_t *parms_id, uint64_t size_capacity)
{
    const SEALContext *ctx = FromVoid<SEALContext>(context);
    IfNullRet(ctx, E_POINTER);
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(parms_id, E_POINTER);

    parms_id_type parms;
    CopyParmsId(parms_id, parms);

    try
    {
        cipher->reserve(*ctx, parms, size_capacity);
        return S_OK;
    }
    catch (const invalid_argument &)
    {
        return E_INVALIDARG;
    }
}

SEAL_C_FUNC Ciphertext_Reserve2(void *thisptr, void *context, uint64_t size_capacity)
{
    const SEALContext *ctx = FromVoid<SEALContext>(context);
    IfNullRet(ctx, E_POINTER);
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);

    try
    {
        cipher->reserve(*ctx, size_capacity);
        return S_OK;
    }
    catch (const invalid_argument &)
    {
        return E_INVALIDARG;
    }
}

SEAL_C_FUNC Ciphertext_Reserve3(void *thisptr, uint64_t size_capacity)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);

    try
    {
        cipher->reserve(size_capacity);
        return S_OK;
    }
    catch (const invalid_argument &)
    {
        return E_INVALIDARG;
    }
}

SEAL_C_FUNC Ciphertext_Set(void *thisptr, void *assign)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    Ciphertext *assignptr = FromVoid<Ciphertext>(assign);
    IfNullRet(assignptr, E_POINTER);

    *cipher = *assignptr;
    return S_OK;
}

SEAL_C_FUNC Ciphertext_Destroy(void *thisptr)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);

    delete cipher;
    return S_OK;
}

SEAL_C_FUNC Ciphertext_Size(void *thisptr, uint64_t *size)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(size, E_POINTER);

    *size = cipher->size();
    return S_OK;
}

SEAL_C_FUNC Ciphertext_SizeCapacity(void *thisptr, uint64_t *size_capacity)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(size_capacity, E_POINTER);

    *size_capacity = cipher->size_capacity();
    return S_OK;
}

SEAL_C_FUNC Ciphertext_PolyModulusDegree(void *thisptr, uint64_t *poly_modulus_degree)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(poly_modulus_degree, E_POINTER);

    *poly_modulus_degree = cipher->poly_modulus_degree();
    return S_OK;
}

SEAL_C_FUNC Ciphertext_CoeffModulusSize(void *thisptr, uint64_t *coeff_modulus_size)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(coeff_modulus_size, E_POINTER);

    *coeff_modulus_size = cipher->coeff_modulus_size();
    return S_OK;
}

SEAL_C_FUNC Ciphertext_ParmsId(void *thisptr, uint64_t *parms_id)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(parms_id, E_POINTER);

    CopyParmsId(cipher->parms_id(), parms_id);
    return S_OK;
}

SEAL_C_FUNC Ciphertext_SetParmsId(void *thisptr, uint64_t *parms_id)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(parms_id, E_POINTER);

    CopyParmsId(parms_id, cipher->parms_id());
    return S_OK;
}

SEAL_C_FUNC Ciphertext_Resize1(void *thisptr, void *context, uint64_t *parms_id, uint64_t size)
{
    const SEALContext *ctx = FromVoid<SEALContext>(context);
    IfNullRet(ctx, E_POINTER);
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);

    parms_id_type parms;
    CopyParmsId(parms_id, parms);

    try
    {
        cipher->resize(*ctx, parms, size);
        return S_OK;
    }
    catch (const invalid_argument &)
    {
        return E_INVALIDARG;
    }
}

SEAL_C_FUNC Ciphertext_Resize2(void *thisptr, void *context, uint64_t size)
{
    const SEALContext *ctx = FromVoid<SEALContext>(context);
    IfNullRet(ctx, E_POINTER);
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);

    try
    {
        cipher->resize(*ctx, size);
        return S_OK;
    }
    catch (const invalid_argument &)
    {
        return E_INVALIDARG;
    }
}

SEAL_C_FUNC Ciphertext_Resize3(void *thisptr, uint64_t size)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);

    try
    {
        cipher->resize(size);
        return S_OK;
    }
    catch (const invalid_argument &)
    {
        return E_INVALIDARG;
    }
}

SEAL_C_FUNC Ciphertext_Resize4(void *thisptr, uint64_t size, uint64_t polyModulusDegree, uint64_t coeffModCount)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);

    try
    {
        ph::resize(cipher, size, polyModulusDegree, coeffModCount);
        return S_OK;
    }
    catch (const invalid_argument &)
    {
        return E_INVALIDARG;
    }
}

SEAL_C_FUNC Ciphertext_GetDataAt1(void *thisptr, uint64_t index, uint64_t *data)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(data, E_POINTER);

    try
    {
        *data = (*cipher)[index];
        return S_OK;
    }
    catch (const out_of_range &)
    {
        return HRESULT_FROM_WIN32(ERROR_INVALID_INDEX);
    }
}

SEAL_C_FUNC Ciphertext_GetDataAt2(void *thisptr, uint64_t poly_index, uint64_t coeff_index, uint64_t *data)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(data, E_POINTER);

    auto poly_uint64_count = util::mul_safe(cipher->poly_modulus_degree(), cipher->coeff_modulus_size());

    // poly_index is verified by the data method, we need to verify coeff_index ourselves.
    if (coeff_index >= poly_uint64_count)
        return HRESULT_FROM_WIN32(ERROR_INVALID_INDEX);

    try
    {
        *data = cipher->data(poly_index)[coeff_index];
        return S_OK;
    }
    catch (const out_of_range &)
    {
        return HRESULT_FROM_WIN32(ERROR_INVALID_INDEX);
    }
}

SEAL_C_FUNC Ciphertext_SetDataAt(void *thisptr, uint64_t index, uint64_t value)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);

    try
    {
        (*cipher)[index] = value;
        return S_OK;
    }
    catch (const out_of_range &)
    {
        return HRESULT_FROM_WIN32(ERROR_INVALID_INDEX);
    }
}

SEAL_C_FUNC Ciphertext_IsNTTForm(void *thisptr, bool *is_ntt_form)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(is_ntt_form, E_POINTER);

    *is_ntt_form = cipher->is_ntt_form();
    return S_OK;
}

SEAL_C_FUNC Ciphertext_SetIsNTTForm(void *thisptr, bool is_ntt_form)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);

    ph::set_ntt_form(cipher, is_ntt_form);
    return S_OK;
}

SEAL_C_FUNC Ciphertext_Scale(void *thisptr, double *scale)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(scale, E_POINTER);

    *scale = cipher->scale();
    return S_OK;
}

SEAL_C_FUNC Ciphertext_SetScale(void *thisptr, double scale)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);

    cipher->scale() = scale;
    return S_OK;
}

SEAL_C_FUNC Ciphertext_CorrectionFactor(void *thisptr, uint64_t *correction_factor)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(correction_factor, E_POINTER);

    *correction_factor = cipher->correction_factor();
    return S_OK;
}

SEAL_C_FUNC Ciphertext_SetCorrectionFactor(void *thisptr, uint64_t correction_factor)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);

    cipher->correction_factor() = correction_factor;
    return S_OK;
}

SEAL_C_FUNC Ciphertext_Release(void *thisptr)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);

    cipher->release();
    return S_OK;
}

SEAL_C_FUNC Ciphertext_IsTransparent(void *thisptr, bool *result)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(result, E_POINTER);

    *result = cipher->is_transparent();
    return S_OK;
}

SEAL_C_FUNC Ciphertext_Pool(void *thisptr, void **pool)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(pool, E_POINTER);

    MemoryPoolHandle *handleptr = new MemoryPoolHandle(cipher->pool());
    *pool = handleptr;
    return S_OK;
}

SEAL_C_FUNC Ciphertext_SaveSize(void *thisptr, uint8_t compr_mode, int64_t *result)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(result, E_POINTER);

    try
    {
        *result = static_cast<int64_t>(cipher->save_size(static_cast<compr_mode_type>(compr_mode)));
        return S_OK;
    }
    catch (const invalid_argument &)
    {
        return E_INVALIDARG;
    }
    catch (const logic_error &)
    {
        return COR_E_INVALIDOPERATION;
    }
}

SEAL_C_FUNC Ciphertext_Save(void *thisptr, uint8_t *outptr, uint64_t size, uint8_t compr_mode, int64_t *out_bytes)
{
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(outptr, E_POINTER);
    IfNullRet(out_bytes, E_POINTER);

    try
    {
        *out_bytes = util::safe_cast<int64_t>(cipher->save(
            reinterpret_cast<seal_byte *>(outptr), util::safe_cast<size_t>(size),
            static_cast<compr_mode_type>(compr_mode)));
        return S_OK;
    }
    catch (const invalid_argument &)
    {
        return E_INVALIDARG;
    }
    catch (const logic_error &)
    {
        return COR_E_INVALIDOPERATION;
    }
    catch (const runtime_error &)
    {
        return COR_E_IO;
    }
}

SEAL_C_FUNC Ciphertext_UnsafeLoad(void *thisptr, void *context, uint8_t *inptr, uint64_t size, int64_t *in_bytes)
{
    const SEALContext *ctx = FromVoid<SEALContext>(context);
    IfNullRet(ctx, E_POINTER);
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(inptr, E_POINTER);
    IfNullRet(in_bytes, E_POINTER);

    try
    {
        *in_bytes = util::safe_cast<int64_t>(
            cipher->unsafe_load(*ctx, reinterpret_cast<seal_byte *>(inptr), util::safe_cast<size_t>(size)));
        return S_OK;
    }
    catch (const invalid_argument &)
    {
        return E_INVALIDARG;
    }
    catch (const logic_error &)
    {
        return COR_E_INVALIDOPERATION;
    }
    catch (const runtime_error &)
    {
        return COR_E_IO;
    }
}

SEAL_C_FUNC Ciphertext_Load(void *thisptr, void *context, uint8_t *inptr, uint64_t size, int64_t *in_bytes)
{
    const SEALContext *ctx = FromVoid<SEALContext>(context);
    IfNullRet(ctx, E_POINTER);
    Ciphertext *cipher = FromVoid<Ciphertext>(thisptr);
    IfNullRet(cipher, E_POINTER);
    IfNullRet(inptr, E_POINTER);
    IfNullRet(in_bytes, E_POINTER);

    try
    {
        *in_bytes = util::safe_cast<int64_t>(
            cipher->load(*ctx, reinterpret_cast<seal_byte *>(inptr), util::safe_cast<size_t>(size)));
        return S_OK;
    }
    catch (const invalid_argument &)
    {
        return E_INVALIDARG;
    }
    catch (const logic_error &)
    {
        return COR_E_INVALIDOPERATION;
    }
    catch (const runtime_error &)
    {
        return COR_E_IO;
    }
}
