/*
 *
 *    Copyright (c) 2021 Project CHIP Authors
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

#include "TxtFields.h"

#include <algorithm>
#include <cctype>
#include <climits>
#include <cstdio>
#include <inttypes.h>
#include <limits>
#include <stdlib.h>
#include <string.h>

#include <lib/core/CHIPSafeCasts.h>
#include <lib/dnssd/Advertiser.h>
#include <lib/dnssd/Resolver.h>
#include <lib/support/BytesToHex.h>
#include <lib/support/CHIPMemString.h>
#include <lib/support/SafeInt.h>

namespace chip {
namespace Dnssd {

namespace Internal {

constexpr uint8_t kTCPClient = 1;
constexpr uint8_t kTCPServer = 2;

namespace {

char SafeToLower(uint8_t ch)
{
    return static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
}
bool IsKey(const ByteSpan & key, const char * desired)
{
    if (key.size() != strlen(desired))
    {
        return false;
    }

    auto desired_bytes = Uint8::from_const_char(desired);
    for (size_t i = 0; i < key.size(); ++i)
    {
        if (SafeToLower(key.data()[i]) != SafeToLower(desired_bytes[i]))
        {
            return false;
        }
    }
    return true;
}

uint32_t MakeU32FromAsciiDecimal(const ByteSpan & val, uint32_t defaultValue = 0)
{
    // +1 because `digits10` means the number of decimal digits that fit in `uint32_t`,
    // not how many digits are enough to represent any `uint32_t`
    // +1 for null-terminator
    char nullTerminatedValue[std::numeric_limits<uint32_t>::digits10 + 2];

    // value is too long to store `uint32_t`
    if (val.size() >= sizeof(nullTerminatedValue))
        return defaultValue;

    // value contains leading zeros
    if (val.size() > 1 && *val.data() == static_cast<uint8_t>('0'))
        return defaultValue;

    Platform::CopyString(nullTerminatedValue, sizeof(nullTerminatedValue), val);

    char * endPtr;
    unsigned long num = strtoul(nullTerminatedValue, &endPtr, 10);

    if (endPtr > nullTerminatedValue && *endPtr == '\0' && num != ULONG_MAX && CanCastTo<uint32_t>(num))
        return static_cast<uint32_t>(num);

    return defaultValue;
}

uint16_t MakeU16FromAsciiDecimal(const ByteSpan & val)
{
    const uint32_t num = MakeU32FromAsciiDecimal(val);
    return CanCastTo<uint16_t>(num) ? static_cast<uint16_t>(num) : 0;
}

uint8_t MakeU8FromAsciiDecimal(const ByteSpan & val)
{
    const uint32_t num = MakeU32FromAsciiDecimal(val);
    return CanCastTo<uint8_t>(num) ? static_cast<uint8_t>(num) : 0;
}

bool MakeBoolFromAsciiDecimal(const ByteSpan & val)
{
    return val.size() == 1 && static_cast<char>(*val.data()) == '1';
}

std::optional<bool> MakeOptionalBoolFromAsciiDecimal(const ByteSpan & val)
{
    char character = static_cast<char>(*val.data());
    if (val.size() == 1 && ((character == '1') || (character == '0')))
    {
        return std::make_optional(character == '1');
    }
    return std::nullopt;
}

size_t GetPlusSignIdx(const ByteSpan & value)
{
    // First value is the vendor id, second (after the +) is the product.
    for (size_t i = 0; i < value.size(); ++i)
    {
        if (static_cast<char>(value.data()[i]) == '+')
        {
            return i;
        }
    }
    return value.size();
}

} // namespace

uint16_t GetProduct(const ByteSpan & value)
{
    size_t plussign = GetPlusSignIdx(value);
    if (plussign < value.size() - 1)
    {
        const uint8_t * productStrStart = value.data() + plussign + 1;
        size_t productStrLen            = value.size() - plussign - 1;
        return MakeU16FromAsciiDecimal(ByteSpan(productStrStart, productStrLen));
    }
    return 0;
}

uint16_t GetVendor(const ByteSpan & value)
{
    size_t plussign = GetPlusSignIdx(value);
    return MakeU16FromAsciiDecimal(ByteSpan(value.data(), plussign));
}

uint16_t GetLongDiscriminator(const ByteSpan & value)
{
    return MakeU16FromAsciiDecimal(value);
}

uint8_t GetCommissioningMode(const ByteSpan & value)
{
    return MakeU8FromAsciiDecimal(value);
}

uint32_t GetDeviceType(const ByteSpan & value)
{
    return MakeU32FromAsciiDecimal(value);
}

void GetDeviceName(const ByteSpan & value, char * name)
{
    Platform::CopyString(name, kMaxDeviceNameLen + 1, value);
}

void GetRotatingDeviceId(const ByteSpan & value, uint8_t * rotatingId, size_t * len)
{
    *len = Encoding::HexToBytes(reinterpret_cast<const char *>(value.data()), value.size(), rotatingId, kMaxRotatingIdLen);
}

uint16_t GetPairingHint(const ByteSpan & value)
{
    return MakeU16FromAsciiDecimal(value);
}

void GetPairingInstruction(const ByteSpan & value, char * pairingInstruction)
{
    Platform::CopyString(pairingInstruction, kMaxPairingInstructionLen + 1, value);
}

uint8_t GetCommissionerPasscode(const ByteSpan & value)
{
    return MakeBoolFromAsciiDecimal(value);
}

std::optional<System::Clock::Milliseconds32> GetRetryInterval(const ByteSpan & value)
{
    const auto undefined     = std::numeric_limits<uint32_t>::max();
    const auto retryInterval = MakeU32FromAsciiDecimal(value, undefined);

    if (retryInterval != undefined && retryInterval <= kMaxRetryInterval.count())
        return std::make_optional(System::Clock::Milliseconds32(retryInterval));

    return std::nullopt;
}

std::optional<System::Clock::Milliseconds16> GetRetryActiveThreshold(const ByteSpan & value)
{
    const auto retryInterval = MakeU16FromAsciiDecimal(value);

    if (retryInterval == 0)
    {
        return std::nullopt;
    }

    return std::make_optional(System::Clock::Milliseconds16(retryInterval));
}

TxtFieldKey GetTxtFieldKey(const ByteSpan & key)
{
    for (auto & info : txtFieldInfo)
    {
        if (IsKey(key, info.keyStr))
        {
            return info.key;
        }
    }
    return TxtFieldKey::kUnknown;
}

} // namespace Internal

void FillNodeDataFromTxt(const ByteSpan & key, const ByteSpan & val, CommissionNodeData & nodeData)
{
    TxtFieldKey keyType = Internal::GetTxtFieldKey(key);
    switch (keyType)
    {
    case TxtFieldKey::kLongDiscriminator:
        nodeData.longDiscriminator = Internal::GetLongDiscriminator(val);
        break;
    case TxtFieldKey::kVendorProduct:
        nodeData.vendorId  = Internal::GetVendor(val);
        nodeData.productId = Internal::GetProduct(val);
        break;
    case TxtFieldKey::kCommissioningMode:
        nodeData.commissioningMode = Internal::GetCommissioningMode(val);
        break;
    case TxtFieldKey::kDeviceType:
        nodeData.deviceType = Internal::GetDeviceType(val);
        break;
    case TxtFieldKey::kDeviceName:
        Internal::GetDeviceName(val, nodeData.deviceName);
        break;
    case TxtFieldKey::kRotatingDeviceId:
        Internal::GetRotatingDeviceId(val, nodeData.rotatingId, &nodeData.rotatingIdLen);
        break;
    case TxtFieldKey::kPairingInstruction:
        Internal::GetPairingInstruction(val, nodeData.pairingInstruction);
        break;
    case TxtFieldKey::kPairingHint:
        nodeData.pairingHint = Internal::GetPairingHint(val);
        break;
    case TxtFieldKey::kCommissionerPasscode:
        nodeData.supportsCommissionerGeneratedPasscode = Internal::GetCommissionerPasscode(val);
        break;
    default:
        FillNodeDataFromTxt(key, val, static_cast<CommonResolutionData &>(nodeData));
        break;
    }
}

void FillNodeDataFromTxt(const ByteSpan & key, const ByteSpan & value, CommonResolutionData & nodeData)
{
    switch (Internal::GetTxtFieldKey(key))
    {
    case TxtFieldKey::kSessionIdleInterval:
        nodeData.mrpRetryIntervalIdle = Internal::GetRetryInterval(value);
        break;
    case TxtFieldKey::kSessionActiveInterval:
        nodeData.mrpRetryIntervalActive = Internal::GetRetryInterval(value);
        break;
    case TxtFieldKey::kSessionActiveThreshold:
        nodeData.mrpRetryActiveThreshold = Internal::GetRetryActiveThreshold(value);
        break;
    case TxtFieldKey::kTcpSupported: {
        // bit 0 is reserved and deprecated
        uint8_t support            = Internal::MakeU8FromAsciiDecimal(value);
        nodeData.supportsTcpClient = (support & (1 << Internal::kTCPClient)) != 0;
        nodeData.supportsTcpServer = (support & (1 << Internal::kTCPServer)) != 0;
        break;
    }
    case TxtFieldKey::kLongIdleTimeICD:
        nodeData.isICDOperatingAsLIT = Internal::MakeOptionalBoolFromAsciiDecimal(value);
        break;
    default:
        break;
    }
}

} // namespace Dnssd
} // namespace chip
