// Copyright (c) 2012-2017, The CryptoNote developers, The Bytecoin developers
// Copyright (c) 2016-2018  zawy12
// Copyright (c) 2016-2018, The Karbowanec developers
// Copyright (c) 2018-2020, The Qwertycoin Group.
//
// This file is part of Qwertycoin.
//
// Qwertycoin is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Qwertycoin is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Qwertycoin.  If not, see <http://www.gnu.org/licenses/>.

#include <cctype>
#include <boost/algorithm/string/trim.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/math/special_functions/round.hpp>
#include <Common/Base58.h>
#include <Common/int-util.h>
#include <Common/StringTools.h>
#include <CryptoNoteCore/Account.h>
#include <CryptoNoteCore/CryptoNoteBasicImpl.h>
#include <CryptoNoteCore/CryptoNoteFormatUtils.h>
#include <CryptoNoteCore/CryptoNoteTools.h>
#include <CryptoNoteCore/Currency.h>
#include <CryptoNoteCore/TransactionExtra.h>
#include <CryptoNoteCore/UpgradeDetector.h>
#include <Global/Constants.h>

#undef ERROR

using namespace Logging;
using namespace Common;
using namespace Qwertycoin;

namespace CryptoNote {

bool Currency::init()
{
    if (!generateGenesisBlock()) {
        logger(ERROR, BRIGHT_RED) << "Failed to generate genesis block";
        return false;
    }

    if (!get_block_hash(m_genesisBlock, m_genesisBlockHash)) {
        logger(ERROR, BRIGHT_RED) << "Failed to get genesis block hash";
        return false;
    }

    if (isTestnet()) {
        m_upgradeHeightV2 = 10;
        m_upgradeHeightV3 = 60;
        m_upgradeHeightV4 = 70;
        m_upgradeHeightV5 = 80;
        m_upgradeHeightV6 = 100;
        m_governancePercent = 10;
        m_governanceHeightStart = 1;
        m_governanceHeightEnd = 100;
        m_blocksFileName = "testnet_" + m_blocksFileName;
        m_blocksCacheFileName = "testnet_" + m_blocksCacheFileName;
        m_blockIndexesFileName = "testnet_" + m_blockIndexesFileName;
        m_txPoolFileName = "testnet_" + m_txPoolFileName;
        m_blockchainIndicesFileName = "testnet_" + m_blockchainIndicesFileName;
    }

    return true;
}

bool Currency::generateGenesisBlock()
{
    m_genesisBlock = boost::value_initialized<Block>();

    // Hard code coinbase tx in genesis block, because "tru" generating tx use random,
    // but genesis should be always the same
    std::string genesisCoinbaseTxHex = GENESIS_COINBASE_TX_HEX;
    BinaryArray minerTxBlob;

    bool r = fromHex(genesisCoinbaseTxHex, minerTxBlob)
             && fromBinaryArray(m_genesisBlock.baseTransaction, minerTxBlob);

    if (!r) {
        logger(ERROR, BRIGHT_RED) << "failed to parse coinbase tx from hard coded blob";
        return false;
    }

    m_genesisBlock.majorVersion = BLOCK_MAJOR_VERSION_1;
    m_genesisBlock.minorVersion = BLOCK_MINOR_VERSION_0;
    m_genesisBlock.timestamp = 0;
    m_genesisBlock.nonce = 70;
    if (m_testnet) {
        ++m_genesisBlock.nonce;
    }

    //miner::find_nonce_for_given_block(bl, 1, 0);

    return true;
}

size_t Currency::blockGrantedFullRewardZoneByBlockVersion(uint8_t blockMajorVersion) const
{
    if (blockMajorVersion >= BLOCK_MAJOR_VERSION_4) {
        return CryptoNote::parameters::CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2;
    } else if (blockMajorVersion == BLOCK_MAJOR_VERSION_3) {
        return m_blockGrantedFullRewardZone;
    } else if (blockMajorVersion == BLOCK_MAJOR_VERSION_2) {
        return CryptoNote::parameters::CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2;
    } else {
        return CryptoNote::parameters::CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1;
    }
}

uint32_t Currency::upgradeHeight(uint8_t majorVersion) const
{
    if (majorVersion == BLOCK_MAJOR_VERSION_6) {
        return m_upgradeHeightV6;
    } else if (majorVersion == BLOCK_MAJOR_VERSION_5) {
        return m_upgradeHeightV5;
    } else if (majorVersion == BLOCK_MAJOR_VERSION_4) {
        return m_upgradeHeightV4;
    } else if (majorVersion == BLOCK_MAJOR_VERSION_2) {
        return m_upgradeHeightV2;
    } else if (majorVersion == BLOCK_MAJOR_VERSION_3) {
        return m_upgradeHeightV3;
    } else {
        return static_cast<uint32_t>(-1);
    }
}

bool Currency::getBlockReward(
    uint8_t blockMajorVersion,
    size_t medianSize,
    size_t currentBlockSize,
    uint64_t alreadyGeneratedCoins,
    uint64_t fee,
    uint64_t &reward,
    int64_t &emissionChange,
    uint32_t height,
    uint64_t blockTarget) const
{
    assert(alreadyGeneratedCoins <= m_moneySupply);
    assert(m_emissionSpeedFactor > 0 && m_emissionSpeedFactor <= 8 * sizeof(uint64_t));

    // Tail emission

    uint64_t baseReward = (m_moneySupply - alreadyGeneratedCoins) >> m_emissionSpeedFactor;
    if (alreadyGeneratedCoins + CryptoNote::parameters::TAIL_EMISSION_REWARD >= m_moneySupply
        || baseReward < CryptoNote::parameters::TAIL_EMISSION_REWARD) {
        baseReward = CryptoNote::parameters::TAIL_EMISSION_REWARD;
    }

    size_t blockGrantedFullRewardZone = blockGrantedFullRewardZoneByBlockVersion(blockMajorVersion);
    medianSize = std::max(medianSize, blockGrantedFullRewardZone);
    if (currentBlockSize > medianSize * UINT64_C(2)) {
        logger(TRACE)
            << "Block cumulative size is too big: "
            << currentBlockSize
            << ", expected less than "
            << medianSize * UINT64_C(2);
        return false;
    }

    uint64_t penalizedBaseReward = getPenalizedAmount(baseReward, medianSize, currentBlockSize);
    uint64_t penalizedFee = fee;
    if (blockMajorVersion >= BLOCK_MAJOR_VERSION_2 || cryptonoteCoinVersion() == 1) {
        penalizedFee = getPenalizedAmount(fee, medianSize, currentBlockSize);
    }

    double consistency = 1.0;
    if (height >= CryptoNote::parameters::UPGRADE_HEIGHT_REWARD_SCHEME && difficultyTarget() != 0) {
        // blockTarget is (Timestamp of New Block - Timestamp of Previous Block)
        consistency = blockTarget / difficultyTarget();

        // consistency range is 0..2
        consistency = std::max<double>(consistency, 0.0);
        consistency = std::min<double>(consistency, 2.0);
    }

    double penalizedReward = static_cast<double>(penalizedBaseReward + penalizedFee);

    emissionChange = penalizedBaseReward - (fee - penalizedFee);
    reward = static_cast<uint64_t>(penalizedReward * consistency);

    return true;
}

size_t Currency::maxBlockCumulativeSize(uint64_t height) const
{
    assert(height <= std::numeric_limits<uint64_t>::max() / m_maxBlockSizeGrowthSpeedNumerator);

    size_t maxSize =
        static_cast<size_t>(m_maxBlockSizeInitial
        + (height * m_maxBlockSizeGrowthSpeedNumerator) / m_maxBlockSizeGrowthSpeedDenominator);

    assert(maxSize >= m_maxBlockSizeInitial);

    return maxSize;
}

bool Currency::isGovernanceEnabled(uint32_t height) const
{
    if (height >= m_governanceHeightStart && height <= m_governanceHeightEnd) {
        return true;
    }
    else {
        return false;
    }
}

uint64_t Currency::getGovernanceReward(uint64_t base_reward) const
{
    // minimum is 1% to avoid a zero amount and maximum is 50%
    uint16_t percent = (m_governancePercent < 1) ? 1 : (m_governancePercent > 50) ? 50 : m_governancePercent;
    return (uint64_t)(base_reward * (percent * 0.01));
}

bool Currency::getGovernanceAddressAndKey(AccountKeys& governanceKeys) const
{
    std::string address       = GOVERNANCE_WALLET_ADDRESS;
    std::string viewSecretkey = GOVERNANCE_VIEW_SECRET_KEY;

    AccountPublicAddress governanceAddress = boost::value_initialized<AccountPublicAddress>();
    if (!parseAccountAddressString(address, governanceAddress)) {
        logger(Logging::ERROR)
            << "Failed to parse governance wallet address ("
            << address
            << "), "
            << "Check /lib/Global/CryptoNoteConfig.h";
        return false;
    }

    Crypto::SecretKey governanceViewSecretKey;
    if (!Common::podFromHex(viewSecretkey, governanceViewSecretKey)) {
        logger(Logging::ERROR)
            << "Failed to parse governance view secret key"
            << "Check /lib/Global/CryptoNoteConfig.h";
        return false;
    }

    governanceKeys.address = governanceAddress;
    governanceKeys.viewSecretKey = governanceViewSecretKey;

    return true;
}

bool Currency::constructMinerTx(
    uint8_t blockMajorVersion,
    uint32_t height,
    size_t medianSize,
    uint64_t alreadyGeneratedCoins,
    size_t currentBlockSize,
    uint64_t fee,
    const AccountPublicAddress &minerAddress,
    Transaction &tx,
    const BinaryArray &extraNonce /* = BinaryArray()*/,
    size_t maxOuts /* = 1*/) const
{
    tx.inputs.clear();
    tx.outputs.clear();
    tx.extra.clear();

    KeyPair txkey = generateKeyPair();
    addTransactionPublicKeyToExtra(tx.extra, txkey.publicKey);
    if (!extraNonce.empty()) {
        if (!addExtraNonceToTransactionExtra(tx.extra, extraNonce)) {
            return false;
        }
    }

    BaseInput in;
    in.blockIndex = height;

    uint64_t governanceReward = 0;
    uint64_t totalRewardWGR = 0; //totalRewardWithoutGovernanceReward

    uint64_t blockReward;
    int64_t emissionChange;

    if (!getBlockReward(
            blockMajorVersion,
            medianSize,
            currentBlockSize,
            alreadyGeneratedCoins,
            fee,
            blockReward,
            emissionChange,
            height,
            difficultyTarget())
        ) {
        logger(INFO) << "Block is too big";
        return false;
    }

    totalRewardWGR = blockReward;

    // If Governance Fee blockReward is decreased by GOVERNANCE_PERCENT_FEE
    bool enableGovernance = isGovernanceEnabled(height);

    if (enableGovernance) {
        governanceReward = getGovernanceReward(blockReward);

        if (alreadyGeneratedCoins != 0) {
            blockReward -= governanceReward;
            totalRewardWGR  = blockReward + governanceReward;
        }
    }

    std::vector<uint64_t> outAmounts;
    decompose_amount_into_digits(
        blockReward,
        UINT64_C(0),
        [&outAmounts](uint64_t a_chunk) { outAmounts.push_back(a_chunk); },
        [&outAmounts](uint64_t a_dust) { outAmounts.push_back(a_dust); }
    );

    if (maxOuts < 1) {
        logger(ERROR, BRIGHT_RED) << "max_out must be non-zero";
        return false;
    }
    while (maxOuts < outAmounts.size()) {
        outAmounts[outAmounts.size() - 2] += outAmounts.back();
        outAmounts.resize(outAmounts.size() - 1);
    }

    uint64_t summaryAmounts = 0;
    for (size_t no = 0; no < outAmounts.size(); no++) {
        Crypto::KeyDerivation derivation = boost::value_initialized<Crypto::KeyDerivation>();
        Crypto::PublicKey outEphemeralPubKey = boost::value_initialized<Crypto::PublicKey>();

        bool r = Crypto::generate_key_derivation(
            minerAddress.viewPublicKey,
            txkey.secretKey,
            derivation
        );

        if (!(r)) {
            logger(ERROR, BRIGHT_RED)
                << "while creating outs: failed to generate_key_derivation("
                << minerAddress.viewPublicKey
                << ", "
                << txkey.secretKey
                << ")";
            return false;
        }

        r = Crypto::derive_public_key(derivation,no,minerAddress.spendPublicKey,outEphemeralPubKey);

        if (!(r)) {
            logger(ERROR, BRIGHT_RED)
                << "while creating outs: failed to derive_public_key("
                << derivation << ", "
                << no << ", "
                << minerAddress.spendPublicKey
                << ")";
            return false;
        }

        KeyOutput tk;
        tk.key = outEphemeralPubKey;

        TransactionOutput out;
        summaryAmounts += out.amount = outAmounts[no];
        out.target = tk;
        tx.outputs.push_back(out);
    }

    if (enableGovernance) {
        AccountKeys governanceKeys;
        getGovernanceAddressAndKey(governanceKeys);

        Crypto::KeyDerivation derivation = boost::value_initialized<Crypto::KeyDerivation>();
        Crypto::PublicKey outEphemeralPubKey = boost::value_initialized<Crypto::PublicKey>();

        bool r = Crypto::generate_key_derivation(governanceKeys.address.viewPublicKey, txkey.secretKey, derivation);
        if (!(r)) {
            logger(ERROR, BRIGHT_RED)
                << "while creating outs: failed to generate_key_derivation("
                << governanceKeys.address.viewPublicKey << ", " << txkey.secretKey << ")";
            return false;
        }
        size_t pos = tx.outputs.size();
        r = Crypto::derive_public_key(derivation, pos++, governanceKeys.address.spendPublicKey, outEphemeralPubKey);

        if (!(r)) {
            logger(ERROR, BRIGHT_RED)
                << "while creating outs: failed to derive_public_key("
                << derivation << ", " << 0 << ", "
                << governanceKeys.address.spendPublicKey << ")";
            return false;
        }

        KeyOutput tk;
        tk.key = outEphemeralPubKey;

        TransactionOutput out;
        summaryAmounts += out.amount = governanceReward;
        out.target = tk;
        tx.outputs.push_back(out);
    }

    if (summaryAmounts != totalRewardWGR) {
        logger(ERROR, BRIGHT_RED)
            << "Failed to construct miner tx, summaryAmounts = "
            << summaryAmounts
            << " not equal blockReward = "
            << blockReward;
        return false;
    }

    tx.version = CURRENT_TRANSACTION_VERSION;
    //lock
    tx.unlockTime = height + m_minedMoneyUnlockWindow;
    tx.inputs.push_back(in);

    return true;
}

bool Currency::isFusionTransaction(
    const std::vector<uint64_t> &inputsAmounts,
    const std::vector<uint64_t> &outputsAmounts,
    size_t size,
    uint32_t height) const
{
    // TODO: Simplify if (...) content.
    if (height <= CryptoNote::parameters::UPGRADE_HEIGHT_V3 ? size > CryptoNote::parameters::CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_CURRENT * 30 / 100 : size > fusionTxMaxSize()) {
        logger(ERROR) << "Fusion transaction verification failed: size exceeded max allowed size.";
        return false;
    }

    if (inputsAmounts.size() < fusionTxMinInputCount()) {
        logger(ERROR)
            << "Fusion transaction verification failed: "
            << "inputs count is less than minimum.";
        return false;
    }

    if (inputsAmounts.size() < outputsAmounts.size() * fusionTxMinInOutCountRatio()) {
        logger(ERROR)
            << "Fusion transaction verification failed: "
            << "inputs to outputs count ratio is less than minimum.";
        return false;
    }

    uint64_t inputAmount = 0;
    for (auto amount : inputsAmounts) {
        if (height < CryptoNote::parameters::UPGRADE_HEIGHT_V4) {
            if (amount < defaultDustThreshold()) {
                logger(ERROR)
                    << "Fusion transaction verification failed: amount "
                    << amount
                    << " is less than dust threshold.";
                return false;
            }
        }
        inputAmount += amount;
    }

    std::vector<uint64_t> expectedOutputsAmounts;
    expectedOutputsAmounts.reserve(outputsAmounts.size());
    decomposeAmount(
        inputAmount,
        height < CryptoNote::parameters::UPGRADE_HEIGHT_V4
        ? defaultDustThreshold()
        : UINT64_C(0), expectedOutputsAmounts);
    std::sort(expectedOutputsAmounts.begin(), expectedOutputsAmounts.end());

    bool decompose = expectedOutputsAmounts == outputsAmounts;
    if (!decompose) {
        logger(ERROR)
            << "Fusion transaction verification failed: "
            << "decomposed output amounts do not match expected.";
        return false;
    }

    return true;
}

bool Currency::isFusionTransaction(const Transaction &transaction,size_t size,uint32_t height) const
{
    assert(getObjectBinarySize(transaction) == size);

    std::vector<uint64_t> outputsAmounts;
    outputsAmounts.reserve(transaction.outputs.size());
    for (const TransactionOutput &output : transaction.outputs) {
        outputsAmounts.push_back(output.amount);
    }

    return isFusionTransaction(getInputsAmounts(transaction), outputsAmounts, size, height);
}

bool Currency::isFusionTransaction(const Transaction &transaction, uint32_t height) const
{
    return isFusionTransaction(transaction, getObjectBinarySize(transaction), height);
}

bool Currency::isAmountApplicableInFusionTransactionInput(
    uint64_t amount,
    uint64_t threshold,
    uint32_t height) const
{
    uint8_t ignore;
    return isAmountApplicableInFusionTransactionInput(amount, threshold, ignore, height);
}

bool Currency::isAmountApplicableInFusionTransactionInput(
    uint64_t amount,
    uint64_t threshold,
    uint8_t &amountPowerOfTen,
    uint32_t height) const
{
    if (amount >= threshold) {
        return false;
    }

    if (height < CryptoNote::parameters::UPGRADE_HEIGHT_V4 && amount < defaultDustThreshold()) {
        return false;
    }

    auto it = std::lower_bound(
        Constants::PRETTY_AMOUNTS.begin(),
        Constants::PRETTY_AMOUNTS.end(),
        amount
    );
    if (it == Constants::PRETTY_AMOUNTS.end() || amount != *it) {
        return false;
    }

    amountPowerOfTen = static_cast<uint8_t>(std::distance(Constants::PRETTY_AMOUNTS.begin(), it)/9);

    return true;
}

std::string Currency::accountAddressAsString(const AccountBase &account) const
{
    return getAccountAddressAsStr(m_publicAddressBase58Prefix, account.getAccountKeys().address);
}

std::string Currency::accountAddressAsString(const AccountPublicAddress &accountPublicAddress) const
{
    return getAccountAddressAsStr(m_publicAddressBase58Prefix, accountPublicAddress);
}

bool Currency::parseAccountAddressString(const std::string &str, AccountPublicAddress &addr) const
{
    uint64_t prefix;
    if (!CryptoNote::parseAccountAddressString(prefix, addr, str)) {
        return false;
    }

    if (prefix != m_publicAddressBase58Prefix) {
        logger(DEBUGGING)
        << "Wrong address prefix: " << prefix
        << ", expected " << m_publicAddressBase58Prefix;
        return false;
    }

    return true;
}

std::string Currency::formatAmount(uint64_t amount) const
{
    std::string s = std::to_string(amount);
    if (s.size() < m_numberOfDecimalPlaces + 1) {
        s.insert(0, m_numberOfDecimalPlaces + 1 - s.size(), '0');
    }
    s.insert(s.size() - m_numberOfDecimalPlaces, ".");

    return s;
}

std::string Currency::formatAmount(int64_t amount) const
{
    std::string s = formatAmount(static_cast<uint64_t>(std::abs(amount)));

    if (amount < 0) {
        s.insert(0, "-");
    }

    return s;
}

bool Currency::parseAmount(const std::string &str, uint64_t &amount) const
{
    std::string strAmount = str;
    boost::algorithm::trim(strAmount);

    size_t pointIndex = strAmount.find_first_of('.');
    size_t fractionSize;
    if (std::string::npos != pointIndex) {
        fractionSize = strAmount.size() - pointIndex - 1;
        while (m_numberOfDecimalPlaces < fractionSize && '0' == strAmount.back()) {
            strAmount.erase(strAmount.size() - 1, 1);
            --fractionSize;
        }
        if (m_numberOfDecimalPlaces < fractionSize) {
            return false;
        }
        strAmount.erase(pointIndex, 1);
    } else {
        fractionSize = 0;
    }

    if (strAmount.empty()) {
        return false;
    }

    if (!std::all_of(strAmount.begin(), strAmount.end(), ::isdigit)) {
        return false;
    }

    if (fractionSize < m_numberOfDecimalPlaces) {
        strAmount.append(m_numberOfDecimalPlaces - fractionSize, '0');
    }

    return Common::fromString(strAmount, amount);
}

// Copyright (c) 2017-2018 Zawy
// http://zawy1.blogspot.com/2017/12/using-difficulty-to-get-constant-value.html
// Moore's law application by Sergey Kozlov
uint64_t Currency::getMinimalFee(
    uint64_t dailyDifficulty,
    uint64_t reward,
    uint64_t avgHistoricalDifficulty,
    uint64_t medianHistoricalReward,
    uint32_t height) const
{
    const uint64_t blocksInTwoYears = parameters::EXPECTED_NUMBER_OF_BLOCKS_PER_DAY * 365 * 2;
    const double gauge = double(0.25);
    uint64_t minimumFee(0);
    double dailyDifficultyMoore = dailyDifficulty
                                  / pow(2, static_cast<double>(height)
                                  / static_cast<double>(blocksInTwoYears));
    double minFee = gauge * parameters::COIN * static_cast<double>(avgHistoricalDifficulty)
                    / dailyDifficultyMoore * static_cast<double>(reward)
                    / static_cast<double>(medianHistoricalReward);
    if (minFee == 0 || !std::isfinite(minFee)) {
        return CryptoNote::parameters::MAXIMUM_FEE; // zero test
    }
    minimumFee = static_cast<uint64_t>(minFee);

    // TODO: return std::min<uint64_t>(CryptoNote::parameters::MAXIMUM_FEE, minimumFee);
    return CryptoNote::parameters::MINIMUM_FEE_V1;
}

uint64_t Currency::roundUpMinFee(uint64_t minimalFee, int digits) const
{
    uint64_t ret(0);
    std::string minFeeString = formatAmount(minimalFee);
    double minFee = boost::lexical_cast<double>(minFeeString);
    double scale = pow(10., floor(log10(fabs(minFee))) + (1 - digits));
    double roundedFee = ceil(minFee / scale) * scale;
    std::stringstream ss;
    ss << std::fixed << std::setprecision(12) << roundedFee;
    std::string roundedFeeString = ss.str();
    parseAmount(roundedFeeString, ret);

    return ret;
}

difficulty_type Currency::nextDifficulty(
    uint32_t height,
    uint8_t blockMajorVersion,
    std::vector<uint64_t> timestamps,
    std::vector<difficulty_type> cumulativeDifficulties) const
{
    if (blockMajorVersion >= BLOCK_MAJOR_VERSION_5) {
        return nextDifficultyV5(blockMajorVersion, timestamps, cumulativeDifficulties);
    } else if (blockMajorVersion == BLOCK_MAJOR_VERSION_3
               || blockMajorVersion == BLOCK_MAJOR_VERSION_4) {
        return nextDifficultyV3(timestamps, cumulativeDifficulties);
    } else if (blockMajorVersion == BLOCK_MAJOR_VERSION_2) {
        return nextDifficultyV2(timestamps, cumulativeDifficulties);
    } else {
        return nextDifficultyV1(timestamps, cumulativeDifficulties);
    }
}

difficulty_type Currency::nextDifficultyV1(
    std::vector<uint64_t> timestamps,
    std::vector<difficulty_type> cumulativeDifficulties) const
{
    assert(m_difficultyWindow >= 2);

    if (timestamps.size() > m_difficultyWindow) {
        timestamps.resize(m_difficultyWindow);
        cumulativeDifficulties.resize(m_difficultyWindow);
    }

    size_t length = timestamps.size();
    assert(length == cumulativeDifficulties.size());
    assert(length <= m_difficultyWindow);
    if (length <= 1) {
        return 1;
    }

    sort(timestamps.begin(), timestamps.end());

    size_t cutBegin, cutEnd;
    assert(2 * m_difficultyCut <= m_difficultyWindow - 2);
    if (length <= m_difficultyWindow - 2 * m_difficultyCut) {
        cutBegin = 0;
        cutEnd = length;
    } else {
        cutBegin = (length - (m_difficultyWindow - 2 * m_difficultyCut) + 1) / 2;
        cutEnd = cutBegin + (m_difficultyWindow - 2 * m_difficultyCut);
    }
    assert(/*cut_begin >= 0 &&*/ cutBegin + 2 <= cutEnd && cutEnd <= length);
    uint64_t timeSpan = timestamps[cutEnd - 1] - timestamps[cutBegin];
    if (timeSpan == 0) {
        timeSpan = 1;
    }

    difficulty_type totalWork = cumulativeDifficulties[cutEnd - 1]-cumulativeDifficulties[cutBegin];
    assert(totalWork > 0);

    uint64_t low, high;
    low = mul128(totalWork, m_difficultyTarget, &high);
    if (high != 0 || low + timeSpan - 1 < low) {
        return 0;
    }

    return (low + timeSpan - 1) / timeSpan;
}

difficulty_type Currency::nextDifficultyV2(
    std::vector<uint64_t> timestamps,
    std::vector<difficulty_type> cumulativeDifficulties) const
{
    // Difficulty calculation v. 2
    // based on Zawy difficulty algorithm v1.0
    // next Diff = Avg past N Diff * TargetInterval / Avg past N solve times
    // as described at https://github.com/monero-project/research-lab/issues/3
    // Window time span and total difficulty is taken instead of average
    // as suggested by Nuclear_chaos

    size_t m_difficultyWindow_2 = CryptoNote::parameters::DIFFICULTY_WINDOW_V2;
    assert(m_difficultyWindow_2 >= 2);

    if (timestamps.size() > m_difficultyWindow_2) {
        timestamps.resize(m_difficultyWindow_2);
        cumulativeDifficulties.resize(m_difficultyWindow_2);
    }

    size_t length = timestamps.size();
    assert(length == cumulativeDifficulties.size());
    assert(length <= m_difficultyWindow_2);
    if (length <= 1) {
        return 1;
    }

    sort(timestamps.begin(), timestamps.end());

    uint64_t timeSpan = timestamps.back() - timestamps.front();
    if (timeSpan == 0) {
        timeSpan = 1;
    }

    difficulty_type totalWork = cumulativeDifficulties.back() - cumulativeDifficulties.front();
    assert(totalWork > 0);

    // uint64_t nextDiffZ = totalWork * m_difficultyTarget / timeSpan;

    uint64_t low, high;
    low = mul128(totalWork, m_difficultyTarget, &high);
    // blockchain error "Difficulty overhead" if this function returns zero
    if (high != 0) {
        return 0;
    }

    uint64_t nextDiffZ = low / timeSpan;

    // minimum limit
    if (!isTestnet() && nextDiffZ < 100000) {
        nextDiffZ = 100000;
    }

    return nextDiffZ;
}

difficulty_type Currency::nextDifficultyV3(
    std::vector<uint64_t> timestamps,
    std::vector<difficulty_type> cumulativeDifficulties) const
{
    // LWMA difficulty algorithm
    // Copyright (c) 2017-2018 Zawy
    // MIT license http://www.opensource.org/licenses/mit-license.php.
    // This is an improved version of Tom Harding's (Deger8) "WT-144"
    // Karbowanec, Masari, Bitcoin Gold, and Bitcoin Cash have contributed.
    // See https://github.com/zawy12/difficulty-algorithms/issues/1 for other algos.
    // Do not use "if solvetime < 0 then solvetime = 1" which allows a catastrophic exploit.
    // T= target_solvetime;
    // N = int(45 * (600 / T) ^ 0.3));

    const int64_t T = static_cast<int64_t>(m_difficultyTarget);
    size_t N = CryptoNote::parameters::DIFFICULTY_WINDOW_V3;

    // return a difficulty of 1 for first 3 blocks if it's the start of the chain
    if (timestamps.size() < 4) {
        return 1;
    } else if (timestamps.size() < N + 1) {
        // otherwise, use a smaller N if the start of the chain is less than N+1
        N = timestamps.size() - 1;
    } else if (timestamps.size() > N + 1) {
        timestamps.erase(timestamps.begin(), timestamps.end() - N - 1);
        cumulativeDifficulties.erase(
            cumulativeDifficulties.begin(),
            cumulativeDifficulties.end() - N - 1
        );
    }

    // To get an average solvetime to within +/- ~0.1%, use an adjustment factor.
    const double adjust = 0.998;
    // The divisor k normalizes LWMA.
    const double k = N * (N + 1) / 2;

    double LWMA(0), sum_inverse_D(0), harmonic_mean_D(0), nextDifficulty(0);
    int64_t solveTime(0);
    uint64_t difficulty(0), next_difficulty(0);

    // Loop through N most recent blocks.
    for (size_t i = 1; i <= N; i++) {
        solveTime = static_cast<int64_t>(timestamps[i]) - static_cast<int64_t>(timestamps[i - 1]);
        solveTime = std::min<int64_t>((T * 7), std::max<int64_t>(solveTime, (-6 * T)));
        difficulty = cumulativeDifficulties[i] - cumulativeDifficulties[i - 1];
        LWMA += (int64_t)(solveTime * i) / k;
        sum_inverse_D += 1 / static_cast<double>(difficulty);
    }

    // Keep LWMA sane in case something unforeseen occurs.
    if (static_cast<int64_t>(boost::math::round(LWMA)) < T / 20)
    LWMA = static_cast<double>(T) / 20;

    harmonic_mean_D = N / sum_inverse_D * adjust;
    nextDifficulty = harmonic_mean_D * T / LWMA;
    next_difficulty = static_cast<uint64_t>(nextDifficulty);

    // minimum limit
    if (!isTestnet() && next_difficulty < 100000) {
        next_difficulty = 100000;
    }

    return next_difficulty;
}

template <typename T>
inline T clamp(T lo, T v, T hi)
{
    return v < lo ? lo : v > hi ? hi : v;
}

// difficulty for block version 5.0
difficulty_type Currency::nextDifficultyV5(
    uint8_t blockMajorVersion,
    std::vector<std::uint64_t> timestamps,
    std::vector<difficulty_type> cumulativeDifficulties) const
{
    // LWMA-2 difficulty algorithm
    // Copyright (c) 2017-2018 Zawy, MIT License
    // https://github.com/zawy12/difficulty-algorithms/issues/3
    // with modifications by Ryo Currency developers
    // courtesy to aivve from Karbo

    const int64_t  T = static_cast<int64_t>(m_difficultyTarget);
    int64_t  N = difficultyBlocksCount3();
    int64_t  L(0), ST, sum_3_ST(0);
    uint64_t nextDiffV5, prev_D;

    assert(timestamps.size() == cumulativeDifficulties.size()
           && timestamps.size() <= static_cast<uint64_t>(N + 1));

    int64_t max_TS, prev_max_TS;
    prev_max_TS = timestamps[0];
    for (int64_t i = 1; i <= N; i++) {
        if (static_cast<int64_t>(timestamps[i]) > prev_max_TS) {
            max_TS = timestamps[i];
        } else {
            max_TS = prev_max_TS + 1;
        }
        ST = std::min(6 * T, max_TS - prev_max_TS);
        prev_max_TS = max_TS;
        L += ST * i;
        if (i > N - 3) {
            sum_3_ST += ST;
        }
    }

    nextDiffV5 = uint64_t((cumulativeDifficulties[N] - cumulativeDifficulties[0]) * T * (N + 1))
                 / uint64_t(2 * L);
    nextDiffV5 = (nextDiffV5 * 99ull) / 100ull;

    prev_D = cumulativeDifficulties[N] - cumulativeDifficulties[N - 1];
    nextDiffV5 = clamp(
        (uint64_t)(prev_D * 67ull / 100ull),
        nextDiffV5,
        (uint64_t)(prev_D * 150ull / 100ull)
    );
    if (sum_3_ST < (8 * T) / 10) {
        nextDiffV5 = (prev_D * 110ull) / 100ull;
    }

    // minimum limit
    if (nextDiffV5 < 10000000) {
        nextDiffV5 = 10000000;
    }
    if(isTestnet()){
        nextDiffV5 = 10000;
    }

    return nextDiffV5;
}

bool Currency::checkProofOfWorkV1(
    Crypto::cn_context &context,
    const Block &block,
    difficulty_type currentDiffic,
    Crypto::Hash &proofOfWork) const
{
    if (BLOCK_MAJOR_VERSION_2 == block.majorVersion||BLOCK_MAJOR_VERSION_3 == block.majorVersion) {
        return false;
    }

    if (!get_block_longhash(context, block, proofOfWork)) {
        return false;
    }

    return check_hash(proofOfWork, currentDiffic);
}

bool Currency::checkProofOfWorkV2(
    Crypto::cn_context &context,
    const Block &block,
    difficulty_type currentDiffic,
    Crypto::Hash &proofOfWork) const
{
    if (block.majorVersion < BLOCK_MAJOR_VERSION_2) {
        return false;
    }

    if (!get_block_longhash(context, block, proofOfWork)) {
        return false;
    }

    if (!check_hash(proofOfWork, currentDiffic)) {
        return false;
    }

    TransactionExtraMergeMiningTag mmTag;
    if (!getMergeMiningTagFromExtra(block.parentBlock.baseTransaction.extra, mmTag)) {
        logger(ERROR)
            << "merge mining tag wasn't found in extra of the parent block miner transaction";
        return false;
    }

    if (8 * sizeof(m_genesisBlockHash) < block.parentBlock.blockchainBranch.size()) {
        return false;
    }

    Crypto::Hash auxBlockHeaderHash;
    if (!get_aux_block_header_hash(block, auxBlockHeaderHash)) {
        return false;
    }

    Crypto::Hash auxBlocksMerkleRoot;
    Crypto::tree_hash_from_branch(
        block.parentBlock.blockchainBranch.data(),
        block.parentBlock.blockchainBranch.size(),
        auxBlockHeaderHash,
        &m_genesisBlockHash,
        auxBlocksMerkleRoot
    );

    if (auxBlocksMerkleRoot != mmTag.merkleRoot) {
        logger(ERROR, BRIGHT_YELLOW) << "Aux block hash wasn't found in merkle tree";
        return false;
    }

    return true;
}

bool Currency::checkProofOfWork(
    Crypto::cn_context &context,
    const Block &block,
    difficulty_type currentDiffic,
    Crypto::Hash &proofOfWork) const
{
    switch (block.majorVersion) {
    case BLOCK_MAJOR_VERSION_1:
        // fall through
    case BLOCK_MAJOR_VERSION_4:
        // fall through
    case BLOCK_MAJOR_VERSION_5:
        // fall through
    case BLOCK_MAJOR_VERSION_6:
        return checkProofOfWorkV1(context, block, currentDiffic, proofOfWork);
    case BLOCK_MAJOR_VERSION_2:
        // fall through
    case BLOCK_MAJOR_VERSION_3:
        return checkProofOfWorkV2(context, block, currentDiffic, proofOfWork);
    }

    logger(ERROR, BRIGHT_RED)
        << "Unknown block major version: "
        << block.majorVersion
        << "."
        << block.minorVersion;

    return false;
}

size_t Currency::getApproximateMaximumInputCount(
    size_t transactionSize,
    size_t outputCount,
    size_t mixinCount) const
{
    const size_t KEY_IMAGE_SIZE = sizeof(Crypto::KeyImage);
    const size_t OUTPUT_KEY_SIZE = sizeof(decltype(KeyOutput::key));
    const size_t AMOUNT_SIZE = sizeof(uint64_t) + 2; // varint
    const size_t GLOBAL_INDEXES_VECTOR_SIZE_SIZE = sizeof(uint8_t); // varint
    const size_t GLOBAL_INDEXES_INITIAL_VALUE_SIZE = sizeof(uint32_t); // varint
    const size_t GLOBAL_INDEXES_DIFFERENCE_SIZE = sizeof(uint32_t); // varint
    const size_t SIGNATURE_SIZE = sizeof(Crypto::Signature);
    const size_t EXTRA_TAG_SIZE = sizeof(uint8_t);
    const size_t INPUT_TAG_SIZE = sizeof(uint8_t);
    const size_t OUTPUT_TAG_SIZE = sizeof(uint8_t);
    const size_t PUBLIC_KEY_SIZE = sizeof(Crypto::PublicKey);
    const size_t TRANSACTION_VERSION_SIZE = sizeof(uint8_t);
    const size_t TRANSACTION_UNLOCK_TIME_SIZE = sizeof(uint64_t);

    const size_t outputsSize = outputCount * (OUTPUT_TAG_SIZE + OUTPUT_KEY_SIZE + AMOUNT_SIZE);
    const size_t headerSize = TRANSACTION_VERSION_SIZE
                              + TRANSACTION_UNLOCK_TIME_SIZE
                              + EXTRA_TAG_SIZE
                              + PUBLIC_KEY_SIZE;
    const size_t inputSize = INPUT_TAG_SIZE
                             + AMOUNT_SIZE
                             + KEY_IMAGE_SIZE
                             + SIGNATURE_SIZE
                             + GLOBAL_INDEXES_VECTOR_SIZE_SIZE
                             + GLOBAL_INDEXES_INITIAL_VALUE_SIZE
                             + mixinCount * (GLOBAL_INDEXES_DIFFERENCE_SIZE + SIGNATURE_SIZE);

    return (transactionSize - headerSize - outputsSize) / inputSize;
}

CurrencyBuilder::CurrencyBuilder(Logging::ILogger &log)
    : m_currency(log)
{
    maxBlockNumber(parameters::CRYPTONOTE_MAX_BLOCK_NUMBER);
    maxBlockBlobSize(parameters::CRYPTONOTE_MAX_BLOCK_BLOB_SIZE);
    maxTxSize(parameters::CRYPTONOTE_MAX_TX_SIZE);
    publicAddressBase58Prefix(parameters::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX);
    minedMoneyUnlockWindow(parameters::CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW);
    transactionSpendableAge(parameters::CRYPTONOTE_TX_SPENDABLE_AGE);
    expectedNumberOfBlocksPerDay(parameters::EXPECTED_NUMBER_OF_BLOCKS_PER_DAY);

    timestampCheckWindow(parameters::BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW);
    timestampCheckWindow_v1(parameters::BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW_V1);
    blockFutureTimeLimit(parameters::CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT);
    blockFutureTimeLimit_v1(parameters::CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT_V1);

    moneySupply(parameters::MONEY_SUPPLY);
    emissionSpeedFactor(parameters::EMISSION_SPEED_FACTOR);
    cryptonoteCoinVersion(parameters::CRYPTONOTE_COIN_VERSION);

    rewardBlocksWindow(parameters::CRYPTONOTE_REWARD_BLOCKS_WINDOW);
    blockGrantedFullRewardZone(parameters::CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE);
    minerTxBlobReservedSize(parameters::CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE);
    maxTransactionSizeLimit(parameters::MAX_TRANSACTION_SIZE_LIMIT);

    governancePercent(parameters::GOVERNANCE_PERCENT_FEE);
    governanceHeightStart(parameters::GOVERNANCE_HEIGHT_START);
    governanceHeightEnd(parameters::GOVERNANCE_HEIGHT_END);

    minMixin(parameters::MIN_TX_MIXIN_SIZE);
    maxMixin(parameters::MAX_TX_MIXIN_SIZE);

    numberOfDecimalPlaces(parameters::CRYPTONOTE_DISPLAY_DECIMAL_POINT);

    minimumFee(parameters::MINIMUM_FEE);
    defaultDustThreshold(parameters::DEFAULT_DUST_THRESHOLD);

    difficultyTarget(parameters::DIFFICULTY_TARGET);
    difficultyWindow(parameters::DIFFICULTY_WINDOW);
    difficultyLag(parameters::DIFFICULTY_LAG);
    difficultyCut(parameters::DIFFICULTY_CUT);

    maxBlockSizeInitial(parameters::MAX_BLOCK_SIZE_INITIAL);
    maxBlockSizeGrowthSpeedNumerator(parameters::MAX_BLOCK_SIZE_GROWTH_SPEED_NUMERATOR);
    maxBlockSizeGrowthSpeedDenominator(parameters::MAX_BLOCK_SIZE_GROWTH_SPEED_DENOMINATOR);

    lockedTxAllowedDeltaSeconds(parameters::CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS);
    lockedTxAllowedDeltaBlocks(parameters::CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS);

    mempoolTxLiveTime(parameters::CRYPTONOTE_MEMPOOL_TX_LIVETIME);
    mempoolTxFromAltBlockLiveTime(parameters::CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME);
    numberOfPeriodsToForgetTxDeletedFromPool(
        parameters::CRYPTONOTE_NUMBER_OF_PERIODS_TO_FORGET_TX_DELETED_FROM_POOL
    );

    fusionTxMaxSize(parameters::FUSION_TX_MAX_SIZE);
    fusionTxMinInputCount(parameters::FUSION_TX_MIN_INPUT_COUNT);
    fusionTxMinInOutCountRatio(parameters::FUSION_TX_MIN_IN_OUT_COUNT_RATIO);

    upgradeHeightV2(parameters::UPGRADE_HEIGHT_V2);
    upgradeHeightV3(parameters::UPGRADE_HEIGHT_V3);
    upgradeHeightV4(parameters::UPGRADE_HEIGHT_V4);
    upgradeHeightV5(parameters::UPGRADE_HEIGHT_V5);
    upgradeHeightV6(parameters::UPGRADE_HEIGHT_V6);
    upgradeVotingThreshold(parameters::UPGRADE_VOTING_THRESHOLD);
    upgradeVotingWindow(parameters::UPGRADE_VOTING_WINDOW);
    upgradeWindow(parameters::UPGRADE_WINDOW);

    blocksFileName(parameters::CRYPTONOTE_BLOCKS_FILENAME);
    blocksCacheFileName(parameters::CRYPTONOTE_BLOCKSCACHE_FILENAME);
    blockIndexesFileName(parameters::CRYPTONOTE_BLOCKINDEXES_FILENAME);
    txPoolFileName(parameters::CRYPTONOTE_POOLDATA_FILENAME);
    blockchainIndicesFileName(parameters::CRYPTONOTE_BLOCKCHAIN_INDICES_FILENAME);

    testnet(false);
}

Transaction CurrencyBuilder::generateGenesisTransaction()
{
    CryptoNote::Transaction tx;
    auto ac = boost::value_initialized<CryptoNote::AccountPublicAddress>();

    m_currency.constructMinerTx(1, 0, 0, 0, 0, 0, ac, tx); // zero fee in genesis

    return tx;
}

CurrencyBuilder& CurrencyBuilder::emissionSpeedFactor(unsigned int val)
{
    if (val <= 0 || val > 8 * sizeof(uint64_t)) {
        throw std::invalid_argument("val at emissionSpeedFactor()");
    }

    m_currency.m_emissionSpeedFactor = val;

    return *this;
}

CurrencyBuilder &CurrencyBuilder::numberOfDecimalPlaces(size_t val)
{
    m_currency.m_numberOfDecimalPlaces = val;
    m_currency.m_coin = 1;
    for (size_t i = 0; i < m_currency.m_numberOfDecimalPlaces; ++i) {
        m_currency.m_coin *= 10;
    }

    return *this;
}

CurrencyBuilder &CurrencyBuilder::difficultyWindow(size_t val)
{
    if (val < 2) {
        throw std::invalid_argument("val at difficultyWindow()");
    }

    m_currency.m_difficultyWindow = val;

    return *this;
}

CurrencyBuilder& CurrencyBuilder::upgradeVotingThreshold(unsigned int val)
{
    if (val <= 0 || val > 100) {
        throw std::invalid_argument("val at upgradeVotingThreshold()");
    }

    m_currency.m_upgradeVotingThreshold = val;

    return *this;
}

CurrencyBuilder &CurrencyBuilder::upgradeWindow(size_t val)
{
    if (val <= 0) {
        throw std::invalid_argument("val at upgradeWindow()");
    }

    m_currency.m_upgradeWindow = static_cast<uint32_t>(val);

    return *this;
}

} // namespace CryptoNote


