//==================================================================================
// BSD 2-Clause License
//
// Copyright (c) 2014-2022, NJIT, Duality Technologies Inc. and other contributors
//
// All rights reserved.
//
// Author TPOC: contact@openfhe.org
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//==================================================================================

/*
  Implementation file for Boolean Circuit FHE context class
 */

#include "binfhecontext.h"

#include <string>
#include <unordered_map>

static constexpr double STD_DEV = 3.19;

namespace lbcrypto {

void BinFHEContext::GenerateBinFHEContext(uint32_t n, uint32_t N, NativeInteger q, NativeInteger Q,
                                          double std, uint32_t baseKS, uint32_t baseG, uint32_t baseR,
                                          SecretKeyDist keyDist, BINFHE_METHOD method, uint32_t numAutoKeys) {
    auto lweparams = std::make_shared<LWECryptoParams>(n, N, q, Q, Q, std, baseKS);
    auto rgswparams =
        std::make_shared<RingGSWCryptoParams>(N, Q, q, baseG, baseR, method, std, keyDist, true, numAutoKeys);
    m_params       = std::make_shared<BinFHECryptoParams>(lweparams, rgswparams);
    m_binfhescheme = std::make_shared<BinFHEScheme>(method);
}

void BinFHEContext::GenerateBinFHEContext(BINFHE_PARAMSET s, bool arbFunc, uint32_t logQ, uint32_t N,
                                          BINFHE_METHOD method, bool timeOptimization) {
    if (method != GINX)
        OPENFHE_THROW("CGGI is the only supported method");
    if (s != STD128 && s != TOY)
        OPENFHE_THROW("STD128 and TOY are the only supported sets");
    if (logQ > 29)
        OPENFHE_THROW("logQ > 29 is not supported");
    if (logQ < 11)
        OPENFHE_THROW("logQ < 11 is not supported");

    isMethodCompatible(method, s);

    auto logQprime = 54;
    uint32_t baseG = 0;
    if (logQ > 25) {
        baseG = 1 << 14;
    }
    else if (logQ > 16) {
        baseG = 1 << 18;
    }
    else if (logQ > 11) {
        baseG = 1 << 27;
    }
    else {  // if (logQ == 11)
        baseG     = 1 << 5;
        logQprime = 27;
    }

    // choose minimum ringD satisfying sl and Q
    // if specified some larger N, security is also satisfied
    auto minRingDim  = StdLatticeParm::FindRingDim(HEStd_ternary, HEStd_128_classic, logQprime);
    uint32_t ringDim = N > minRingDim ? N : minRingDim;

    // find prime Q for NTT
    NativeInteger Q = LastPrime<NativeInteger>(logQprime, 2 * ringDim);

    // q = 2*ringDim by default for maximum plaintext space, if needed for arbitrary function evaluation, q = ringDim
    uint32_t q = arbFunc ? ringDim : 2 * ringDim;

    uint64_t qKS = uint64_t(1) << 35;

    uint32_t n      = (s == TOY) ? 32 : 1305;
    auto lweparams  = std::make_shared<LWECryptoParams>(n, ringDim, q, Q, qKS, STD_DEV, 32);
    auto rgswparams = std::make_shared<RingGSWCryptoParams>(ringDim, Q, q, baseG, 23, method, STD_DEV, UNIFORM_TERNARY,
                                                            ((logQ != 11) && timeOptimization));

    m_params           = std::make_shared<BinFHECryptoParams>(lweparams, rgswparams);
    m_binfhescheme     = std::make_shared<BinFHEScheme>(method);
    m_timeOptimization = timeOptimization;
}

void BinFHEContext::GenerateBinFHEContext(BINFHE_PARAMSET s, BINFHE_METHOD method) {
    enum { PRIME = 0 };  // value for modKS if you want to use the intermediate prime for modulus for key switching

    isMethodCompatible(method, s);

    // clang-format off
    static const std::unordered_map<BINFHE_PARAMSET, BinFHEContextParams> paramsMap{
    //  { BINFHE_PARAMSET      { bits, cycOrder, latParam, modq,   modKS, Bks,        Bg, Brk, autoKeys,         keyDist, stdDev } },
        { TOY,                 {   27,     1024,       64,  512,   PRIME,  25,       512,  23,        9, UNIFORM_TERNARY,   3.19 } },
        { MEDIUM,              {   28,     2048,      422, 1024,   16384, 128,      1024,  32,       10, UNIFORM_TERNARY,   3.19 } },
        { STD128_AP,           {   27,     2048,      559, 2048,   32768,  32,       512,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD128,              {   27,     2048,      556, 2048,   32768,  32,       128,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD128_3,            {   27,     2048,      595, 2048,   65536,  64,       128,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD128_4,            {   27,     2048,      635, 2048,  131072,  64,        32,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD128Q,             {   25,     2048,      601, 2048,   32768,  32,        16,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD128Q_3,           {   25,     2048,      641, 2048,   65536,  64,        16,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD128Q_4,           {   50,     4096,      683, 4096,  131072,  64,    131072,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD192,              {   37,     4096,      821, 2048,   32768,  32,      8192,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD192_3,            {   37,     4096,      876, 2048,   65536,  64,      8192,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD192_4,            {   37,     4096,      932, 4096,  131072,  64,      8192,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD192Q,             {   34,     4096,      890, 2048,   32768,  32,      4096,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD192Q_3,           {   34,     4096,      948, 2048,   65536,  64,      4096,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD192Q_4,           {   34,     4096,     1009, 4096,  131072,  64,      4096,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD256,              {   29,     4096,     1299, 2048,  262144,  64,      1024,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD256_3,            {   29,     4096,     1241, 2048,  131072,  64,       256,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD256_4,            {   29,     4096,     1218, 4096,  131072,  64,        32,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD256Q,             {   26,     4096,     1242, 2048,   65536,  64,        64,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD256Q_3,           {   26,     4096,     1319, 4096,  131072,  64,        32,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD256Q_4,           {   26,     4096,     1319, 4096,  131072,  64,        16,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD128_LMKCDEY,      {   27,     2048,      581, 1024,   32768,  32,       512,  32,       10, UNIFORM_TERNARY,   3.19 } },
        { STD128_3_LMKCDEY,    {   27,     2048,      595, 2048,   65536,  64,       128,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD128_4_LMKCDEY,    {   27,     2048,      635, 2048,  131072,  64,        64,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD128Q_LMKCDEY,     {   25,     2048,      640, 1024,   32768,  32,       128,  32,       10, UNIFORM_TERNARY,   3.19 } },
        { STD128Q_3_LMKCDEY,   {   25,     2048,      641, 2048,   65536,  64,        16,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD128Q_4_LMKCDEY,   {   25,     2048,      685, 2048,  131072,  64,        16,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD192_LMKCDEY,      {   39,     4096,      716, 4096,   32768,  32,   1048576,  64,       10,        GAUSSIAN,   3.19 } },
        { STD192_3_LMKCDEY,    {   37,     4096,      876, 2048,   65536,  64,      1024,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD192_4_LMKCDEY,    {   37,     4096,      932, 4096,  131072,  64,      1024,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD192Q_LMKCDEY,     {   36,     4096,      778, 4096,   32768,  32,      4096,  64,       10,        GAUSSIAN,   3.19 } },
        { STD192Q_3_LMKCDEY,   {   34,     4096,      948, 2048,   65536,  64,      4096,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD192Q_4_LMKCDEY,   {   34,     4096,     1009, 4096,  131072,  64,      4096,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD256_LMKCDEY,      {   29,     4096,     1079, 2048,   32768,  32,      1024,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD256_3_LMKCDEY,    {   29,     4096,     1218, 2048,  131072,  64,       256,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD256_4_LMKCDEY,    {   29,     4096,     1218, 4096,  131072,  64,       256,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD256Q_LMKCDEY,     {   26,     4096,     1242, 2048,   65536,  64,       128,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD256Q_3_LMKCDEY,   {   26,     4096,     1319, 4096,  131072,  64,        64,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { STD256Q_4_LMKCDEY,   {   26,     4096,     1319, 4096,  131072,  64,        32,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { LPF_STD128,          {   27,     2048,      556, 2048,   32768,  32,       128,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { LPF_STD128Q,         {   25,     2048,      601, 2048,   32768,  32,        16,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { LPF_STD128_LMKCDEY,  {   27,     2048,      556, 2048,   32768,  32,       128,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { LPF_STD128Q_LMKCDEY, {   25,     2048,      601, 2048,   32768,  32,        16,  64,       10, UNIFORM_TERNARY,   3.19 } },
        { SIGNED_MOD_TEST,     {   28,     2048,      512, 1024,   PRIME,  25,       128,  23,       10, UNIFORM_TERNARY,   3.19 } },
    };
    // clang-format on

    auto search = paramsMap.find(s);
    if (paramsMap.end() == search)
        OPENFHE_THROW("unknown parameter set");

    auto& params = search->second;

    auto Q         = LastPrime<NativeInteger>(params.numberBits, params.cyclOrder);
    auto ringDim   = params.cyclOrder >> 1;
    auto lweparams = std::make_shared<LWECryptoParams>(params.latticeParam, ringDim, params.mod, Q,
                                                       (params.modKS == PRIME ? Q : params.modKS), params.stdDev,
                                                       params.baseKS, params.keyDist);
    auto rgswparams =
        std::make_shared<RingGSWCryptoParams>(ringDim, Q, params.mod, params.gadgetBase, params.baseRK, method,
                                              params.stdDev, params.keyDist, false, params.numAutoKeys);
    m_params = std::make_shared<BinFHECryptoParams>(lweparams, rgswparams);

    m_binfhescheme = std::make_shared<BinFHEScheme>(method);
}

void BinFHEContext::GenerateBinFHEContext(const BinFHEContextParams& params, BINFHE_METHOD method) {
    enum { PRIME = 0 };  // value for modKS if you want to use the intermediate prime for modulus for key switching

    auto Q         = LastPrime<NativeInteger>(params.numberBits, params.cyclOrder);
    auto ringDim   = params.cyclOrder >> 1;
    auto lweparams = std::make_shared<LWECryptoParams>(params.latticeParam, ringDim, params.mod, Q,
                                                       (params.modKS == PRIME ? Q : params.modKS), params.stdDev,
                                                       params.baseKS, params.keyDist);
    auto rgswparams =
        std::make_shared<RingGSWCryptoParams>(ringDim, Q, params.mod, params.gadgetBase, params.baseRK, method,
                                              params.stdDev, params.keyDist, false, params.numAutoKeys);
    m_params       = std::make_shared<BinFHECryptoParams>(lweparams, rgswparams);
    m_binfhescheme = std::make_shared<BinFHEScheme>(method);
}

LWEPrivateKey BinFHEContext::KeyGen() const {
    auto&& LWEParams = m_params->GetLWEParams();
    if (LWEParams->GetKeyDist() == GAUSSIAN)
        return m_LWEscheme->KeyGenGaussian(LWEParams->Getn(), LWEParams->GetqKS());
    return m_LWEscheme->KeyGen(LWEParams->Getn(), LWEParams->GetqKS());
}

LWEPrivateKey BinFHEContext::KeyGenN() const {
    auto&& LWEParams = m_params->GetLWEParams();
    if (LWEParams->GetKeyDist() == GAUSSIAN)
        return m_LWEscheme->KeyGenGaussian(LWEParams->GetN(), LWEParams->GetQ());
    return m_LWEscheme->KeyGen(LWEParams->GetN(), LWEParams->GetQ());
}

LWEKeyPair BinFHEContext::KeyGenPair() const {
    return m_LWEscheme->KeyGenPair(m_params->GetLWEParams());
}

LWEPublicKey BinFHEContext::PubKeyGen(ConstLWEPrivateKey& sk) const {
    if (sk == nullptr)
        OPENFHE_THROW("PrivateKey is empty");
    return m_LWEscheme->PubKeyGen(m_params->GetLWEParams(), sk);
}

LWECiphertext BinFHEContext::Encrypt(ConstLWEPrivateKey& sk, LWEPlaintext m, BINFHE_OUTPUT output,
                                     LWEPlaintextModulus p, NativeInteger mod) const {
    if (sk == nullptr)
        OPENFHE_THROW("PrivateKey is empty");
    auto&& LWEParams = m_params->GetLWEParams();
    auto ct          = m_LWEscheme->Encrypt(LWEParams, sk, m, p, (mod == 0 ? LWEParams->Getq() : mod));

    // BINFHE_OUTPUT is kept as it is for backward compatibility but
    // this logic is obsolete now and commented out
    // if ((output != FRESH) && (p == 4)) {
    //    ct = m_binfhescheme->Bootstrap(m_params, m_BTKey, ct);
    //}
    return ct;
}

LWECiphertext BinFHEContext::Encrypt(ConstLWEPublicKey& pk, LWEPlaintext m, BINFHE_OUTPUT output, LWEPlaintextModulus p,
                                     NativeInteger mod) const {
    if (pk == nullptr)
        OPENFHE_THROW("PublicKey is empty");
    auto&& LWEParams = m_params->GetLWEParams();
    auto ct          = m_LWEscheme->EncryptN(LWEParams, pk, m, p, (mod == 0 ? LWEParams->GetQ() : mod));

    // Switch from ct of modulus Q and dimension N to smaller q and n
    // This is done by default while calling Encrypt but the output could
    // be set to LARGE_DIM to skip this switching
    if (output == SMALL_DIM) {
        ct = SwitchCTtoqn(m_BTKey.KSkey, ct);
        ct->SetptModulus(p);
    }
    return ct;
}

LWECiphertext BinFHEContext::SwitchCTtoqn(ConstLWESwitchingKey& ksk, ConstLWECiphertext& ct) const {
    if (ksk == nullptr)
        OPENFHE_THROW("SwitchingKey is empty");
    if (ct == nullptr)
        OPENFHE_THROW("Ciphertext is empty");
    auto&& LWEParams = m_params->GetLWEParams();
    if ((ct->GetLength() != LWEParams->GetN()) && (ct->GetModulus() != LWEParams->GetQ()))
        OPENFHE_THROW("ciphertext dimension and modulus are not large N and Q");
    return m_LWEscheme->SwitchCTtoqn(LWEParams, ksk, ct);
}

void BinFHEContext::Decrypt(ConstLWEPrivateKey& sk, ConstLWECiphertext& ct, LWEPlaintext* result,
                            LWEPlaintextModulus p) const {
    if (sk == nullptr)
        OPENFHE_THROW("PrivateKey is empty");
    if (ct == nullptr)
        OPENFHE_THROW("Ciphertext is empty");
    m_LWEscheme->Decrypt(m_params->GetLWEParams(), sk, ct, result, p);
}

LWESwitchingKey BinFHEContext::KeySwitchGen(ConstLWEPrivateKey& sk, ConstLWEPrivateKey& skN) const {
    if (sk == nullptr)
        OPENFHE_THROW("New PrivateKey is empty");
    if (skN == nullptr)
        OPENFHE_THROW("Old PrivateKey is empty");

    return m_LWEscheme->KeySwitchGen(m_params->GetLWEParams(), sk, skN);
}

void BinFHEContext::BTKeyGen(ConstLWEPrivateKey& sk, KEYGEN_MODE keygenMode) {
    if (sk == nullptr)
        OPENFHE_THROW("PrivateKey is empty");
    auto&& RGSWParams = m_params->GetRingGSWParams();
    auto temp         = RGSWParams->GetBaseG();

    if (m_timeOptimization) {
        for (auto&& [k, v] : RGSWParams->GetGPowerMap()) {
            RGSWParams->Change_BaseG(k);
            m_BTKey_map[k] = m_binfhescheme->KeyGen(m_params, sk, keygenMode);
        }
        RGSWParams->Change_BaseG(temp);
    }

    if (m_BTKey_map.size() != 0) {
        m_BTKey = m_BTKey_map[temp];
    }
    else {
        m_BTKey           = m_binfhescheme->KeyGen(m_params, sk, keygenMode);
        m_BTKey_map[temp] = m_BTKey;
    }
}

LWECiphertext BinFHEContext::EvalBinGate(const BINGATE gate, ConstLWECiphertext& ct1, ConstLWECiphertext& ct2,
                                         bool extended) const {
    if (ct1 == nullptr)
        OPENFHE_THROW("Ciphertext1 is empty");
    if (ct2 == nullptr)
        OPENFHE_THROW("Ciphertext2 is empty");
    return m_binfhescheme->EvalBinGate(m_params, gate, m_BTKey, ct1, ct2, extended);
}

LWECiphertext BinFHEContext::EvalBinGate(const BINGATE gate, const std::vector<LWECiphertext>& ctvector,
                                         bool extended) const {
    return m_binfhescheme->EvalBinGate(m_params, gate, m_BTKey, ctvector, extended);
}

LWECiphertext BinFHEContext::Bootstrap(ConstLWECiphertext& ct, bool extended) const {
    if (ct == nullptr)
        OPENFHE_THROW("Ciphertext is empty");
    return m_binfhescheme->Bootstrap(m_params, m_BTKey, ct, extended);
}

LWECiphertext BinFHEContext::EvalNOT(ConstLWECiphertext& ct) const {
    if (ct == nullptr)
        OPENFHE_THROW("Ciphertext is empty");
    return m_binfhescheme->EvalNOT(m_params, ct);
}

LWECiphertext BinFHEContext::EvalConstant(bool value) const {
    return m_LWEscheme->NoiselessEmbedding(m_params->GetLWEParams(), value);
}

LWECiphertext BinFHEContext::EvalFunc(ConstLWECiphertext& ct, const std::vector<NativeInteger>& LUT) const {
    if (ct == nullptr)
        OPENFHE_THROW("Ciphertext is empty");
    return m_binfhescheme->EvalFunc(m_params, m_BTKey, ct, LUT, GetBeta());
}

LWECiphertext BinFHEContext::EvalFloor(ConstLWECiphertext& ct, uint32_t roundbits) const {
    //    auto q = m_params->GetLWEParams()->Getq().ConvertToInt();
    //    if (roundbits != 0) {
    //        NativeInteger newp = this->GetMaxPlaintextSpace();
    //        SetQ(q / newp * (1 << roundbits));
    //    }
    //    SetQ(q);
    //    return res;
    if (ct == nullptr)
        OPENFHE_THROW("Ciphertext is empty");
    return m_binfhescheme->EvalFloor(m_params, m_BTKey, ct, GetBeta(), roundbits);
}

LWECiphertext BinFHEContext::EvalSign(ConstLWECiphertext& ct, bool schemeSwitch) {
    if (ct == nullptr)
        OPENFHE_THROW("Ciphertext is empty");
    return m_binfhescheme->EvalSign(std::make_shared<BinFHECryptoParams>(*m_params), m_BTKey_map, ct, GetBeta(),
                                    schemeSwitch);
}

std::vector<LWECiphertext> BinFHEContext::EvalDecomp(ConstLWECiphertext& ct) {
    if (ct == nullptr)
        OPENFHE_THROW("Ciphertext is empty");
    return m_binfhescheme->EvalDecomp(m_params, m_BTKey_map, ct, GetBeta());
}

std::vector<NativeInteger> BinFHEContext::GenerateLUTviaFunction(NativeInteger (*f)(NativeInteger m, NativeInteger p),
                                                                 NativeInteger p) {
    if (!IsPowerOfTwo(p.ConvertToInt<BasicInteger>()))
        OPENFHE_THROW("plaintext p not power of two");

    NativeInteger q{GetParams()->GetLWEParams()->Getq()};
    NativeInteger x{0};

    std::vector<NativeInteger> vec(q.ConvertToInt(), q / p);
    for (size_t i = 0; i < vec.size(); ++i, x += p) {
        vec[i] *= f(x / q, p);  // x/q = (i*p)/q = i/(q/p)
        if (vec[i] >= q)        // (f(x/q, p) >= p) --> (f(x/q, p)*(q/p) >= q)
            OPENFHE_THROW("input function should output in Z_{p_output}");
    }
    return vec;
}

}  // namespace lbcrypto
