/*
 *  Copyright (c) 2016-2020, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. 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.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  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.
 */

/**
 * @file
 *   This file includes definitions for Thread neighbor table.
 */

#include "neighbor_table.hpp"

#include "instance/instance.hpp"

namespace ot {

NeighborTable::NeighborTable(Instance &aInstance)
    : InstanceLocator(aInstance)
    , mCallback(nullptr)
{
}

Neighbor *NeighborTable::FindParent(const Neighbor::AddressMatcher &aMatcher)
{
    Neighbor *neighbor = nullptr;

    if (Get<Mle::Mle>().GetParent().Matches(aMatcher))
    {
        neighbor = &Get<Mle::Mle>().GetParent();
    }
    else if (Get<Mle::Mle>().GetParentCandidate().Matches(aMatcher))
    {
        neighbor = &Get<Mle::Mle>().GetParentCandidate();
    }

    return neighbor;
}

Neighbor *NeighborTable::FindParent(Mac::ShortAddress aShortAddress, Neighbor::StateFilter aFilter)
{
    return FindParent(Neighbor::AddressMatcher(aShortAddress, aFilter));
}

Neighbor *NeighborTable::FindParent(const Mac::ExtAddress &aExtAddress, Neighbor::StateFilter aFilter)
{
    return FindParent(Neighbor::AddressMatcher(aExtAddress, aFilter));
}

Neighbor *NeighborTable::FindParent(const Mac::Address &aMacAddress, Neighbor::StateFilter aFilter)
{
    return FindParent(Neighbor::AddressMatcher(aMacAddress, aFilter));
}

Neighbor *NeighborTable::FindNeighbor(const Neighbor::AddressMatcher &aMatcher)
{
    Neighbor *neighbor = nullptr;

#if OPENTHREAD_FTD
    if (Get<Mle::Mle>().IsRouterOrLeader())
    {
        neighbor = FindChildOrRouter(aMatcher);
    }

    if (neighbor == nullptr)
#endif
    {
        neighbor = FindParent(aMatcher);
    }

#if OPENTHREAD_CONFIG_P2P_ENABLE
    if (neighbor == nullptr)
    {
        neighbor = FindPeer(aMatcher);
    }
#endif

    return neighbor;
}

Neighbor *NeighborTable::FindNeighbor(Mac::ShortAddress aShortAddress, Neighbor::StateFilter aFilter)
{
    Neighbor *neighbor = nullptr;

    VerifyOrExit((aShortAddress != Mac::kShortAddrBroadcast) && (aShortAddress != Mac::kShortAddrInvalid));
    neighbor = FindNeighbor(Neighbor::AddressMatcher(aShortAddress, aFilter));

exit:
    return neighbor;
}

Neighbor *NeighborTable::FindNeighbor(const Mac::ExtAddress &aExtAddress, Neighbor::StateFilter aFilter)
{
    return FindNeighbor(Neighbor::AddressMatcher(aExtAddress, aFilter));
}

Neighbor *NeighborTable::FindNeighbor(const Mac::Address &aMacAddress, Neighbor::StateFilter aFilter)
{
    return FindNeighbor(Neighbor::AddressMatcher(aMacAddress, aFilter));
}

#if OPENTHREAD_CONFIG_P2P_ENABLE
Neighbor *NeighborTable::FindPeer(const Neighbor::AddressMatcher &aMatcher)
{
    return Get<PeerTable>().FindPeer(aMatcher);
}
#endif

#if OPENTHREAD_FTD

Neighbor *NeighborTable::FindChildOrRouter(const Neighbor::AddressMatcher &aMatcher)
{
    Neighbor *neighbor;

    neighbor = Get<ChildTable>().FindChild(aMatcher);

    if (neighbor == nullptr)
    {
        neighbor = Get<RouterTable>().FindRouter(aMatcher);
    }

    return neighbor;
}

Neighbor *NeighborTable::FindNeighbor(const Ip6::Address &aIp6Address, Neighbor::StateFilter aFilter)
{
    Neighbor    *neighbor = nullptr;
    Mac::Address macAddress;

    if (aIp6Address.IsLinkLocalUnicast())
    {
        macAddress.SetExtendedFromIid(aIp6Address.GetIid());
    }

    if (Get<Mle::Mle>().IsRoutingLocator(aIp6Address))
    {
        macAddress.SetShort(aIp6Address.GetIid().GetLocator());
    }

    if (!macAddress.IsNone())
    {
        neighbor = FindNeighbor(Neighbor::AddressMatcher(macAddress, aFilter));
        ExitNow();
    }

    for (Child &child : Get<ChildTable>().Iterate(aFilter))
    {
        if (child.HasIp6Address(aIp6Address))
        {
            ExitNow(neighbor = &child);
        }
    }

exit:
    return neighbor;
}

Neighbor *NeighborTable::FindRxOnlyNeighborRouter(const Mac::ExtAddress &aExtAddress)
{
    Mac::Address macAddress;

    macAddress.SetExtended(aExtAddress);

    return FindRxOnlyNeighborRouter(macAddress);
}

Neighbor *NeighborTable::FindRxOnlyNeighborRouter(const Mac::Address &aMacAddress)
{
    Neighbor *neighbor = nullptr;

    VerifyOrExit(Get<Mle::Mle>().IsChild());
    neighbor = Get<RouterTable>().FindNeighbor(aMacAddress);

exit:
    return neighbor;
}

Error NeighborTable::GetNextNeighborInfo(Iterator &aIterator, Neighbor::Info &aNeighInfo)
{
    Error   error = kErrorNone;
    int16_t index;

    // Non-negative iterator value gives the Child index into child table

    if (aIterator >= 0)
    {
        for (index = aIterator;; index++)
        {
            Child *child = Get<ChildTable>().GetChildAtIndex(static_cast<uint16_t>(index));

            if (child == nullptr)
            {
                break;
            }

            if (child->IsStateValid())
            {
                aNeighInfo.SetFrom(*child);
                aNeighInfo.mIsChild = true;
                index++;
                aIterator = index;
                ExitNow();
            }
        }

        aIterator = 0;
    }

    // Negative iterator value gives the current index into mRouters array

    for (index = static_cast<int16_t>(-aIterator); index <= Mle::kMaxRouterId; index++)
    {
        Router *router = Get<RouterTable>().FindRouterById(static_cast<uint8_t>(index));

        if (router != nullptr && router->IsStateValid())
        {
            aNeighInfo.SetFrom(*router);
            aNeighInfo.mIsChild = false;
            index++;
            aIterator = static_cast<Iterator>(-index);
            ExitNow();
        }
    }

    aIterator = static_cast<Iterator>(-index);
    error     = kErrorNotFound;

exit:
    return error;
}

#endif // OPENTHREAD_FTD

#if OPENTHREAD_MTD

Error NeighborTable::GetNextNeighborInfo(Iterator &aIterator, Neighbor::Info &aNeighInfo)
{
    Error error = kErrorNotFound;

    VerifyOrExit(aIterator == kIteratorInit);

    aIterator++;
    VerifyOrExit(Get<Mle::Mle>().GetParent().IsStateValid());

    aNeighInfo.SetFrom(Get<Mle::Mle>().GetParent());
    aNeighInfo.mIsChild = false;
    error               = kErrorNone;

exit:
    return error;
}

#endif

void NeighborTable::Signal(Event aEvent, const Neighbor &aNeighbor)
{
#if !OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
    if (mCallback != nullptr)
#endif
    {
        EntryInfo info;

        info.mInstance = &GetInstance();

        switch (aEvent)
        {
        case kChildAdded:
        case kChildRemoved:
        case kChildModeChanged:
#if OPENTHREAD_FTD
            OT_ASSERT(Get<ChildTable>().Contains(aNeighbor));
            static_cast<Child::Info &>(info.mInfo.mChild).SetFrom(static_cast<const Child &>(aNeighbor));
#endif
            break;

        case kRouterAdded:
        case kRouterRemoved:
            static_cast<Neighbor::Info &>(info.mInfo.mRouter).SetFrom(aNeighbor);
            break;
        }

#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
        Get<HistoryTracker::Local>().RecordNeighborEvent(aEvent, info);

        if (mCallback != nullptr)
#endif
        {
            mCallback(static_cast<otNeighborTableEvent>(aEvent), &info);
        }
    }

#if OPENTHREAD_CONFIG_OTNS_ENABLE
    Get<Utils::Otns>().EmitNeighborChange(aEvent, aNeighbor);
#endif

    switch (aEvent)
    {
    case kChildAdded:
        Get<Notifier>().Signal(kEventThreadChildAdded);
        break;

    case kChildRemoved:
        Get<Notifier>().Signal(kEventThreadChildRemoved);
        break;

#if OPENTHREAD_FTD
    case kRouterAdded:
        Get<RouterTable>().SignalTableChanged(RouterTable::kEventNeighborAdded);
        break;

    case kRouterRemoved:
        Get<RouterTable>().SignalTableChanged(RouterTable::kEventNeighborRemoved);
        break;
#endif

    default:
        break;
    }
}

} // namespace ot