#ifndef TYPE_TRAITS
#define TYPE_TRAITS
#include "rider/faiz/cstddef.hpp" // for size_t
#include "rider/faiz/faiz_fwd.hpp"
#include <boost/predef.h>
#include <type_traits>
/*
Don't implement:
*/

namespace Rider::Faiz
{
	// Provides member typedef type, which is defined as T if B is true at
	// compile time, or as F if B is false.
	Tpl<bool B, Typ T, Typ F> struct conditional : type_identity<T>
	{};

	// Provides member typedef type, which is defined as T if B is true at
	// compile time, or as F if B is false.
	Tpl<Typ T, Typ F> struct conditional<false, T, F> : type_identity<F>
	{};

	//  Provides member typedef type, which is defined as T if B is true at
	//  compile time, or as F if B is false.
	Tpl<bool B, Typ T, Typ F> using conditional_t = _t<conditional<B, T, F>>;

	// forward declaration
	// If the imaginary function definition tTo `test() { return
	// std::declval<tFrom>(); }` is well-formed, (that is, either
	// `Faiz::declval<tFrom>()` can be converted to tTo using implicit
	// conversions, or both tFrom and tTo are possibly `cv-qualified void`),
	// provides the member constant value equal to true. Otherwise value is
	// false. For the purposes of this check, the use of `Faiz::declval` in the
	// return statement is not considered an odr-use. Access checks are
	// performed as if from a context unrelated to either type. Only the
	// validity of the immediate context of the expression in the return
	// statement (including conversions to the return type) is considered.
	Tpl<Typ tFrom, Typ tTo> struct is_convertible;

