/*
 * BSD 2-Clause License
 *
 * Copyright (c) 2020, Agnieszka Cicha-Cisek
 * Copyright (c) 2020, Patryk Cisek
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * 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.
*/

#include "authenticationexception.h"
#include "nitrokeyimpl.h"

#include "libnitrokey/NitrokeyManager.h"
#include "libnitrokey/DeviceCommunicationExceptions.h"

#include <boost/log/trivial.hpp>

NitrokeyImpl::NitrokeyImpl(int logLevel)
    : keyManager{nitrokey::NitrokeyManager::instance()}
{
    keyManager->set_loglevel(5 - logLevel);
}

bool NitrokeyImpl::connect()
{
    return keyManager->connect();
}

bool NitrokeyImpl::isConnected() const
{
    return keyManager->is_connected();
}

bool NitrokeyImpl::firstAuth(const std::string &adminPin, const std::string &tempPassword)
{
    try {
        return keyManager->first_authenticate(adminPin.c_str(), tempPassword.c_str());
    } catch (const CommandFailedException &e) {
        BOOST_LOG_TRIVIAL(error) << "firstAuth(). Got error: " << e.what();
        throw AuthenticationException(e.what());
    }
}

void NitrokeyImpl::userAuth(const std::string &userPin, const std::string &tempPassword)
{
    try {
        keyManager->user_authenticate(userPin.c_str(), tempPassword.c_str());
    } catch (const CommandFailedException &e) {
        BOOST_LOG_TRIVIAL(debug) << "userAuth(). Got error: " << e.what();
        throw AuthenticationException(e.what());
    }
}

void NitrokeyImpl::writeTotpSlot(std::uint8_t slotNumber, const std::string &slotName,
                           const std::string &hexSecret, std::uint16_t timeWindow,
                           const std::string &temporaryPassword)
{
    constexpr bool USE_8_DIGITS = false;
    constexpr bool USE_ENTER = false;
    constexpr bool USE_TOKEN_ID = false;
    constexpr char *TOKEN_ID = nullptr;

    keyManager->write_TOTP_slot(slotNumber, slotName.c_str(), hexSecret.c_str(),
                                timeWindow, USE_8_DIGITS, USE_ENTER, USE_TOKEN_ID,
                                TOKEN_ID, temporaryPassword.c_str());
}

void NitrokeyImpl::eraseTotpSlot(std::uint8_t slotNumber,
                                  const std::string &temporaryPassword)
{
    keyManager->erase_totp_slot(slotNumber, temporaryPassword.c_str());
}

std::vector<TOTPSlot> NitrokeyImpl::getSlots() const
{
    std::vector<TOTPSlot> s;
    for (std::uint8_t i = 0; i < slotMaxIndex(); ++i) {
        try {
            s.push_back(getSlot(i));
        } catch (const DeviceCommunicationException &e) {
            BOOST_LOG_TRIVIAL(debug) << "DeviceCommunicationException exception thrown "
                      << "when trying to read slot number: "
                      << static_cast<unsigned>(i) << " is empty";
        } catch (const CommandFailedException &e) {
            BOOST_LOG_TRIVIAL(debug) << "CommandFailedExceptionexception thrown "
                      << "when trying to read slot number: "
                      << static_cast<unsigned>(i) << " is empty\n";
        }
    }

    return s;
}

std::string NitrokeyImpl::getSlotName(std::uint8_t slot) const
{
    // Internally libnitrokey uses strndup() (see strndup(3) for details)
    // to duplicate the name and returns this duplicated buffer to the
    // caller, thus we have to make sure, we're freeing the buffer using
    // free() (see free(3) for details) when no longer needed.
    auto slotNameCStr =
            std::unique_ptr<char, std::function<void(char*)>>
            (keyManager->get_totp_slot_name(slot), [](char* c) {
        std::free(c);
    });

    return std::string(slotNameCStr.get());
}

void NitrokeyImpl::setTime(std::uint64_t timestamp) const
{
    keyManager->set_time(timestamp);
}

void NitrokeyImpl::setTimeSoft(std::uint64_t timestamp) const
{
    keyManager->set_time_soft(timestamp);
}

std::string NitrokeyImpl::getTOTPCode(const TOTPSlot &slot,
                                       const std::string &tempPassword) const
{
    auto code = keyManager->get_TOTP_code(slot.slotNumber(), tempPassword.c_str());
    BOOST_LOG_TRIVIAL(debug) << "TOTP code for slot: " << static_cast<int>(slot.slotNumber())
                             << "\tslot name: " << slot.slotName()
                             << "\tcode: " << code;
    return code;
}

TOTPSlot NitrokeyImpl::getSlot(std::uint8_t slot) const
{
    return TOTPSlot(keyManager->get_totp_slot_name(slot), slot);
}
