/*
 *    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.
 */
#pragma once

#include <app/ConcreteAttributePath.h>
#include <app/data-model/Nullable.h>
#include <app/util/attribute-metadata.h>
#include <lib/support/BufferReader.h>
#include <lib/support/BufferWriter.h>
#include <lib/support/Span.h>

#include <cstring>
#include <inttypes.h>

namespace chip {
namespace app {

/**
 * Interface for persisting attribute values. This will always write attributes in storage as little-endian
 * and uses a different key space from AttributePersistenceProvider.
 */

class SafeAttributePersistenceProvider
{
public:
    virtual ~SafeAttributePersistenceProvider() = default;
    SafeAttributePersistenceProvider()          = default;

    // The following API provides helper functions to simplify the access of commonly used types.
    // The API may not be complete.
    // Currently implemented write and read types are: bool, uint8_t, uint16_t, uint32_t, unit64_t and
    // their nullable varieties, as well as ByteSpans.

    /**
     * Write an attribute value of type intX, uintX or bool to non-volatile memory.
     *
     * @param [in] aPath the attribute path for the data being written.
     * @param [in] aValue the data to write.
     */
    template <typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
    CHIP_ERROR WriteScalarValue(const ConcreteAttributePath & aPath, T aValue)
    {
        uint8_t value[sizeof(T)];
        auto w = Encoding::LittleEndian::BufferWriter(value, sizeof(T));
        if constexpr (std::is_signed_v<T>)
        {
            w.EndianPutSigned(aValue, sizeof(T));
        }
        else
        {
            w.EndianPut(aValue, sizeof(T));
        }

        return SafeWriteValue(aPath, ByteSpan(value));
    }

    /**
     * Read an attribute of type intX, uintX or bool from non-volatile memory.
     *
     * @param [in]     aPath the attribute path for the data being persisted.
     * @param [out]    aValue where to place the data.
     *
     * @retval CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND if no stored value exists for the attribute
     */
    template <typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
    CHIP_ERROR ReadScalarValue(const ConcreteAttributePath & aPath, T & aValue)
    {
        uint8_t attrData[sizeof(T)];
        MutableByteSpan tempVal(attrData);
        ReturnErrorOnFailure(SafeReadValue(aPath, tempVal));
        VerifyOrReturnError(tempVal.size() == sizeof(T), CHIP_ERROR_INCORRECT_STATE);
        Encoding::LittleEndian::Reader r(tempVal.data(), tempVal.size());
        r.RawReadLowLevelBeCareful(&aValue);
        return r.StatusCode();
    }

    /**
     * Write an attribute value of type nullable intX, uintX to non-volatile memory.
     *
     * @param [in] aPath the attribute path for the data being written.
     * @param [in] aValue the data to write.
     */
    template <typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
    CHIP_ERROR WriteScalarValue(const ConcreteAttributePath & aPath, const DataModel::Nullable<T> & aValue)
    {
        typename NumericAttributeTraits<T>::StorageType storageValue;
        if (aValue.IsNull())
        {
            NumericAttributeTraits<T>::SetNull(storageValue);
        }
        else
        {
            NumericAttributeTraits<T>::WorkingToStorage(aValue.Value(), storageValue);
        }
        return WriteScalarValue(aPath, storageValue);
    }

    /**
     * Read an attribute of type nullable intX, uintX from non-volatile memory.
     *
     * @param [in]     aPath the attribute path for the data being persisted.
     * @param [out]    aValue where to place the data.
     *
     * @retval CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND if no stored value exists for the attribute
     */
    template <typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
    CHIP_ERROR ReadScalarValue(const ConcreteAttributePath & aPath, DataModel::Nullable<T> & aValue)
    {
        typename NumericAttributeTraits<T>::StorageType storageValue;
        ReturnErrorOnFailure(ReadScalarValue(aPath, storageValue));

        if (NumericAttributeTraits<T>::IsNullValue(storageValue))
        {
            aValue.SetNull();
            return CHIP_NO_ERROR;
        }

        // Consider checking CanRepresentValue here, so we don't produce invalid data
        // if the storage hands us invalid values.
        aValue.SetNonNull(NumericAttributeTraits<T>::StorageToWorking(storageValue));
        return CHIP_NO_ERROR;
    }

    /**
     * Write an attribute value from the attribute store (i.e. not a struct or
     * list) to non-volatile memory.
     *
     * @param [in] aPath the attribute path for the data being written.
     * @param [in] aValue the data to write. The value will be stored as-is.
     */
    virtual CHIP_ERROR SafeWriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue) = 0;

    /**
     * Read an attribute value as a raw sequence of bytes from non-volatile memory.
     *
     * @param [in]     aPath the attribute path for the data being persisted.
     * @param [in,out] aValue where to place the data.  The size of the buffer
     *                 will be equal to `aValue.size()`. On success aValue.size()
     *                 will be the actual number of bytes read.
     *
     * @retval CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND if no stored value exists for the attribute
     * @retval CHIP_ERROR_BUFFER_TOO_SMALL aValue.size() is too small to hold the value.
     */
    virtual CHIP_ERROR SafeReadValue(const ConcreteAttributePath & aPath, MutableByteSpan & aValue) = 0;
};

/**
 * Instance getter for the global SafeAttributePersistenceProvider.
 *
 * Callers have to externally synchronize usage of this function.
 *
 * @return The global SafeAttributePersistenceProvider.  This must never be null.
 */
SafeAttributePersistenceProvider * GetSafeAttributePersistenceProvider();

/**
 * Instance setter for the global SafeAttributePersistenceProvider.
 *
 * Callers have to externally synchronize usage of this function.
 *
 * If the `provider` is nullptr, the value is not changed.
 *
 * @param[in] aProvider the SafeAttributePersistenceProvider implementation to use.
 */
void SetSafeAttributePersistenceProvider(SafeAttributePersistenceProvider * aProvider);

} // namespace app
} // namespace chip