	namespace detail
	{

		Tpl<Typ Default,
			class AlwaysVoid,
			Tpl<Typ...> class Op,
			Typ... Args> struct detector : type_identity<Default>
		{
			using value_t = false_;
		};

		Tpl<Typ Default, Tpl<Typ...> class Op, class... Args> struct detector<
			Default,
			void_t<Op<Args...>>,
			Op,
			Args...>
#if BOOST_COMP_GNUC
			: type_identity<Op<Args...>>
#endif
		{
			using value_t = true_;
#if BOOST_COMP_CLANG
			using type = Op<Args...>;
#endif
		};

	} // namespace detail

	// nonesuch cannot be constructed, destroyed, or copied in the usual way.
	// However, it is an aggregate and therefore can be constructed (presumably
	// unintentionally) via aggregate initialization in contexts where the
	// destructor's availability is not an issue, such as a new-expression: new
	// std::experimental::nonesuch{}.
	//
	// This type was inspired by, and patterned after, the internal type __nat
	// (which we believe is an acronym for “not a type”) found in libc++.
	struct nonesuch
	{
		~nonesuch() = delete;
		nonesuch(nonesuch const&) = delete;
		void
		operator=(nonesuch const&)
			= delete;
	};

	// The alias Tpl `is_detected` is equivalent to Typ
	// `detected_or<Faiz::nonesuch, Op, Args...>::value_t`. It is an
	// alias for `Faiz::true_type` if the `Tpl-id Op<Args...>` denotes a
	// valid type; otherwise it is an alias for `Faiz::false_type`.
	Tpl<Tpl<Typ...> class Op, Typ... Args> using is_detected
		= Typ detail::detector<nonesuch, void, Op, Args...>::value_t;

