/*
 *    Copyright (c) 2024 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 <cstddef>
#include <lib/support/Span.h>

#include <optional>

namespace chip {

/// represents a wrapper around a type `T` that contains internal
/// `Span<...>` values of other sub-types. It allows searching within the container sub-spans
/// to create new containers.
///
/// The use case is that we very often search within nested containers, like "find-endpoint" + "find-cluster" + "find-attribute"
/// and we generally only care about "does the last element exist or not"
///
/// A typical example of the way this class is used looks like this:
///
///    SpanSearchValue container(somePointer);
///
///    const AcceptedCommandData * value =
///           container
///              .Find<ByEndpoint>(path.mEndpointId, mEndpointIndexHint)
///              .Find<ByServerCluster>(path.mClusterId, mServerClusterHint)
///              .Find<ByAcceptedCommand>(path.mCommandId, mAcceptedCommandHint)
///              .Value();
///
/// Where a `ByFoo` structure looks like:
///
///    struct ByFoo {
///      using Key  = int;              // the KEY inside a type
///      using Type = SomeValueType;    // The type that is indexed by `Key`
///
///      /// Allows getting the "Span of Type" from an underlying structure.
///      /// A `SpanSearchValue<Foo>` will require a `GetSpan(Foo&)`
///      static Span<Type> GetSpan(ContainerType & data) { /* return ... */ }
///
///      /// Checks that the `Type` value has the given `Key` or not
///      static bool HasKey(const Key & id, const Type & instance) { /* return "instance has key id" */ }
///    }
///
/// Where we define:
///    - how to get a "span of sub-elements" for an object (`GetSpan`)
///    - how to determine if a given sub-element has the "correct key"
template <typename T>
class SpanSearchValue
{
public:
    SpanSearchValue() : mValue(nullptr) {}
    SpanSearchValue(std::nullptr_t) : mValue(nullptr) {}
    explicit SpanSearchValue(T * value) : mValue(value) {}

    /// Returns nullptr if such an element does not exist or non-null valid value if the element exists
    T * Value() const { return mValue; }

    /// Gets the first element of `TYPE::Type`
    template <typename TYPE>
    SpanSearchValue<typename TYPE::Type> First(unsigned & indexHint)
    {
        // if no value, searching more also yields no value
        VerifyOrReturnValue(mValue != nullptr, nullptr);

        Span<typename TYPE::Type> value_span = TYPE::GetSpan(*mValue);
        VerifyOrReturnValue(!value_span.empty(), nullptr);

        // found it, save the hint
        indexHint = 0;
        return SpanSearchValue<typename TYPE::Type>(&value_span[0]);
    }

    /// Find the value corresponding to `key`
    template <typename TYPE>
    SpanSearchValue<typename TYPE::Type> Find(typename TYPE::Key key, unsigned & indexHint)
    {
        VerifyOrReturnValue(mValue != nullptr, nullptr);

        Span<typename TYPE::Type> value_span = TYPE::GetSpan(*mValue);

        if (!FindIndexUsingHint(key, value_span, indexHint, TYPE::HasKey))
        {
            return nullptr;
        }

        return SpanSearchValue<typename TYPE::Type>(&value_span[indexHint]);
    }

    /// Finds the value that occurs after `key` in the underlying collection.
    template <typename TYPE>
    SpanSearchValue<typename TYPE::Type> Next(typename TYPE::Key key, unsigned & indexHint)
    {
        VerifyOrReturnValue(mValue != nullptr, nullptr);

        Span<typename TYPE::Type> value_span = TYPE::GetSpan(*mValue);

        if (!FindIndexUsingHint(key, value_span, indexHint, TYPE::HasKey))
        {
            return nullptr;
        }

        VerifyOrReturnValue((indexHint + 1) < value_span.size(), nullptr);

        indexHint++;
        return SpanSearchValue<typename TYPE::Type>(&value_span[indexHint]);
    }

private:
    T * mValue = nullptr; // underlying value, NULL if such a value does not exist

    /// Search for the index where `needle` is located inside `haystack`
    ///
    /// using `haystackValueMatchesNeedle` to find if a given haystack value matches the given needle
    ///
    /// `in_out_hint` contains a start search point at the start and will contain the found index
    /// location (if found) at the end.
    ///
    /// Returns true on success (index found) false on failure (index not found). If returning
    /// false, the value of `in_out_hint` is unchanged
    template <typename N, typename H>
    static bool FindIndexUsingHint(const N & needle, Span<H> haystack, unsigned & in_out_hint,
                                   bool (*haystackValueMatchesNeedle)(const N &, const typename std::remove_const<H>::type &))
    {
        // search starts at `hint` rather than 0
        const unsigned haystackSize = static_cast<unsigned>(haystack.size());
        unsigned checkIndex         = (in_out_hint < haystackSize) ? in_out_hint : 0;

        for (unsigned i = 0; i < haystackSize; i++, checkIndex++)
        {
            if (haystackValueMatchesNeedle(needle, haystack[checkIndex % haystackSize]))
            {
                in_out_hint = checkIndex % haystackSize;
                return true;
            }
        }

        return false;
    }
};

} // namespace chip
