/*
 *
 *    Copyright (c) 2021-2022 Project CHIP Authors
 *    All rights reserved.
 *
 *    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.
 */

/**
 *    @file
 *          Platform-specific key value storage implementation for SILABS
 */

#include "MigrationManager.h"
#include <crypto/CHIPCryptoPAL.h>
#include <platform/CHIPDeviceLayer.h>
#include <platform/KeyValueStoreManager.h>
#include <platform/silabs/SilabsConfig.h>
#include <stdio.h>
#include <string.h>

using namespace ::chip;
using namespace ::chip::Crypto;
using namespace ::chip::DeviceLayer::Internal;

#define CONVERT_KEYMAP_INDEX_TO_NVM3KEY(index) (SilabsConfig::kConfigKey_KvsFirstKeySlot + index)
#define CONVERT_NVM3KEY_TO_KEYMAP_INDEX(nvm3Key) (nvm3Key - SilabsConfig::kConfigKey_KvsFirstKeySlot)

namespace chip {
namespace DeviceLayer {
namespace PersistedStorage {

KeyValueStoreManagerImpl KeyValueStoreManagerImpl::sInstance;
uint16_t mKvsKeyMap[KeyValueStoreManagerImpl::kMaxEntries] = { 0 };

CHIP_ERROR KeyValueStoreManagerImpl::Init(void)
{
    CHIP_ERROR err;
    err = SilabsConfig::Init();
    SuccessOrExit(err);

    Silabs::MigrationManager::GetMigrationInstance().applyMigrations();

    memset(mKvsKeyMap, 0, sizeof(mKvsKeyMap));
    size_t outLen;
    err = SilabsConfig::ReadConfigValueBin(SilabsConfig::kConfigKey_KvsStringKeyMap, reinterpret_cast<uint8_t *>(mKvsKeyMap),
                                           sizeof(mKvsKeyMap), outLen);

    if (err == CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND) // Initial boot
    {
        err = CHIP_NO_ERROR;
    }

exit:
    return err;
}

bool KeyValueStoreManagerImpl::IsValidKvsNvm3Key(uint32_t nvm3Key) const
{
    return ((SilabsConfig::kConfigKey_KvsFirstKeySlot <= nvm3Key) && (nvm3Key <= SilabsConfig::kConfigKey_KvsLastKeySlot));
}

uint16_t KeyValueStoreManagerImpl::hashKvsKeyString(const char * key) const
{
    uint8_t hash256[Crypto::kSHA256_Hash_Length] = { 0 };
    Crypto::Hash_SHA256(reinterpret_cast<const uint8_t *>(key), strlen(key), hash256);

    uint16_t hash16 = 0, i = 0;

    while (!hash16 && (i < (Crypto::kSHA256_Hash_Length - 1)))
    {
        hash16 = (hash256[i] | (hash256[i + 1] << 8));
        i++;
    }
    return hash16;
}

CHIP_ERROR KeyValueStoreManagerImpl::MapKvsKeyToNvm3(const char * key, uint16_t hash, uint32_t & nvm3Key, bool isSlotNeeded) const
{
    CHIP_ERROR err;
    char * strPrefix           = nullptr;
    uint16_t firstEmptyKeySlot = kMaxEntries;
    for (uint16_t keyIndex = 0; keyIndex < kMaxEntries; keyIndex++)
    {
        if (mKvsKeyMap[keyIndex] == hash)
        {
            uint32_t tempNvm3key = CONVERT_KEYMAP_INDEX_TO_NVM3KEY(keyIndex);
            VerifyOrDie(IsValidKvsNvm3Key(tempNvm3key) == true);

            size_t readCount;
            size_t length = strlen(key);
            if (strPrefix == nullptr)
            {
                // Use a calloc to initialize all bits to 0. alloc +1 for a null char
                strPrefix = static_cast<char *>(Platform::MemoryCalloc(1, length + 1));
                VerifyOrDie(strPrefix != nullptr);
            }

            // Collision prevention
            // Read the data from NVM3 it should be prefixed by the kvsString
            // else we will look for another matching hash in the map
            SilabsConfig::ReadConfigValueBin(tempNvm3key, reinterpret_cast<uint8_t *>(strPrefix), length, readCount, 0);
            if (strcmp(key, strPrefix) == 0)
            {
                // String matches we have confirmed the hash pointed us the right key data
                nvm3Key = tempNvm3key;
                Platform::MemoryFree(strPrefix);
                return CHIP_NO_ERROR;
            }
        }

        if (isSlotNeeded && (firstEmptyKeySlot == kMaxEntries) && (mKvsKeyMap[keyIndex] == 0))
        {
            firstEmptyKeySlot = keyIndex;
        }
    }

    Platform::MemoryFree(strPrefix);

    if (isSlotNeeded)
    {
        if (firstEmptyKeySlot != kMaxEntries)
        {
            nvm3Key = CONVERT_KEYMAP_INDEX_TO_NVM3KEY(firstEmptyKeySlot);
            VerifyOrDie(IsValidKvsNvm3Key(nvm3Key) == true);
            err = CHIP_NO_ERROR;
        }
        else
        {
            err = CHIP_ERROR_PERSISTED_STORAGE_FAILED;
        }
    }
    else
    {
        err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
    }
    return err;
}

void KeyValueStoreManagerImpl::ForceKeyMapSave()
{
    OnScheduledKeyMapSave(nullptr, nullptr);
}

void KeyValueStoreManagerImpl::OnScheduledKeyMapSave(System::Layer * systemLayer, void * appState)
{
    SilabsConfig::WriteConfigValueBin(SilabsConfig::kConfigKey_KvsStringKeyMap, reinterpret_cast<const uint8_t *>(mKvsKeyMap),
                                      sizeof(mKvsKeyMap));
}

void KeyValueStoreManagerImpl::ScheduleKeyMapSave(void)
{
    /*
        During commissioning, the key map will be modified multiples times subsequently.
        Commit the key map in nvm once it as stabilized.
    */
    SystemLayer().StartTimer(
        std::chrono::duration_cast<System::Clock::Timeout>(System::Clock::Seconds32(SL_KVS_SAVE_DELAY_SECONDS)),
        KeyValueStoreManagerImpl::OnScheduledKeyMapSave, NULL);
}

CHIP_ERROR KeyValueStoreManagerImpl::_Get(const char * key, void * value, size_t value_size, size_t * read_bytes_size,
                                          size_t offset_bytes) const
{
    VerifyOrReturnError(key != nullptr, CHIP_ERROR_INVALID_ARGUMENT);

    uint32_t nvm3Key;
    uint16_t hash  = hashKvsKeyString(key);
    CHIP_ERROR err = MapKvsKeyToNvm3(key, hash, nvm3Key);
    VerifyOrReturnError(err == CHIP_NO_ERROR, err);

    size_t outLen;
    // The user doesn't need the KeyString prefix, Read data after it
    size_t KeyStringLen = strlen(key);
    err                 = SilabsConfig::ReadConfigValueBin(nvm3Key, reinterpret_cast<uint8_t *>(value), value_size, outLen,
                                                           (offset_bytes + KeyStringLen));
    if (read_bytes_size)
    {
        *read_bytes_size = outLen;
    }

    if (err == CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND)
    {
        return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
    }

    return err;
}

CHIP_ERROR KeyValueStoreManagerImpl::_Put(const char * key, const void * value, size_t value_size)
{
    VerifyOrReturnError(key != nullptr, CHIP_ERROR_INVALID_ARGUMENT);

    uint32_t nvm3Key;
    uint16_t hash  = hashKvsKeyString(key);
    CHIP_ERROR err = MapKvsKeyToNvm3(key, hash, nvm3Key, /* isSlotNeeded */ true);
    VerifyOrReturnError(err == CHIP_NO_ERROR, err);

    // add the string Key as prefix to the stored data as a collision prevention mechanism.
    size_t keyStringLen    = strlen(key);
    uint8_t * prefixedData = static_cast<uint8_t *>(Platform::MemoryAlloc(keyStringLen + value_size));
    VerifyOrDie(prefixedData != nullptr);
    memcpy(prefixedData, key, keyStringLen);
    memcpy(prefixedData + keyStringLen, value, value_size);

    err = SilabsConfig::WriteConfigValueBin(nvm3Key, prefixedData, keyStringLen + value_size);
    if (err == CHIP_NO_ERROR)
    {
        uint32_t keyIndex    = CONVERT_NVM3KEY_TO_KEYMAP_INDEX(nvm3Key);
        mKvsKeyMap[keyIndex] = hash;
        ScheduleKeyMapSave();
    }
    Platform::MemoryFree(prefixedData);
    return err;
}

CHIP_ERROR KeyValueStoreManagerImpl::_Delete(const char * key)
{
    VerifyOrReturnError(key != nullptr, CHIP_ERROR_INVALID_ARGUMENT);

    uint32_t nvm3Key;
    uint16_t hash  = hashKvsKeyString(key);
    CHIP_ERROR err = MapKvsKeyToNvm3(key, hash, nvm3Key);
    VerifyOrReturnError(err == CHIP_NO_ERROR, err);

    err = SilabsConfig::ClearConfigValue(nvm3Key);
    if (err == CHIP_NO_ERROR)
    {
        uint32_t keyIndex    = CONVERT_NVM3KEY_TO_KEYMAP_INDEX(nvm3Key);
        mKvsKeyMap[keyIndex] = 0;
        ScheduleKeyMapSave();
    }

    return err;
}

void KeyValueStoreManagerImpl::ErasePartition(void)
{
    // Iterate over all the Matter Kvs nvm3 records and delete each one...
    for (uint32_t nvm3Key = SilabsConfig::kMinConfigKey_MatterKvs; nvm3Key <= SilabsConfig::kConfigKey_KvsLastKeySlot; nvm3Key++)
    {
        SilabsConfig::ClearConfigValue(nvm3Key);
    }

    memset(mKvsKeyMap, 0, sizeof(mKvsKeyMap));
}

void KeyValueStoreManagerImpl::KvsMapMigration(void)
{
    size_t readlen                                                                        = 0;
    constexpr uint8_t oldMaxEntires                                                       = 120;
    char mKvsStoredKeyString[oldMaxEntires][PersistentStorageDelegate::kKeyLengthMax + 1] = { 0 };
    CHIP_ERROR err =
        SilabsConfig::ReadConfigValueBin(SilabsConfig::kConfigKey_KvsStringKeyMap, reinterpret_cast<uint8_t *>(mKvsStoredKeyString),
                                         sizeof(mKvsStoredKeyString), readlen);

    if (err == CHIP_NO_ERROR)
    {
        for (uint8_t i = 0; i < oldMaxEntires; i++)
        {
            if (mKvsStoredKeyString[i][0] != 0)
            {
                size_t dataLen   = 0;
                uint32_t nvm3Key = CONVERT_KEYMAP_INDEX_TO_NVM3KEY(i);

                if (SilabsConfig::ConfigValueExists(nvm3Key, dataLen))
                {
                    // Read old data and prefix it with the string Key for the collision prevention mechanism.
                    size_t keyStringLen    = strlen(mKvsStoredKeyString[i]);
                    uint8_t * prefixedData = static_cast<uint8_t *>(Platform::MemoryAlloc(keyStringLen + dataLen));
                    VerifyOrDie(prefixedData != nullptr);
                    memcpy(prefixedData, mKvsStoredKeyString[i], keyStringLen);

                    SilabsConfig::ReadConfigValueBin(nvm3Key, prefixedData + keyStringLen, dataLen, readlen);
                    SilabsConfig::WriteConfigValueBin(nvm3Key, prefixedData, keyStringLen + dataLen);
                    mKvsKeyMap[i] = KeyValueStoreMgrImpl().hashKvsKeyString(mKvsStoredKeyString[i]);
                    Platform::MemoryFree(prefixedData);
                }
            }
        }
        ForceKeyMapSave();
    }
    else if (err != CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND)
    {
        // Error reading the old String Keymap. Cannot not resolve stored data for migration.
        ChipLogError(DeviceLayer, "Migration failed ! Kvs Key map could not be recovered %" CHIP_ERROR_FORMAT, err.Format());
        // start with a fresh kvs section.
        KeyValueStoreMgrImpl().ErasePartition();
    }
}

} // namespace PersistedStorage

namespace Silabs {

void MigrateKvsMap(void)
{
    PersistedStorage::KeyValueStoreMgrImpl().KvsMapMigration();
}

} // namespace Silabs
} // namespace DeviceLayer
} // namespace chip