	// The alias Tpl `detected_t` is equivalent to Typ
	// `detected_or<Faiz::nonesuch, Op, Args...>::type`. It is an
	// alias for `Op<Args...>` if that Tpl-id denotes a valid type;
	// otherwise it is an alias for the class `Faiz::nonesuch`.
	Tpl<Tpl<Typ...> class Op, Typ... Args> using detected_t
		= _t<detail::detector<nonesuch, void, Op, Args...>>;

	// The alias Tpl `detected_or` is an alias for an unspecified class
	// type with two public member typedefs `value_t` and type, which are
	// defined as follows:
	//
	// - If the Tpl-id `Op<Args...>` denotes a valid type,
	// then `value_t`
	//  is an alias for `Faiz::true_type`, and type is an alias for
	// `Op<Args...>`;
	// - Otherwise, `value_t` is an alias for `Faiz::false_type` and type is
	// an alias for `Default`.
	Tpl<Typ Default, Tpl<Typ...> class Op, Typ... Args> using detected_or
		= detail::detector<Default, void, Op, Args...>;

	Tpl<Tpl<Typ...> class Op, Typ... Args> cexp bool is_detected_v
		= is_detected<Op, Args...>::value;

	Tpl<Typ Default, Tpl<Typ...> class Op, Typ... Args> using detected_or_t
		= _t<detected_or<Default, Op, Args...>>;
	//
	// The alias Tpl is_detected_exact checks whether `detected_t<Op,
	// Args...>` is Expected.
	Tpl<Typ Expected, Tpl<Typ...> class Op, Typ... Args> using is_detected_exact
		= is_same<Expected, detected_t<Op, Args...>>;

	Tpl<Typ Expected, Tpl<Typ...> class Op, Typ... Args> cexp bool
		is_detected_exact_v
		= is_detected_exact<Expected, Op, Args...>::value;
	//
	//  The alias Tpl `is_detected_convertible` checks whether
	// `detected_t<Op, Args...>` is convertible to tTo.
	Tpl<Typ tTo, Tpl<Typ...> class Op, Typ... Args> using is_detected_convertible
		= is_convertible<detected_t<Op, Args...>, tTo>;

	Tpl<Typ tTo, Tpl<Typ...> class Op, Typ... Args> cexp bool
		is_detected_convertible_v
		= is_detected_convertible<tTo, Op, Args...>::value;

	Tpl<Typ T> using is_void = is_same<void, remove_cv_t<T>>;

	IS_NOT_ARE_ANY(void)

	Tpl<Typ T> using is_referenceable_aux = is_void<T&>;
	Tpl<Typ T> cexp bool is_referenceable_v
		= is_detected_v<is_referenceable_aux, T> and not_void_v<T>;
	Tpl<Typ T> struct is_referenceable : bool_<is_referenceable_v<T>>
	{};

	NOT(referenceable)

	Tpl<Typ T> struct is_rvalue_reference;

	IS_NOT_ARE_ANY(rvalue_reference)

} // namespace Rider::Faiz

namespace Rider::Faiz::detail
{
	Tpl<Typ T> struct is_member_pointer_helper : false_
	{};

	Tpl<Typ T, Typ U> struct is_member_pointer_helper<T U::*> : true_
	{};

	Tpl<Typ Base> true_
	is_base_of_test_func(const volatile Base*);
	Tpl<Typ Base> Faiz::false_
	is_base_of_test_func(const volatile void*);
	Tpl<Typ Base, Typ Derived> using pre_is_base_of
		= decltype(is_base_of_test_func<Base>(declval<Derived*>()));

	// with <experimental/type_traits>:
	// Tpl <Typ Base, Typ Derived>
	// using pre_is_base_of2 =
	// detected_or_t<true_, pre_is_base_of,
	// Base, Derived>;
	Tpl<Typ Base, Typ Derived, Typ = void> struct pre_is_base_of2 : true_
	{};
	// note Faiz::void_t is a C++17 feature
	Tpl<Typ Base, Typ Derived> struct pre_is_base_of2<Base,
		Derived,
		void_t<pre_is_base_of<Base, Derived>>> : pre_is_base_of<Base, Derived>
	{};

	Tpl<Typ T, Typ> struct is_polymorphic_impl : false_
	{};
	Tpl<Typ T> struct is_polymorphic_impl<T,
		decltype(dynamic_cast<void*>(declval<remove_cv_t<T>*>()))> : true_
	{};

	// NOTE: !Rider::Faiz::is_assignable<bool &, std::nullptr_t>::value'
	// "Error", clang++ has a bug here.
	// TODO: use is_detected_v
	Tpl<Typ T, Typ Arg, Typ> struct is_assignable_imp : false_
	{};
	Tpl<Typ T, Typ Arg> struct is_assignable_imp<T,
		Arg,
		void_t<decltype(declval<T>() = declval<Arg>())>> : not_void<Arg>
	{};

	Tpl<bool IsArithmetic, Typ T> struct is_signed_impl : bool_<T(-1) < T(0)>
	{};
	Tpl<Typ T> struct is_signed_impl<false, T> : false_
	{};

	Tpl<bool IsArithmetic, Typ T> struct is_unsigned_impl
		: bool_<(T(-1) > T(0))>
	{};
	Tpl<Typ T> struct is_unsigned_impl<false, T> : false_
	{};

	Tpl<Typ T, Typ U = remove_cv_t<T>> struct is_floating_point_aux
		: is_any<U,
			  float,
			  double,
#if _GLIBCXX_USE_FLOAT128
			  __float128, // add gcc specific
#endif
			  long double>
	{};

	Tpl<Typ T> cexp bool not_enum_rvalue_reference_v
		= logic::and_v<not_enum<T>, not_rvalue_reference<T>>;

	// TODO: use is_detected_v
	Tpl<Typ T> using is_integral_arith
		= void_t<decltype(T{} * T{}), decltype(+T{})&, decltype(T{} % 1)>;
	Tpl<Typ T> cexp bool is_integral_impl
		= is_detected_v<is_integral_arith,
			  T> and not_enum_rvalue_reference_v<T>;

	Tpl<Typ T, bool = is_integral_impl<T>> struct is_integral_aux : false_
	{};
	Tpl<Typ T> struct is_integral_aux<T, true> : true_
	{};

	Tpl<Typ T> using is_arithmetic_arith
		= void_t<decltype(T{} * T{}), decltype(+T{})&>;

	// XXX: is_arithmetic_arith will allow std::complex, which is not arithmetic
	// type(If T is an arithmetic type (that is, an integral type or a
	// floating-point type) or a cv-qualified version thereof, provides the
	// member constant value equal true. For any other type, value is false.) ,
	// so add is_class to check.
	Tpl<Typ T> cexp bool is_arithmetic_impl
		= is_detected_v<is_arithmetic_arith,
			  T> and not_class_v<T> and not_enum_rvalue_reference_v<T>;

	Tpl<Typ T, bool = is_arithmetic_impl<T>> struct is_arithmetic_aux : false_
	{};

	Tpl<Typ T> struct is_arithmetic_aux<T, true> : true_
	{};

	Tpl<Typ T, bool = is_referenceable_v<T>> struct add_rvalue_reference_impl
		: type_identity<T&&>
	{};
	Tpl<Typ T> struct add_rvalue_reference_impl<T, false> : type_identity<T>
	{};

	Tpl<Typ T, bool = is_referenceable_v<T>> struct add_lvalue_reference_impl
		: type_identity<T&>
	{};
	Tpl<Typ T> struct add_lvalue_reference_impl<T, false> : type_identity<T>
	{};

	Tpl<Typ T> char
	test(int T::*);
	struct two
	{
		char c[2];
	};
	Tpl<Typ T> two
	test(...);

	Tpl<Typ T> struct is_pointer_helper : false_
	{};
	Tpl<Typ T> struct is_pointer_helper<T*> : true_
	{};

	Tpl<Typ T, bool is_function_type = false> struct add_pointer
		: type_identity<remove_reference_t<T>*>
	{};

	Tpl<Typ T> struct add_pointer<T, true> : type_identity<T>
	{};

	Tpl<Typ T, Typ... Args> struct add_pointer<T(Args...), true>
		: type_identity<T (*)(Args...)>
	{};

	Tpl<Typ T, Typ... Args> struct add_pointer<T(Args..., ...), true>
		: type_identity<T (*)(Args..., ...)>
	{};

	Tpl<Typ T, Typ... _Args> struct is_nt_constructible_impl
		: bool_<noexcept(T(declval<_Args>()...))>
	{};

	Tpl<Typ T, Typ _Arg> struct is_nt_constructible_impl<T, _Arg>
		: bool_<noexcept(static_cast<T>(declval<_Arg>()))>
	{};

	Tpl<Typ T> struct is_nt_constructible_impl<T>
		: is_nothrow_default_constructible<T>
	{};

} // namespace Rider::Faiz::detail

namespace Rider::Faiz
{

	Tpl<Typ... B> using conjunction = logic::and_<B...>;
	Tpl<Typ... B> inline cexp bool conjunction_v = conjunction<B...>::value;

	Tpl<Typ... B> using disjunction = logic::or_<B...>;
	Tpl<Typ... B> inline cexp bool disjunction_v = disjunction<B...>::value;

	Tpl<Typ B> using negation = logic::not_<B>;
	Tpl<Typ B> inline cexp bool negation_v = negation<B>::value;

	// clang-format on

	Tpl<Typ T> struct remove_reference : type_identity<T>
	{};
	Tpl<Typ T> struct remove_reference<T&> : type_identity<T>
	{};
	Tpl<Typ T> struct remove_reference<T&&> : type_identity<T>
	{};

	Tpl<Typ T> struct remove_const : type_identity<T>
	{};
	Tpl<Typ T> struct remove_const<const T> : type_identity<T>
	{};

	Tpl<Typ T> struct remove_volatile : type_identity<T>
	{};

	Tpl<Typ T> struct remove_volatile<volatile T> : type_identity<T>
	{};

	Tpl<Typ T> struct remove_extent : type_identity<T>
	{};

	Tpl<Typ T> struct remove_extent<T[]> : type_identity<T>
	{};

	Tpl<Typ T, size_t N> struct remove_extent<T[N]> : type_identity<T>
	{};
	Tpl<Typ T> using remove_extent_t = _t<remove_extent<T>>;

	Tpl<Typ T> struct remove_all_extents;
	Tpl<Typ T> using remove_all_extents_t = _t<remove_all_extents<T>>;

	Tpl<Typ T> struct remove_all_extents : type_identity<T>
	{};
	Tpl<Typ T> struct remove_all_extents<T[]>
		: type_identity<remove_all_extents_t<T>>
	{};
	Tpl<Typ T, size_t V> struct remove_all_extents<T[V]>
		: type_identity<remove_all_extents_t<T>>
	{};

	// Provides the member typedef type which is the type pointed to by T,
	// or, if T is not a pointer, then type is the same as T.
	// ```cpp
	// print_is_same<int, remove_pointer<int*>::type>();  // true
	// print_is_same<int, remove_pointer<int**>::type>(); // false
	// ```
	Tpl<Typ T> struct remove_pointer : type_identity<T>
	{};
	Tpl<Typ T> struct remove_pointer<T*> : type_identity<T>
	{};
	Tpl<Typ T> struct remove_pointer<T* const> : type_identity<T>
	{};
	Tpl<Typ T> struct remove_pointer<T* volatile> : type_identity<T>
	{};
	Tpl<Typ T> struct remove_pointer<T* const volatile> : type_identity<T>
	{};

	Tpl<Typ T> using remove_cv_t = _t<remove_cv<T>>;
	Tpl<Typ T> using remove_const_t = _t<remove_const<T>>;
	Tpl<Typ T> using remove_volatile_t = _t<remove_volatile<T>>;
	Tpl<Typ T> using remove_extent_t = _t<remove_extent<T>>;
	Tpl<Typ T> using remove_pointer_t = _t<remove_pointer<T>>;

	// If the type **T** is a reference type, provides the member typedef
	// type which is the type referred to by
	// **T** with its topmost cv-qualifiers removed. Otherwise type is **T**
	// with its topmost cv-qualifiers removed.
	//
	// Removing cvref does most of what decay does, but doesn't convert
	// function types and array types to pointers.

	Tpl<Typ T> struct remove_cvref : remove_cv<remove_reference_t<T>>
	{};

	// Tpl<Typ T> using remove_cvref_t = typename remove_cvref<T>::type;

	/******************** is *********************/

	// If T and U name the same type (including const/volatile
	// qualifications), provides the member constant value equal to true.
	// Otherwise value is false.
	Tpl<Typ T, Typ U> struct is_same : false_
	{};

	// If T and U name the same type (including const/volatile
	// qualifications), provides the member constant value equal to true.
	// Otherwise value is false.
	Tpl<Typ T> struct is_same<T, T> : true_
	{};

	Tpl<Typ T> struct is_null_pointer : is_same<nullptr_t, remove_cv_t<T>>
	{};
	IS(null_pointer)

	Tpl<Typ T> struct is_const : false_
	{};
	Tpl<Typ T> struct is_const<const T> : true_
	{};

	IS_NOT_ARE_ANY(const)

	Tpl<Typ T> struct is_reference : false_
	{};
	Tpl<Typ T> struct is_reference<T&> : true_
	{};
	Tpl<Typ T> struct is_reference<T&&> : true_
	{};

	IS_NOT_ARE_ANY(reference)

	Tpl<Typ T> struct is_function
		: bool_<not_const_v<T const> and not_reference_v<T>>
	{};

	Tpl<Typ T> struct is_volatile : false_
	{};
	Tpl<Typ T> struct is_volatile<T volatile> : true_
	{};
	Tpl<Typ T, size_t N> struct is_volatile<T volatile[N]> : true_
	{};
	Tpl<Typ T> struct is_volatile<T volatile[]> : true_
	{};

	IS_NOT_ARE_ANY(volatile)

	IS_NOT_ARE_ANY(function)

	Tpl<Typ T> struct is_floating_point : detail::is_floating_point_aux<T>
	{};

	IS_NOT_ARE_ANY(floating_point)

	// Checks whether T is an integral type. Provides the member constant
	// value which is equal to true, if T is the type bool, char, char16_t,
	// char32_t, wchar_t, short, int, long, long long, or any
	// implementation-defined extended integer types, including any signed,
	// unsigned, and cv-qualified variants. Otherwise, value is equal to
	// false.

	Tpl<Typ T> struct is_integral : detail::is_integral_aux<T>
	{};

	IS_NOT_ARE_ANY(integral)

	//  If T is an arithmetic type (that is, an integral type or a
	// floating-point type) or a cv-qualified version thereof, provides the
	// member constant value equal true. For any other type, value is false.
	//
	// gcc may complains, latest msvc/clang compiles happily
	Tpl<Typ T> struct is_arithmetic : detail::is_arithmetic_aux<T>
	{};

	IS_NOT_ARE_ANY(arithmetic)

	// Checks whether T is **a pointer to object** or **a pointer to
	// function** (but not a pointer to member/member function). Provides
	// the member constant value which is equal to true, if T is a
	// object/function pointer type. Otherwise, value is equal to false.
	Tpl<Typ T> struct is_pointer : detail::is_pointer_helper<remove_cv_t<T>>
	{};

	IS_NOT_ARE_ANY(pointer)

	Tpl<Typ T> struct is_lvalue_reference : false_
	{};
	Tpl<Typ T> struct is_lvalue_reference<T&> : true_
	{};

	Tpl<Typ T> struct is_rvalue_reference : false_
	{};
	Tpl<Typ T> struct is_rvalue_reference<T&&> : true_
	{};

	Tpl<Typ T> struct is_object
		: bool_<not_reference_v<T> and not_void_v<T> and not_function_v<T>>
	{};

	Tpl<Typ T, unsigned N = 0> struct extent : size_t_<0>
	{};

	Tpl<Typ T> struct extent<T[], 0> : size_t_<0>
	{};

	Tpl<Typ T, unsigned N> struct extent<T[], N> : extent<T, N - 1>
	{};

	Tpl<Typ T, size_t I> struct extent<T[I], 0> : size_t_<I>
	{};

	Tpl<Typ T, size_t I, unsigned N> struct extent<T[I], N> : extent<T, N - 1>
	{};
	Tpl<Typ T, unsigned N = 0> inline cexp size_t extent_v
		= extent<T, N>::value;

	//  If T is an object type or a function type that has no cv- or ref-
	//  qualifier (since C++17), provides a member typedef type which is T&. If
	//  T is an rvalue reference to some type U, then type is U&. Otherwise,
	//  type is T.

	// FIXME: add_rvalue_reference_impl is a workaround here, otherwise forward
	// declaration can also not solve type dependency.
	Tpl<Typ T> struct add_rvalue_reference
		: detail::add_rvalue_reference_impl<T>
	{};

	Tpl<Typ T> struct add_lvalue_reference
		: detail::add_lvalue_reference_impl<T>
	{};

	// Converts any type **T** to a reference type, making it possible to
	// use member functions in *decltype* expressions without the need to go
	// through constructors.
	//
	// *declval* is commonly used in tpls where acceptable Tpl
	// parameters may have no constructor in common, but have the same
	// member function whose return type is needed.
	//
	// Note that because no definition exists for *declval*, it can only be
	// used in unevaluated contexts; it is an error to evaluate an
	// expression that contains this function. Formally, the program is
	// *ill-formed* if this function is *odr-used*.
	//
	// Cannot be called and thus never returns a value. The return type is
	// T&& unless **T** is (possibly cv-qualified) void, in which case the
	// return type is **T**.
	//
	// return `add_rvalue_reference_t<T>` is for reference collapsing

	Tpl<Typ T> struct add_cv : type_identity<const volatile T>
	{};

	Tpl<Typ T> struct add_const : type_identity<const T>
	{};

	Tpl<Typ T> struct add_volatile : type_identity<volatile T>
	{};

	Tpl<Typ T> using add_cv_t = _t<add_cv<T>>;
	Tpl<Typ T> using add_const_t = _t<add_const<T>>;
	Tpl<Typ T> using add_volatile_t = _t<add_volatile<T>>;

	// p0007r0
	// https://stackoverflow.com/questions/34566063/why-is-the-const-overload-of-as-const-deleted
	// Forms lvalue reference to const type of t
	Tpl<Typ T> cexp add_const_t<T>&
	as_const(T& t) noexcept;

	//  const rvalue reference overload is deleted to disallow rvalue arguments
	Tpl<Typ T> void
	as_const(const T&&)
		= delete;

	// TODO: check whether standard has as_cref_t
	Tpl<Typ T, Typ R = remove_reference_t<T>> using as_cref_t
		= add_lvalue_reference_t<add_const_t<R>>;

	Tpl<Typ T> struct is_signed : detail::is_signed_impl<is_arithmetic_v<T>, T>
	{};

	// using std::is_signed_v;
	// IS_NOT_ARE_ANY(signed)
	IS_NOT_ARE_ANY(signed)

	Tpl<Typ T> cexp bool is_unsigned_v = not_signed_v<T> and is_arithmetic_v<T>;
	Tpl<Typ T> struct is_unsigned : bool_<is_unsigned_v<T>>
	{};

	// TODO: add support for compiler extension larger signed.
	// XXX: I don't want support non-standard extension now.
	Tpl<Typ T> struct make_signed
	{
	private:
		static_assert((is_integral_v<T> or is_enum_v<T>),
			"The Tpl argument to make_signed must be an integer or enum "
			"type.");
		static_assert(not_same_v<remove_cv_t<T>, bool>,
			"The Tpl argument to make_signed must not be the type bool.");

		using t_no_cv = remove_cv_t<T>;

		static cfn
		base_integer_type_impl()
		{
			cIf(is_signed_v<
					T> and is_integral_v<T> and not_any_v<t_no_cv, char, wchar_t, bool>)
			{
				return type_identity<T>{};
			}
			cElseIf(
				is_integral_v<T> and not_any_v<t_no_cv, char, wchar_t, bool>)
			{
				cIf(is_same_v<t_no_cv, unsigned char>)
				{
					return type_identity<signed char>{};
				}
				cElseIf(is_same_v<t_no_cv, unsigned short>)
				{
					return type_identity<signed short>{};
				}
				cElseIf(is_same_v<t_no_cv, unsigned int>)
				{
					return type_identity<int>{};
				}
				cElseIf(is_same_v<t_no_cv, unsigned long>)
				{
					return type_identity<long>{};
				}
				else
				{
					return type_identity<long long>{};
				}
			}
			else
			{
				cIf(sizeof(t_no_cv) == sizeof(unsigned char))
				{
					return type_identity<signed char>{};
				}
				cElseIf(sizeof(t_no_cv) == sizeof(unsigned short))
				{
					return type_identity<signed short>{};
				}
				cElseIf(sizeof(t_no_cv) == sizeof(unsigned int))
				{
					return type_identity<int>{};
				}
				cElseIf(sizeof(t_no_cv) == sizeof(unsigned long))
				{
					return type_identity<long>{};
				}
				else
				{
					return type_identity<long long>{};
				}
			}
		}
		using base_integer_type = _t<decltype(base_integer_type_impl())>;

		// Add back any const qualifier:
		using const_base_integer_type = meta::
			if_<is_const<T>, add_const_t<base_integer_type>, base_integer_type>;

	public:
		// Add back any volatile qualifier:
		using type = meta::if_<is_volatile<T>,
			add_volatile_t<const_base_integer_type>,
			const_base_integer_type>;
	};

	Tpl<Typ T> struct make_unsigned
	{
	private:
		static_assert(is_integral_v<T> or is_enum_v<T>,
			"The Tpl argument to make_unsigned must be an integer or enum "
			"type.");
		static_assert(not_same_v<remove_cv_t<T>, bool>,
			"The Tpl argument to make_unsigned must not be the type "
			"bool");

		using t_no_cv = remove_cv_t<T>;

		// clang-format off
		static cfn
		base_integer_type_impl()
		{
			cIf(is_unsigned_v<T> and
						 is_integral_v<T> and
						 not_any_v<t_no_cv, char, wchar_t, bool>)
			{
				return type_identity<T>{};
			}
			cElseIf(is_integral_v<T> and
							  not_any_v<t_no_cv, char, wchar_t, bool>)
			{
				cIf(is_same_v<t_no_cv, signed char>)
				{
					return type_identity<unsigned char>{};
				}
				cElseIf(is_same_v<t_no_cv, short>)
				{
					return type_identity<unsigned short>{};
				}
				cElseIf(is_same_v<t_no_cv, int>)
				{
					return type_identity<unsigned int>{};
				}
				cElseIf(is_same_v<t_no_cv, long>)
				{
					return type_identity<unsigned long>{};
				}
				else
				{
					return type_identity<unsigned long long>{};
				}
			}
			else
			{
				cIf(sizeof(t_no_cv) == sizeof(unsigned char))
				{
					return type_identity<unsigned char>{};
				}
				cElseIf(sizeof(t_no_cv) == sizeof(unsigned short))
				{
					return type_identity<unsigned short>{};
				}
				cElseIf(sizeof(t_no_cv) == sizeof(unsigned int))
				{
					return type_identity<unsigned int>{};
				}
				cElseIf(sizeof(t_no_cv) == sizeof(unsigned long))
				{
					return type_identity<unsigned long>{};
				}
				else
				{
					return type_identity<unsigned long long>{};
				}
			}
		}

		// clang-format on

		using base_integer_type = _t<decltype(base_integer_type_impl())>;

		using const_base_integer_type = meta::
			if_<is_const<T>, add_const_t<base_integer_type>, base_integer_type>;

	public:
		// Add back any volatile qualifier:
		using type = meta::if_<is_volatile<T>,
			add_volatile_t<const_base_integer_type>,
			const_base_integer_type>;
	};

	Tpl<Typ T> using make_unsigned_t = _t<make_unsigned<T>>;

	Tpl<Typ T> using make_signed_t = _t<make_signed<T>>;

	Tpl<bool B, Typ T> struct enable_if
	{};

	Tpl<Typ T> struct enable_if<true, T> : type_identity<T>
	{};

	Tpl<Typ T> inline cexp bool is_array_v = false;
	Tpl<Typ T> inline cexp bool is_array_v<T[]> = true;
	Tpl<Typ T, size_t N> inline cexp bool is_array_v<T[N]> = true;

	Tpl<Typ T> struct is_array : bool_<is_array_v<T>>
	{};

	NOT_ARE_ANY(array)

	// If the imaginary function definition tTo test() { return
	// std::declval<tFrom>(); } is well-formed, (that is, either
	// std::declval<tFrom>() can be converted to tTo using implicit conversions,
	// or both tFrom and tTo are possibly cv-qualified void), provides the
	// member constant value equal to true. Otherwise value is false. For the
	// purposes of this check, the use of std::declval in the return statement
	// is not considered an odr-use.
	//
	//  Access checks are performed as if from a context unrelated to either
	// type. Only the validity of the immediate context of the expression in the
	// return statement (including conversions to the return type) is
	// considered.
	// // using namespace logic;
	// Tpl<Typ tFrom, Typ tTo>
	// cexp bool is_convertible_v = or_<
	// 	and_<or_<is_void<tFrom>, is_function<tTo>, is_array<tTo>>,
	// is_void<tTo>>, 	is_detected<is_convertible_helper, tFrom, tTo>>::value;
	//
	Tpl<Typ tTo1> static void test_aux(tTo1) noexcept;

	Tpl<Typ tFrom, Typ tTo> using is_convertible_helper
		= decltype(test_aux<tTo>(declval<tFrom>()));

	Tpl<Typ tFrom, Typ tTo> cfn
	my_is_convertible()
	{
		cIf(disjunction_v<is_void<tFrom>, is_function<tTo>, is_array<tTo>>)
		{
			return is_void<tTo>();
		}
		else
		{
			return is_detected<is_convertible_helper, tFrom, tTo>();
		}
	}

	Tpl<Typ tFrom, Typ tTo> struct is_convertible
		: decltype(my_is_convertible<tFrom, tTo>())
	{};

	BI_IS_NOT_ARE_ANY(convertible)
	BI_NOT_ALL(convertible)

	// TODO: use detection idiom implement is_convertible
	// If T is an object or reference type and the variable definition T
	// obj(std::declval<Args>()...); is well-formed, provides the member
	// constant value equal to true. In all other cases, value is false.

	// FIXME: the codes below cannot pass tests somehow
	// Tpl<Typ T, class, class...>
	// struct is_constructible_impl : false_
	// {};
	// Tpl<Typ T, class... Us>
	// struct is_constructible_impl<T,
	// 	decltype(::new(declval<void*>()) T(declval<Us&>()...)),
	// 	Us...> : true_
	// {};
	// Tpl<Typ T, class... Us>
	// struct is_constructible : is_constructible_impl<T, void, Us...>
	// {};

	Tpl<Typ Tp> struct is_copy_constructible
		: is_constructible<Tp, as_cref_t<Tp>>
	{};

	IS_NOT_ARE_ANY(copy_constructible)

	Tpl<Typ T> inline cexp size_t rank_v = 0;
	Tpl<Typ T> inline cexp size_t rank_v<T[]> = 1u + rank_v<T>;
	Tpl<Typ T, size_t N> inline cexp size_t rank_v<T[N]> = 1u + rank_v<T>;

	Tpl<Typ T> struct rank : size_t_<rank_v<T>>
	{};

	// If **T** is a reference type, then provides the member typedef type
	// which is a pointer to the referred type. Otherwise, if **T** names an
	// object type, a function type that is not **cv-** or **ref-qualified**
	// (since C++17), or a (possibly **cv-qualified**) void type, provides
	// the member typedef type which is the type T*. Otherwise (if T is a
	// **cv-** or **ref-qualified** function type), provides the member
	// typedef type which is the type **T**
	//
	// * type:	pointer to T or to the type referenced by T
	Tpl<Typ T> struct add_pointer : detail::add_pointer<T, is_function_v<T>>
	{};
	Tpl<Typ T> using add_pointer_t = _t<add_pointer<T>>;

	Tpl<Typ T, Typ U = remove_reference_t<T>, Typ E = remove_cv_t<U>> fn
	decay_impl()
	{
		cIf(not_referenceable_v<T>)
		{
			return type_identity<E>();
		}
		cElseIf(is_array_v<U>)
		{
			return type_identity<add_pointer_t<remove_extent_t<U>>>();
		}
		cElseIf(is_function_v<U>)
		{
			return type_identity<add_pointer_t<U>>();
		}
		cElse
		{
			return type_identity<E>();
		}
	}

	Tpl<Typ T> struct decay : decltype(decay_impl<T>())
	{};

	Tpl<Typ T> using decay_t = _t<decay<T>>;

	Tpl<Typ T, Typ Arg> struct is_assignable
		: detail::is_assignable_imp<T, Arg, void>
	{};

	// If Derived is derived from Base or if both are the same non-union class
	// (in both cases ignoring cv-qualification), provides the member constant
	// value equal to true. Otherwise value is false.
	//
	//  If both Base and Derived are non-union class types, and they are not the
	// same type (ignoring cv-qualification), Derived shall be a complete type;
	// otherwise the behavior is undefined.
	//
	// Faiz::is_base_of<A, B>::value is true even if A is a private, protected,
	// or ambiguous base class of B. In many situations,
	// Faiz::is_convertible<B*, A*> is the more appropriate test.
	//
	// Although no class is its own base, Faiz::is_base_of<T, T>::value is true
	// because the intent of the trait is to model the "is-a" relationship, and
	// T is a T. Despite that, Faiz::is_base_of<int, int>::value is false
	// because only classes participate in the relationship that this trait
	// models.
	Tpl<Typ Base, Typ Derived> struct is_base_of
		: meta::if_<logic::and_<is_class<Base>, is_class<Derived>>,
			  detail::pre_is_base_of2<Base, Derived>,
			  false_>
	{};
	Tpl<Typ Base, Typ Derived> inline cexp bool is_base_of_v
		= is_base_of<Base, Derived>::value;

	Tpl<Typ T> struct is_member_pointer
		: detail::is_member_pointer_helper<remove_cv_t<T>>
	{};

	IS(member_pointer)
	// If `T` is a scalar type (that is a possibly **cv-qualified** arithmetic,
	// pointer, pointer to member, enumeration, or `Faiz::nullptr_t` type),
	// provides the member constant value equal `true`. For any other type,
	// value is `false`.
	//
	// Each individual memory location in the C++ memory model, including the
	// hidden memory locations used by language features (e.g virtual table
	// pointer), has scalar type (or is a sequence of adjacent bit-fields of
	// non-zero length). Sequencing of side-effects in expression evaluation,
	// interthread synchronization, and dependency ordering are all defined in
	// terms of individual scalar objects.
	//
	Tpl<Typ T> struct is_scalar : disjunction<is_arithmetic<T>,
									  is_enum<T>,
									  is_pointer<T>,
									  is_member_pointer<T>,
									  is_null_pointer<T>>
	{};

	IS_NOT_ARE_ANY(scalar)

	Tpl<Typ T> struct is_member_function_pointer_helper : false_
	{};

	Tpl<Typ T, Typ U> struct is_member_function_pointer_helper<T U::*>
		: is_function<T>
	{};
	//
	//  Checks whether T is a non-static member function pointer. Provides the
	// member constant value which is equal to true, if T is a non-static member
	// function pointer type. Otherwise, value is equal to false.
	Tpl<Typ T> struct is_member_function_pointer
		: is_member_function_pointer_helper<remove_cv_t<T>>
	{};

	IS_NOT_ARE_ANY(member_function_pointer)

	// Checks whether T is a non-static member object. Provides the member
	// constant value which is equal to true, if T is a non-static member object
	// type. Otherwise, value is equal to false.
	Tpl<Typ T> struct is_member_object_pointer
		: bool_<is_member_pointer_v<T> and not_member_function_pointer_v<T>>
	{};

	IS_NOT_ARE_ANY(member_object_pointer)

	// Given two (possibly identical) types Base and Derived,
	// is_base_of<Base, Derived>::value == true if and only if Base is a
	// direct or indirect base class of Derived. This is like
	// is_base_of<Base, Derived> but returns false if Derived is the same as
	// Base. So is_derived is true only if Derived is actually a subclass of
	// Base and not Base itself.
	//
	// is_derived may only be applied to complete types.
	//
	// Example usage:
	//     is_derived<int, int>::value             => false
	//     is_derived<int, bool>::value            => false
	//     is_derived<Parent, Child>::value        => true
	//     is_derived<Child, Parent>::value        => false
	Tpl<Typ Base, Typ Derived> struct is_derived
		: bool_<
			  is_base_of_v<Base,
				  Derived> and not_same_v<remove_cv_t<Base>, remove_cv_t<Derived>>>
	{};

	BI_IS_NOT_ARE_ANY(derived)

	Tpl<Typ T> struct is_polymorphic : detail::is_polymorphic_impl<T, void*>
	{};

	IS(polymorphic)

	Tpl<Typ T> struct is_copy_assignable
		: is_assignable<add_lvalue_reference_t<T>, as_cref_t<T>>
	{};

	IS_NOT_ARE_ANY(copy_assignable)

	// Tpl<Typ... T >
	// inline cexp bool are_copy_assignable_v = (is_copy_assignable_v<T> &&
	// ...);

	// Tpl<Typ... T>
	// struct are_copy_assignable : bool_<are_copy_assignable_v<T...>>
	// {
	// };

	Tpl<Typ T> struct is_trivially_copy_assignable
		: is_trivially_assignable<add_lvalue_reference_t<T>, as_cref_t<T>>
	{};

	IS_NOT_ARE_ANY(trivially_copy_assignable)

	Tpl<bool, Typ T, Typ A> struct is_nothrow_assignable_aux;

	Tpl<Typ T, Typ A> struct is_nothrow_assignable_aux<false, T, A> : false_
	{};

	Tpl<Typ T> using move_assignment_t
		= decltype(declval<T&>() = declval<T&&>());

	Tpl<Typ T> inline cexp bool is_move_assignable_v
		= is_detected_v<move_assignment_t, T>;

	Tpl<Typ T> struct is_move_assignable : bool_<is_move_assignable_v<T>>
	{};

	NOT_ARE_ANY(move_assignable)

	// // clang-format off
	// Tpl<Typ T>
	// inline cexp bool is_nothrow_move_assignable_v
	// 	= is_move_assignable_v<T> and noexcept(declval<T&>() = declval<T&&>());
	// // clang-format on

	// Tpl<Typ T>
	// struct is_nothrow_move_assignable :
	// bool_<is_nothrow_move_assignable_v<T>>
	// {};

	Tpl<Typ T> struct is_nothrow_move_assignable
		: is_nothrow_assignable<add_lvalue_reference_t<T>,
			  add_rvalue_reference_t<T>>
	{};

	IS_NOT_ARE_ANY(nothrow_move_assignable)

	Tpl<Typ T, Typ A> struct is_nothrow_assignable_aux<true, T, A>
		: bool_<noexcept(declval<T>() = declval<A>())>
	{};

	Tpl<Typ T, Typ A> struct is_nothrow_assignable
		: is_nothrow_assignable_aux<is_assignable_v<T, A>, T, A>
	{};

	Tpl<Typ T> struct is_nothrow_copy_assignable
		: is_nothrow_assignable<add_lvalue_reference_t<T>, as_cref_t<T>>
	{};

	IS_NOT_ARE_ANY(nothrow_copy_assignable)

	Tpl<Typ T> struct is_unknown_bound_array : false_
	{};
	Tpl<Typ T> struct is_unknown_bound_array<T[]> : true_
	{};

	IS_NOT_ARE_ANY(unknown_bound_array)

	Tpl<Typ T, Typ U = remove_all_extents_t<T>> using has_dtor
		= decltype(declval<U>().~U());

	// clang-format off
    // FIXME \\\ fix compiler.....  gcc has bug here, I post a thread here: https://stackoverflow.com/questions/53456848/implement-is-destructible-with-detected-idiomhttps://stackoverflow.com/questions/53456848/implement-is-destructible-with-detected-idiom
    // Tpl<Typ T>
    // cexp bool is_destructible_v =
    // 	is_reference_v<T> || (!(is_void_v<T> || is_function_v<T> || is_unknown_bound_array_v<T>) and is_object_v<T> and is_detected_v<has_dtor, T>);

 #if BOOST_COMP_CLANG or BOOST_COMP_MSVC
    Tpl<Typ T>
    cexp bool is_destructible_v =
        (is_detected_v<has_dtor,
           T>
       or is_reference_v<T>)
    and not_unknown_bound_array_v<T>
        and not_function_v<T>;

 #elif BOOST_COMP_GNUC
    Tpl<Typ T>
    cexp bool
    my_is_destructible()
    {
        cIf(is_reference_v<T>)
        {
            return true;
        }
        cElseIf(
            is_same_v<remove_cv_t<T>,
                void> || is_function_v<T> || is_unknown_bound_array<T>::value)
        {
            return false;
        }
        cElseIf(is_object_v<T>)
        {
            return is_detected_v<has_dtor, T>;
        }
        else
        {
            return false;
        }
    }
    Tpl<Typ T>
    cexp bool is_destructible_v = my_is_destructible<T>();
 #endif
	// clang-format on
	Tpl<Typ T> struct is_destructible : bool_<is_destructible_v<T>>
	{};

	Tpl<Typ T> inline cexp bool is_nothrow_destructible_v
		= is_destructible_v<T> and noexcept(is_destructible_v<T>);
	Tpl<Typ T> struct is_nothrow_destructible
		: bool_<is_nothrow_destructible_v<T>>
	{};

	Tpl<Typ T> struct alignment_of : size_t_<alignof(T)>
	{};

#ifdef _WIN32
	enum class endian
	{
		little = 0,
		big = 1,
		native = little
	};
#endif

	/// is_nothrow_constructible
	Tpl<Typ T, Typ... _Args> struct is_nothrow_constructible
		: logic::and_<is_constructible<T, _Args...>,
			  detail::is_nt_constructible_impl<T, _Args...>>
	{};

	Tpl<Typ T,
		bool = is_referenceable_v<T>> struct is_nothrow_copy_constructible_impl;

	Tpl<Typ T> struct is_nothrow_copy_constructible_impl<T, false> : false_
	{};

	Tpl<Typ T> struct is_nothrow_copy_constructible_impl<T, true>
		: is_nothrow_constructible<T, const T&>
	{};

	/// is_nothrow_copy_constructible
	Tpl<Typ T> struct is_nothrow_copy_constructible
		: is_nothrow_copy_constructible_impl<T>
	{};

	IS_NOT_ARE_ANY(nothrow_copy_constructible)

	namespace detail
	{
		Tpl<Typ T,
			bool
			= is_referenceable_v<T>> struct is_nothrow_move_constructible_impl;

		Tpl<Typ T> struct is_nothrow_move_constructible_impl<T, false> : false_
		{};

		Tpl<Typ T> struct is_nothrow_move_constructible_impl<T, true>
			: is_nothrow_constructible<T, T&&>
		{};
	} // namespace detail

	Tpl<Typ T, Typ... _Args> constexpr bool is_nothrow_constructible_v
		= is_nothrow_constructible<T, _Args...>::value;

	PACK_ARE(nothrow_constructible)

	Tpl<Typ T> struct is_nothrow_move_constructible
		: detail::is_nothrow_move_constructible_impl<T>
	{};
	IS_NOT_ARE_ANY(nothrow_move_constructible)
} // namespace Rider::Faiz

#endif