/******************************************************************************
The MIT License(MIT)

Embedded Template Library.
https://github.com/ETLCPP/etl
https://www.etlcpp.com

Copyright(c) 2016 jwellbelove

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
******************************************************************************/

#include "unit_test_framework.h"

#include "data.h"

#include "etl/intrusive_links.h"

namespace
{
  //*******************************************************
  // Forward
  //*******************************************************
  typedef etl::forward_link<0> FLink0;
  typedef etl::forward_link<1> FLink1;

  struct FData : public FLink0, public FLink1
  {
    FData(int value_)
      : value(value_)
    {
    }

    int value;
  };

  //*******************************************************
  // Bidirectional
  //*******************************************************
  typedef etl::bidirectional_link<0> BLink0;
  typedef etl::bidirectional_link<1> BLink1;

  struct BData : public BLink0, public BLink1
  {
    BData(int value_)
      : value(value_)
    {
    }

    int value;
  };

  //*******************************************************
  // Tree
  //*******************************************************
  typedef etl::tree_link<0> TLink0;
  typedef etl::tree_link<1> TLink1;
  typedef etl::tree_link<2> TLink2;

  struct TData : public TLink0, public TLink1
  {
    TData(int value_)
      : value(value_)
    {
    }

    int value;
  };

  //*******************************************************
  // Mixed
  //*******************************************************
  struct MData : public FLink0, public BLink1, public TLink2
  {
    MData(int value_)
      : value(value_)
    {
    }

    int value;
  };

  SUITE(test_forward_list)
  {
    //*************************************************************************
    TEST(test_link_forward_link)
    {
      FData data0(0);
      FData data1(1);
      FData data2(2);
      FData data3(3);

      data0.FLink0::clear();
      etl::link<FLink0>(data0,  data1);
      CHECK(data0.FLink0::etl_next == &data1);

      data0.FLink0::clear();
      etl::link<FLink0>(&data0, data1);
      CHECK(data0.FLink0::etl_next == &data1);

      data0.FLink0::clear();
      etl::link<FLink0>(data0,  &data1);
      CHECK(data0.FLink0::etl_next == &data1);

      data0.FLink0::clear();
      etl::link<FLink0>(&data0, &data1);
      CHECK(data0.FLink0::etl_next == &data1);

      etl::link<FLink0>(data1,  data2);
      etl::link<FLink0>(data2,  data3);
      etl::link<FLink0>(data3,  nullptr);

      etl::link<FLink1>(data3, data2);
      etl::link<FLink1>(data2, data1);
      etl::link<FLink1>(data1, data0);
      etl::link<FLink1>(data0, nullptr);

      CHECK(data1.FLink0::etl_next == &data2);
      CHECK(data2.FLink0::etl_next == &data3);
      CHECK(data3.FLink0::etl_next == nullptr);

      CHECK(data3.FLink1::etl_next == &data2);
      CHECK(data2.FLink1::etl_next == &data1);
      CHECK(data1.FLink1::etl_next == &data0);
      CHECK(data0.FLink1::etl_next == nullptr);

      FData* pdata;

      pdata = static_cast<FData*>(data0.FLink0::etl_next);
      CHECK_EQUAL(1, pdata->value);
      pdata = static_cast<FData*>(pdata->FLink0::etl_next);
      CHECK_EQUAL(2, pdata->value);
      pdata = static_cast<FData*>(pdata->FLink0::etl_next);
      CHECK_EQUAL(3, pdata->value);

      pdata = static_cast<FData*>(data3.FLink1::etl_next);
      CHECK_EQUAL(2, pdata->value);
      pdata = static_cast<FData*>(pdata->FLink1::etl_next);
      CHECK_EQUAL(1, pdata->value);
      pdata = static_cast<FData*>(pdata->FLink1::etl_next);
      CHECK_EQUAL(0, pdata->value);
    }

    //*************************************************************************
    TEST(test_link_splice_forward_link)
    {
      FData data0(0);
      FData data1(1);
      FData data2(2);
      FData data3(3);

      data0.FLink0::clear();
      etl::link_splice<FLink0>(data0, data1);
      CHECK(data0.FLink0::etl_next == &data1);
      CHECK(data1.FLink0::etl_next == nullptr);

      data0.FLink0::clear();
      etl::link_splice<FLink0>(data0, &data1);
      CHECK(data0.FLink0::etl_next == &data1);
      CHECK(data1.FLink0::etl_next == nullptr);

      data0.FLink0::clear();
      etl::link_splice<FLink0>(&data0, data1);
      CHECK(data0.FLink0::etl_next == &data1);
      CHECK(data1.FLink0::etl_next == nullptr);

      data0.FLink0::clear();
      etl::link_splice<FLink0>(&data0, &data1);
      CHECK(data0.FLink0::etl_next == &data1);
      CHECK(data1.FLink0::etl_next == nullptr);

      data0.FLink0::clear();
      etl::link_splice<FLink0>(data0, data3);
      etl::link_splice<FLink0>(data0, data1);
      etl::link_splice<FLink0>(data1, data2);

      CHECK(data0.FLink0::etl_next == &data1);
      CHECK(data1.FLink0::etl_next == &data2);
      CHECK(data2.FLink0::etl_next == &data3);
      CHECK(data3.FLink0::etl_next == nullptr);
    }

    //*************************************************************************
    TEST(test_link_splice_forward_link_range)
    {
      FData data0(0);
      FData data1(1);
      FData data2(2);
      FData data3(3);
      FData data4(4);
      FData data5(5);
      FData data6(6);
      FData data7(7);

      // First range.
      data0.FLink0::clear();
      etl::link_splice<FLink0>(data0, data1);
      etl::link_splice<FLink0>(data1, data6);
      etl::link_splice<FLink0>(data6, data7);

      // Second range.
      data2.FLink0::clear();
      etl::link_splice<FLink0>(data2, data3);
      etl::link_splice<FLink0>(data3, data4);
      etl::link_splice<FLink0>(data4, data5);

      etl::link_splice<FLink0>(data1, data2, data5);

      CHECK(data0.FLink0::etl_next == &data1);
      CHECK(data1.FLink0::etl_next == &data2);
      CHECK(data2.FLink0::etl_next == &data3);
      CHECK(data3.FLink0::etl_next == &data4);
      CHECK(data4.FLink0::etl_next == &data5);
      CHECK(data5.FLink0::etl_next == &data6);
      CHECK(data6.FLink0::etl_next == &data7);
      CHECK(data7.FLink0::etl_next == nullptr);

      // Do it again with a pointer.
      // First range.
      data0.FLink0::clear();
      etl::link_splice<FLink0>(data0, data1);
      etl::link_splice<FLink0>(data1, data6);
      etl::link_splice<FLink0>(data6, data7);

      // Second range.
      data2.FLink0::clear();
      etl::link_splice<FLink0>(data2, data3);
      etl::link_splice<FLink0>(data3, data4);
      etl::link_splice<FLink0>(data4, data5);

      etl::link_splice<FLink0>(&data1, data2, data5);

      CHECK(data0.FLink0::etl_next == &data1);
      CHECK(data1.FLink0::etl_next == &data2);
      CHECK(data2.FLink0::etl_next == &data3);
      CHECK(data3.FLink0::etl_next == &data4);
      CHECK(data4.FLink0::etl_next == &data5);
      CHECK(data5.FLink0::etl_next == &data6);
      CHECK(data6.FLink0::etl_next == &data7);
      CHECK(data7.FLink0::etl_next == nullptr);

      // Do it again with a nullptr pointer.
      // Second range.
      data2.FLink0::clear();
      etl::link_splice<FLink0>(data2, data3);
      etl::link_splice<FLink0>(data3, data4);
      etl::link_splice<FLink0>(data4, data5);

      etl::link_splice<FLink0>(nullptr, data2, data5);

      CHECK(data2.FLink0::etl_next == &data3);
      CHECK(data3.FLink0::etl_next == &data4);
      CHECK(data4.FLink0::etl_next == &data5);
      CHECK(data5.FLink0::etl_next == nullptr);
    }


    //*************************************************************************
    TEST(test_unlink_after_forward_link)
    {
      FData data0(0);
      FData data1(1);
      FData data2(2);
      FData data3(3);

      etl::link<FLink0>(data0, data1);
      etl::link<FLink0>(data1, data2);
      etl::link<FLink0>(data2, data3);
      etl::link<FLink0>(data3, nullptr);

      etl::link<FLink1>(data3, data2);
      etl::link<FLink1>(data2, data1);
      etl::link<FLink1>(data1, data0);
      etl::link<FLink1>(data0, nullptr);

      etl::unlink_after<FLink0>(data1);
      data2.FLink0::clear();

      CHECK(data0.FLink0::etl_next  == &data1);
      CHECK(data1.FLink0::etl_next  == &data3);
      CHECK(data2.FLink0::etl_next  == nullptr);
      CHECK(data3.FLink0::etl_next  == nullptr);

      CHECK(data3.FLink1::etl_next == &data2);
      CHECK(data2.FLink1::etl_next == &data1);
      CHECK(data1.FLink1::etl_next == &data0);
      CHECK(data0.FLink1::etl_next == nullptr);

      etl::unlink_after<FLink1>(data2);
      data1.FLink1::clear();

      CHECK(data0.FLink0::etl_next  == &data1);
      CHECK(data1.FLink0::etl_next  == &data3);
      CHECK(data3.FLink0::etl_next  == nullptr);

      CHECK(data3.FLink1::etl_next == &data2);
      CHECK(data2.FLink1::etl_next == &data0);
      CHECK(data1.FLink1::etl_next == nullptr);
      CHECK(data0.FLink1::etl_next == nullptr);

      etl::unlink_after<FLink0>(data3);
      etl::unlink_after<FLink1>(data0);

      CHECK(data0.FLink0::etl_next  == &data1);
      CHECK(data1.FLink0::etl_next  == &data3);
      CHECK(data3.FLink0::etl_next  == nullptr);

      CHECK(data3.FLink1::etl_next == &data2);
      CHECK(data2.FLink1::etl_next == &data0);
      CHECK(data0.FLink1::etl_next == nullptr);
    }

    //*************************************************************************
    TEST(test_unlink_after_range_forward_link)
    {
      FData data0(0);
      FData data1(1);
      FData data2(2);
      FData data3(3);

      etl::link<FLink0>(data0, data1);
      etl::link<FLink0>(data1, data2);
      etl::link<FLink0>(data2, data3);
      etl::link<FLink0>(data3, nullptr);

      etl::link<FLink1>(data3, data2);
      etl::link<FLink1>(data2, data1);
      etl::link<FLink1>(data1, data0);
      etl::link<FLink1>(data0, nullptr);

      etl::unlink_after<FLink0>(data0, data2);
      data1.FLink0::clear();
      data2.FLink0::clear();

      CHECK(data0.FLink0::etl_next == &data3);
      CHECK(data1.FLink0::etl_next == nullptr);
      CHECK(data2.FLink0::etl_next == nullptr);
      CHECK(data3.FLink0::etl_next == nullptr);

      CHECK(data3.FLink1::etl_next == &data2);
      CHECK(data2.FLink1::etl_next == &data1);
      CHECK(data1.FLink1::etl_next == &data0);
      CHECK(data0.FLink1::etl_next == nullptr);
    }

    //*************************************************************************
    TEST(test_self_link_forward_link)
    {
      FData data0(0);

      etl::link<FLink0>(data0, data0);

      CHECK(data0.FLink0::etl_next == &data0);

      etl::unlink_after<FLink0>(data0);

      CHECK(data0.FLink0::etl_next == &data0);
    }

    //*************************************************************************
    TEST(test_link_bidirectional_link)
    {
      BData data0(0);
      BData data1(1);
      BData data2(2);
      BData data3(3);

      etl::link<BLink0>(nullptr, data0);

      data1.BLink0::clear();
      etl::link<BLink0>(data0, data1);
      CHECK(data0.BLink0::etl_next     == &data1);
      CHECK(data1.BLink0::etl_previous == &data0);

      data1.BLink0::clear();
      etl::link<BLink0>(data0, data1);
      CHECK(data0.BLink0::etl_next     == &data1);
      CHECK(data1.BLink0::etl_previous == &data0);

      data1.BLink0::clear();
      etl::link<BLink0>(data0,  data1);
      CHECK(data0.BLink0::etl_next     == &data1);
      CHECK(data1.BLink0::etl_previous == &data0);

      data1.BLink0::clear();
      etl::link<BLink0>(data0, data1);
      CHECK(data0.BLink0::etl_next     == &data1);
      CHECK(data1.BLink0::etl_previous == &data0);

      etl::link<BLink0>(data1, data2);
      etl::link<BLink0>(data2, data3);
      etl::link<BLink0>(data3, nullptr);

      CHECK(data0.BLink0::etl_previous == nullptr);
      CHECK(data1.BLink0::etl_previous == &data0);
      CHECK(data1.BLink0::etl_next     == &data2);
      CHECK(data2.BLink0::etl_previous == &data1);
      CHECK(data2.BLink0::etl_next     == &data3);
      CHECK(data3.BLink0::etl_previous == &data2);
      CHECK(data3.BLink0::etl_next     == nullptr);

      etl::link<BLink1>(nullptr, data3);
      etl::link<BLink1>(data3,   data2);
      etl::link<BLink1>(data2,   data1);
      etl::link<BLink1>(data1,   data0);
      etl::link<BLink1>(data0,   nullptr);

      CHECK(data3.BLink1::etl_previous    == nullptr);
      CHECK(data3.BLink1::etl_next        == &data2);
      CHECK(data2.BLink1::etl_previous    == &data3);
      CHECK(data2.BLink1::etl_next        == &data1);
      CHECK(data1.BLink1::etl_previous    == &data2);
      CHECK(data1.BLink1::etl_next        == &data0);
      CHECK(data0.BLink1::etl_previous    == &data1);
      CHECK(data0.BLink1::etl_next        == nullptr);

      BData* pdata;

      pdata = static_cast<BData*>(data0.BLink0::etl_next);
      CHECK_EQUAL(1, pdata->value);
      pdata = static_cast<BData*>(pdata->BLink0::etl_next);
      CHECK_EQUAL(2, pdata->value);
      pdata = static_cast<BData*>(pdata->BLink0::etl_next);
      CHECK_EQUAL(3, pdata->value);

      pdata = static_cast<BData*>(data3.BLink0::etl_previous);
      CHECK_EQUAL(2, pdata->value);
      pdata = static_cast<BData*>(pdata->BLink0::etl_previous);
      CHECK_EQUAL(1, pdata->value);
      pdata = static_cast<BData*>(pdata->BLink0::etl_previous);
      CHECK_EQUAL(0, pdata->value);

      pdata = static_cast<BData*>(data3.BLink1::etl_next);
      CHECK_EQUAL(2, pdata->value);
      pdata = static_cast<BData*>(pdata->BLink1::etl_next);
      CHECK_EQUAL(1, pdata->value);
      pdata = static_cast<BData*>(pdata->BLink1::etl_next);
      CHECK_EQUAL(0, pdata->value);

      pdata = static_cast<BData*>(data0.BLink1::etl_previous);
      CHECK_EQUAL(1, pdata->value);
      pdata = static_cast<BData*>(pdata->BLink1::etl_previous);
      CHECK_EQUAL(2, pdata->value);
      pdata = static_cast<BData*>(pdata->BLink1::etl_previous);
      CHECK_EQUAL(3, pdata->value);

      data1.BLink0::unlink();
      CHECK(data0.BLink0::etl_next     == &data2);
      CHECK(data2.BLink0::etl_previous == &data0);

      CHECK(data3.BLink1::etl_previous    == nullptr);
      CHECK(data3.BLink1::etl_next        == &data2);
      CHECK(data2.BLink1::etl_previous    == &data3);
      CHECK(data2.BLink1::etl_next        != nullptr);
      CHECK(data0.BLink1::etl_previous    != nullptr);
      CHECK(data0.BLink1::etl_next        == nullptr);

      data0.BLink0::unlink();
      CHECK(data2.BLink0::etl_next     == &data3);
      CHECK(data2.BLink0::etl_previous == nullptr);
      CHECK(data3.BLink0::etl_previous == &data2);

      CHECK(data3.BLink1::etl_previous    == nullptr);
      CHECK(data3.BLink1::etl_next        == &data2);
      CHECK(data2.BLink1::etl_previous    == &data3);
      CHECK(data2.BLink1::etl_next        != nullptr);

      data3.BLink0::unlink();
      CHECK(data2.BLink0::etl_next     == nullptr);
      CHECK(data2.BLink0::etl_previous == nullptr);

      CHECK(data2.BLink1::etl_next        != nullptr);
      CHECK(data2.BLink1::etl_previous    != nullptr);

      data2.BLink0::unlink();
    }

    //*************************************************************************
    TEST(test_link_splice_bidirectional_link)
    {
      BData data0(0);
      BData data1(1);
      BData data2(2);
      BData data3(3);

      data0.BLink0::clear();
      etl::link_splice<BLink0>(nullptr, data0);

      etl::link_splice<BLink0>(data0, data1);
      CHECK(data0.BLink0::etl_next     == &data1);
      CHECK(data1.BLink0::etl_previous == &data0);
      CHECK(data1.BLink0::etl_next     == nullptr);

      data0.BLink0::clear();
      etl::link_splice<BLink0>(nullptr, data0);

      etl::link_splice<BLink0>(data0, &data1);
      CHECK(data0.BLink0::etl_next     == &data1);
      CHECK(data1.BLink0::etl_previous == &data0);
      CHECK(data1.BLink0::etl_next     == nullptr);

      data0.BLink0::clear();
      etl::link_splice<BLink0>(nullptr, data0);

      etl::link_splice<BLink0>(&data0, data1);
      CHECK(data0.BLink0::etl_next     == &data1);
      CHECK(data1.BLink0::etl_previous == &data0);
      CHECK(data1.BLink0::etl_next     == nullptr);

      data0.BLink0::clear();
      etl::link_splice<BLink0>(nullptr, data0);

      etl::link_splice<BLink0>(&data0, &data1);
      CHECK(data0.BLink0::etl_next     == &data1);
      CHECK(data1.BLink0::etl_previous == &data0);
      CHECK(data1.BLink0::etl_next     == nullptr);

      data0.BLink0::clear();
      etl::link_splice<BLink0>(nullptr, data0);
      etl::link_splice<BLink0>(data0,   data3);
      etl::link_splice<BLink0>(data0,   data1);
      etl::link_splice<BLink0>(data1,   data2);

      CHECK(data0.BLink0::etl_previous == nullptr);
      CHECK(data0.BLink0::etl_next     == &data1);
      CHECK(data1.BLink0::etl_previous == &data0);
      CHECK(data1.BLink0::etl_next     == &data2);
      CHECK(data2.BLink0::etl_previous == &data1);
      CHECK(data2.BLink0::etl_next     == &data3);
      CHECK(data3.BLink0::etl_previous == &data2);
      CHECK(data3.BLink0::etl_next     == nullptr);
    }

    //*************************************************************************
    TEST(test_link_splice_range_bidirectional_link)
    {
      BData data0(0);
      BData data1(1);
      BData data2(2);
      BData data3(3);
      BData data4(4);
      BData data5(5);
      BData data6(6);
      BData data7(7);

      // Build the first range.
      data0.BLink0::clear();
      etl::link_splice<BLink0>(nullptr, data0);
      etl::link_splice<BLink0>(data0,   data1);
      etl::link_splice<BLink0>(data1,   data6);
      etl::link_splice<BLink0>(data6,   data7);

      // Build the second range.
      data2.BLink0::clear();
      etl::link_splice<BLink0>(nullptr, data2);
      etl::link_splice<BLink0>(data2,   data3);
      etl::link_splice<BLink0>(data3,   data4);
      etl::link_splice<BLink0>(data4,   data5);

      etl::link_splice<BLink0>(data1, data2, data5);

      CHECK(data0.BLink0::etl_previous == nullptr);
      CHECK(data0.BLink0::etl_next     == &data1);
      CHECK(data1.BLink0::etl_previous == &data0);
      CHECK(data1.BLink0::etl_next     == &data2);
      CHECK(data2.BLink0::etl_previous == &data1);
      CHECK(data2.BLink0::etl_next     == &data3);
      CHECK(data3.BLink0::etl_previous == &data2);
      CHECK(data3.BLink0::etl_next     == &data4);
      CHECK(data4.BLink0::etl_previous == &data3);
      CHECK(data4.BLink0::etl_next     == &data5);
      CHECK(data5.BLink0::etl_previous == &data4);
      CHECK(data5.BLink0::etl_next     == &data6);
      CHECK(data6.BLink0::etl_previous == &data5);
      CHECK(data6.BLink0::etl_next     == &data7);
      CHECK(data7.BLink0::etl_previous == &data6);
      CHECK(data7.BLink0::etl_next     == nullptr);

      // Do it again with a pointer parameter.
      // Build the first range.
      data0.BLink0::clear();
      etl::link_splice<BLink0>(nullptr, data0);
      etl::link_splice<BLink0>(data0, data1);
      etl::link_splice<BLink0>(data1, data6);
      etl::link_splice<BLink0>(data6, data7);

      // Build the second range.
      data2.BLink0::clear();
      etl::link_splice<BLink0>(nullptr, data2);
      etl::link_splice<BLink0>(data2, data3);
      etl::link_splice<BLink0>(data3, data4);
      etl::link_splice<BLink0>(data4, data5);

      etl::link_splice<BLink0>(&data1, data2, data5);

      CHECK(data0.BLink0::etl_previous == nullptr);
      CHECK(data0.BLink0::etl_next == &data1);
      CHECK(data1.BLink0::etl_previous == &data0);
      CHECK(data1.BLink0::etl_next == &data2);
      CHECK(data2.BLink0::etl_previous == &data1);
      CHECK(data2.BLink0::etl_next == &data3);
      CHECK(data3.BLink0::etl_previous == &data2);
      CHECK(data3.BLink0::etl_next == &data4);
      CHECK(data4.BLink0::etl_previous == &data3);
      CHECK(data4.BLink0::etl_next == &data5);
      CHECK(data5.BLink0::etl_previous == &data4);
      CHECK(data5.BLink0::etl_next == &data6);
      CHECK(data6.BLink0::etl_previous == &data5);
      CHECK(data6.BLink0::etl_next == &data7);
      CHECK(data7.BLink0::etl_previous == &data6);
      CHECK(data7.BLink0::etl_next == nullptr);

      // Do it again with a nullptr parameter.
      // Build the range.
      data2.BLink0::clear();
      etl::link_splice<BLink0>(nullptr, data2);
      etl::link_splice<BLink0>(data2,   data3);
      etl::link_splice<BLink0>(data3,   data4);
      etl::link_splice<BLink0>(data4,   data5);

      etl::link_splice<BLink0>(nullptr, data2, data5);

      CHECK(data2.BLink0::etl_previous == nullptr);
      CHECK(data2.BLink0::etl_next     == &data3);
      CHECK(data3.BLink0::etl_previous == &data2);
      CHECK(data3.BLink0::etl_next     == &data4);
      CHECK(data4.BLink0::etl_previous == &data3);
      CHECK(data4.BLink0::etl_next     == &data5);
      CHECK(data5.BLink0::etl_previous == &data4);
      CHECK(data5.BLink0::etl_next     == nullptr);
    }

    //*************************************************************************
    TEST(test_unlink_bidirectional_link)
    {
      BData data0(0);
      BData data1(1);
      BData data2(2);
      BData data3(3);

      etl::link<BLink0>(nullptr, data0);
      etl::link<BLink0>(data0,   data1);
      etl::link<BLink0>(data1,   data2);
      etl::link<BLink0>(data2,   data3);
      etl::link<BLink0>(data3,   nullptr);

      etl::link<BLink1>(nullptr, data3);
      etl::link<BLink1>(data3,   data2);
      etl::link<BLink1>(data2,   data1);
      etl::link<BLink1>(data1,   data0);
      etl::link<BLink1>(data0,   nullptr);

      etl::unlink<BLink0>(data1);
      data1.BLink0::clear();

      CHECK(data0.BLink0::etl_previous  == nullptr);
      CHECK(data0.BLink0::etl_next      == &data2);
      CHECK(data1.BLink0::etl_previous  == nullptr);
      CHECK(data1.BLink0::etl_next      == nullptr);
      CHECK(data2.BLink0::etl_previous  == &data0);
      CHECK(data2.BLink0::etl_next      == &data3);
      CHECK(data3.BLink0::etl_previous  == &data2);
      CHECK(data3.BLink0::etl_next      == nullptr);

      CHECK(data3.BLink1::etl_previous == nullptr);
      CHECK(data3.BLink1::etl_next     == &data2);
      CHECK(data2.BLink1::etl_previous == &data3);
      CHECK(data2.BLink1::etl_next     == &data1);
      CHECK(data1.BLink1::etl_previous == &data2);
      CHECK(data1.BLink1::etl_next     == &data0);
      CHECK(data0.BLink1::etl_previous == &data1);
      CHECK(data0.BLink1::etl_next     == nullptr);

      etl::unlink<BLink1>(data2);
      data2.BLink1::clear();

      CHECK(data0.BLink0::etl_previous  == nullptr);
      CHECK(data0.BLink0::etl_next      == &data2);
      CHECK(data1.BLink0::etl_previous  == nullptr);
      CHECK(data1.BLink0::etl_next      == nullptr);
      CHECK(data2.BLink0::etl_previous  == &data0);
      CHECK(data2.BLink0::etl_next      == &data3);
      CHECK(data3.BLink0::etl_previous  == &data2);
      CHECK(data3.BLink0::etl_next      == nullptr);

      CHECK(data3.BLink1::etl_previous == nullptr);
      CHECK(data3.BLink1::etl_next     == &data1);
      CHECK(data2.BLink1::etl_previous == nullptr);
      CHECK(data2.BLink1::etl_next     == nullptr);
      CHECK(data1.BLink1::etl_previous == &data3);
      CHECK(data1.BLink1::etl_next     == &data0);
      CHECK(data0.BLink1::etl_previous == &data1);
      CHECK(data0.BLink1::etl_next     == nullptr);

      etl::unlink<BLink0>(data0);
      data0.BLink0::clear();

      CHECK(data0.BLink0::etl_previous  == nullptr);
      CHECK(data0.BLink0::etl_next      == nullptr);
      CHECK(data1.BLink0::etl_previous  == nullptr);
      CHECK(data1.BLink0::etl_next      == nullptr);
      CHECK(data2.BLink0::etl_previous  == nullptr);
      CHECK(data2.BLink0::etl_next      == &data3);
      CHECK(data3.BLink0::etl_previous  == &data2);
      CHECK(data3.BLink0::etl_next      == nullptr);

      CHECK(data3.BLink1::etl_previous == nullptr);
      CHECK(data3.BLink1::etl_next     == &data1);
      CHECK(data2.BLink1::etl_previous == nullptr);
      CHECK(data2.BLink1::etl_next     == nullptr);
      CHECK(data1.BLink1::etl_previous == &data3);
      CHECK(data1.BLink1::etl_next     == &data0);
      CHECK(data0.BLink1::etl_previous == &data1);
      CHECK(data0.BLink1::etl_next     == nullptr);

      etl::unlink<BLink1>(data3);
      data3.BLink1::clear();

      CHECK(data0.BLink0::etl_previous  == nullptr);
      CHECK(data0.BLink0::etl_next      == nullptr);
      CHECK(data1.BLink0::etl_previous  == nullptr);
      CHECK(data1.BLink0::etl_next      == nullptr);
      CHECK(data2.BLink0::etl_previous  == nullptr);
      CHECK(data2.BLink0::etl_next      == &data3);
      CHECK(data3.BLink0::etl_previous  == &data2);
      CHECK(data3.BLink0::etl_next      == nullptr);

      CHECK(data3.BLink1::etl_previous == nullptr);
      CHECK(data3.BLink1::etl_next     == nullptr);
      CHECK(data2.BLink1::etl_previous == nullptr);
      CHECK(data2.BLink1::etl_next     == nullptr);
      CHECK(data1.BLink1::etl_previous == nullptr);
      CHECK(data1.BLink1::etl_next     == &data0);
      CHECK(data0.BLink1::etl_previous == &data1);
      CHECK(data0.BLink1::etl_next     == nullptr);
    }

    //*************************************************************************
    TEST(test_unlink_range_bidirectional_link)
    {
      BData data0(0);
      BData data1(1);
      BData data2(2);
      BData data3(3);

      etl::link<BLink0>(nullptr, data0);
      etl::link<BLink0>(data0, data1);
      etl::link<BLink0>(data1, data2);
      etl::link<BLink0>(data2, data3);
      etl::link<BLink0>(data3, nullptr);

      etl::link<BLink1>(nullptr, data3);
      etl::link<BLink1>(data3, data2);
      etl::link<BLink1>(data2, data1);
      etl::link<BLink1>(data1, data0);
      etl::link<BLink1>(data0, nullptr);

      etl::unlink<BLink0>(data1, data2);
      data1.BLink0::clear();
      data2.BLink0::clear();

      CHECK(data0.BLink0::etl_previous == nullptr);
      CHECK(data0.BLink0::etl_next == &data3);
      CHECK(data1.BLink0::etl_previous == nullptr);
      CHECK(data1.BLink0::etl_next == nullptr);
      CHECK(data2.BLink0::etl_previous == nullptr);
      CHECK(data2.BLink0::etl_next == nullptr);
      CHECK(data3.BLink0::etl_previous == &data0);
      CHECK(data3.BLink0::etl_next == nullptr);

      CHECK(data3.BLink1::etl_previous == nullptr);
      CHECK(data3.BLink1::etl_next == &data2);
      CHECK(data2.BLink1::etl_previous == &data3);
      CHECK(data2.BLink1::etl_next == &data1);
      CHECK(data1.BLink1::etl_previous == &data2);
      CHECK(data1.BLink1::etl_next == &data0);
      CHECK(data0.BLink1::etl_previous == &data1);
      CHECK(data0.BLink1::etl_next == nullptr);
    }

    //*************************************************************************
    TEST(test_self_link_bidirectional_link)
    {
      BData data0(0);

      etl::link<BLink0>(data0, data0);

      CHECK(data0.BLink0::etl_previous == &data0);
      CHECK(data0.BLink0::etl_next     == &data0);

      etl::unlink<BLink0>(data0);

      CHECK(data0.BLink0::etl_previous == &data0);
      CHECK(data0.BLink0::etl_next     == &data0);
    }

    //*************************************************************************
    TEST(test_tree_link)
    {
      TData data0(0);
      TData data1(1);
      TData data2(2);
      TData data3(3);
      TData data4(4);
      TData data5(5);
      TData data6(6);

      // First link
      data0.TLink0::clear();
      etl::link_left<TLink0>(data0,  data1);
      etl::link_right<TLink0>(data0, data2);
      CHECK(data0.TLink0::etl_left   == &data1);
      CHECK(data0.TLink0::etl_right  == &data2);
      CHECK(data1.TLink0::etl_parent == &data0);
      CHECK(data2.TLink0::etl_parent == &data0);

      data0.TLink0::clear();
      etl::link_left<TLink0>(&data0,  data1);
      etl::link_right<TLink0>(&data0, data2);
      CHECK(data0.TLink0::etl_left    == &data1);
      CHECK(data0.TLink0::etl_right   == &data2);
      CHECK(data1.TLink0::etl_parent  == &data0);
      CHECK(data2.TLink0::etl_parent  == &data0);

      data0.TLink0::clear();
      etl::link_left<TLink0>(data0,  &data1);
      etl::link_right<TLink0>(data0, &data2);
      CHECK(data0.TLink0::etl_left   == &data1);
      CHECK(data0.TLink0::etl_right  == &data2);
      CHECK(data1.TLink0::etl_parent == &data0);
      CHECK(data2.TLink0::etl_parent == &data0);

      data0.TLink0::clear();
      etl::link_left<TLink0>(&data0,  &data1);
      etl::link_right<TLink0>(&data0, &data2);

      etl::link_left<TLink0>(data1,  data3);
      etl::link_right<TLink0>(data1, data4);
      etl::link_left<TLink0>(data3,  nullptr);
      etl::link_right<TLink0>(data3, nullptr);
      etl::link_left<TLink0>(data4,  nullptr);
      etl::link_right<TLink0>(data4, nullptr);

      etl::link_left<TLink0>(data2,  data5);
      etl::link_right<TLink0>(data2, data6);
      etl::link_left<TLink0>(data5,  nullptr);
      etl::link_right<TLink0>(data5, nullptr);
      etl::link_left<TLink0>(data6,  nullptr);
      etl::link_right<TLink0>(data6, nullptr);

      // Second link
      data0.TLink1::clear();
      etl::link_left<TLink1>(&data6,  &data4);
      etl::link_right<TLink1>(&data6, &data5);

      etl::link_left<TLink1>(data4,  data0);
      etl::link_right<TLink1>(data4, data1);
      etl::link_left<TLink1>(data0,  nullptr);
      etl::link_right<TLink1>(data0, nullptr);
      etl::link_left<TLink1>(data1,  nullptr);
      etl::link_right<TLink1>(data1, nullptr);

      etl::link_left<TLink1>(data5,  data2);
      etl::link_right<TLink1>(data5, data3);
      etl::link_left<TLink1>(data2,  nullptr);
      etl::link_right<TLink1>(data2, nullptr);
      etl::link_left<TLink1>(data3,  nullptr);
      etl::link_right<TLink1>(data3, nullptr);

      // Check first
      CHECK(data0.TLink0::etl_left   == &data1);
      CHECK(data0.TLink0::etl_right  == &data2);
      CHECK(data1.TLink0::etl_parent == &data0);
      CHECK(data2.TLink0::etl_parent == &data0);

      CHECK(data1.TLink0::etl_left   == &data3);
      CHECK(data1.TLink0::etl_right  == &data4);
      CHECK(data3.TLink0::etl_parent == &data1);
      CHECK(data3.TLink0::etl_left   == nullptr);
      CHECK(data3.TLink0::etl_right  == nullptr);
      CHECK(data4.TLink0::etl_parent == &data1);
      CHECK(data4.TLink0::etl_left   == nullptr);
      CHECK(data4.TLink0::etl_right  == nullptr);

      CHECK(data2.TLink0::etl_left   == &data5);
      CHECK(data2.TLink0::etl_right  == &data6);
      CHECK(data5.TLink0::etl_parent == &data2);
      CHECK(data5.TLink0::etl_left   == nullptr);
      CHECK(data5.TLink0::etl_right  == nullptr);
      CHECK(data6.TLink0::etl_parent == &data2);
      CHECK(data6.TLink0::etl_left   == nullptr);
      CHECK(data6.TLink0::etl_right  == nullptr);

      // Check second
      CHECK(data6.TLink1::etl_left   == &data4);
      CHECK(data6.TLink1::etl_right  == &data5);
      CHECK(data4.TLink1::etl_parent == &data6);
      CHECK(data5.TLink1::etl_parent == &data6);

      CHECK(data4.TLink1::etl_left   == &data0);
      CHECK(data4.TLink1::etl_right  == &data1);
      CHECK(data0.TLink1::etl_parent == &data4);
      CHECK(data0.TLink1::etl_left   == nullptr);
      CHECK(data0.TLink1::etl_right  == nullptr);
      CHECK(data1.TLink1::etl_parent == &data4);
      CHECK(data1.TLink1::etl_left   == nullptr);
      CHECK(data1.TLink1::etl_right  == nullptr);

      CHECK(data5.TLink1::etl_left   == &data2);
      CHECK(data5.TLink1::etl_right  == &data3);
      CHECK(data2.TLink1::etl_parent == &data5);
      CHECK(data2.TLink1::etl_left   == nullptr);
      CHECK(data2.TLink1::etl_right  == nullptr);
      CHECK(data3.TLink1::etl_parent == &data5);
      CHECK(data3.TLink1::etl_left   == nullptr);
      CHECK(data3.TLink1::etl_right  == nullptr);
    }

    //*************************************************************************
    TEST(test_is_linked)
    {
      // Forward link
      FData fdata(0);

      fdata.FLink0::clear();
      fdata.FLink1::clear();
      CHECK(!fdata.FLink0::is_linked());
      CHECK(!fdata.FLink1::is_linked());

      etl::link<FLink0>(fdata, fdata);
      CHECK(fdata.FLink0::is_linked());
      CHECK(!fdata.FLink1::is_linked());

      etl::link<FLink1>(fdata, fdata);
      CHECK(fdata.FLink0::is_linked());
      CHECK(fdata.FLink1::is_linked());

      // Bidirectional link
      BData bdata(0);

      bdata.BLink0::clear();
      bdata.BLink1::clear();
      CHECK(!bdata.BLink0::is_linked());
      CHECK(!bdata.BLink1::is_linked());

      etl::link<BLink0>(bdata, bdata);
      CHECK(bdata.BLink0::is_linked());
      CHECK(!bdata.BLink1::is_linked());

      etl::link<BLink1>(bdata, bdata);
      CHECK(bdata.BLink0::is_linked());
      CHECK(bdata.BLink1::is_linked());

      // Tree link
      TData tdata(0);

      tdata.TLink0::clear();
      tdata.TLink1::clear();
      CHECK(!tdata.TLink0::is_linked());
      CHECK(!tdata.TLink1::is_linked());

      etl::link_left<TLink0>(tdata, tdata);
      CHECK(tdata.TLink0::is_linked());
      CHECK(!tdata.TLink1::is_linked());

      etl::link_right<TLink1>(tdata, tdata);
      CHECK(tdata.TLink0::is_linked());
      CHECK(tdata.TLink1::is_linked());
    }

    //*************************************************************************
    TEST(test_link_mixed_links)
    {
      MData data0(0);
      MData data1(1);
      MData data2(2);
      MData data3(3);
      MData data4(4);
      MData data5(5);
      MData data6(6);

      // Forward
      data0.FLink0::clear();
      etl::link<FLink0>(data0, data1);
      etl::link<FLink0>(data1, data2);
      etl::link<FLink0>(data2, data3);
      etl::link<FLink0>(data3, nullptr);

      // Bidirectional
      etl::link<BLink1>(nullptr, data3);
      etl::link<BLink1>(data3,   data2);
      etl::link<BLink1>(data2,   data1);
      etl::link<BLink1>(data1,   data0);
      etl::link<BLink1>(data0,   nullptr);

      // Tree
      data0.TLink2::clear();
      etl::link_left<TLink2>(data0,  data1);
      etl::link_right<TLink2>(data0, data2);
      etl::link_left<TLink2>(data1,  data3);
      etl::link_right<TLink2>(data1, data4);
      etl::link_left<TLink2>(data3,  nullptr);
      etl::link_right<TLink2>(data3, nullptr);
      etl::link_left<TLink2>(data4,  nullptr);
      etl::link_right<TLink2>(data4, nullptr);
      etl::link_left<TLink2>(data2,  data5);
      etl::link_right<TLink2>(data2, data6);
      etl::link_left<TLink2>(data5,  nullptr);
      etl::link_right<TLink2>(data5, nullptr);
      etl::link_left<TLink2>(data6,  nullptr);
      etl::link_right<TLink2>(data6, nullptr);

      CHECK(data0.FLink0::etl_next == &data1);
      CHECK(data1.FLink0::etl_next == &data2);
      CHECK(data2.FLink0::etl_next == &data3);
      CHECK(data3.FLink0::etl_next == nullptr);

      CHECK(data3.BLink1::etl_previous == nullptr);
      CHECK(data3.BLink1::etl_next     == &data2);
      CHECK(data2.BLink1::etl_previous == &data3);
      CHECK(data2.BLink1::etl_next     == &data1);
      CHECK(data1.BLink1::etl_previous == &data2);
      CHECK(data1.BLink1::etl_next     == &data0);
      CHECK(data0.BLink1::etl_previous == &data1);
      CHECK(data0.BLink1::etl_next     == nullptr);

      CHECK(data0.TLink2::etl_left   == &data1);
      CHECK(data0.TLink2::etl_right  == &data2);
      CHECK(data1.TLink2::etl_parent == &data0);
      CHECK(data2.TLink2::etl_parent == &data0);

      CHECK(data1.TLink2::etl_left   == &data3);
      CHECK(data1.TLink2::etl_right  == &data4);
      CHECK(data3.TLink2::etl_parent == &data1);
      CHECK(data3.TLink2::etl_left   == nullptr);
      CHECK(data3.TLink2::etl_right  == nullptr);
      CHECK(data4.TLink2::etl_parent == &data1);
      CHECK(data4.TLink2::etl_left   == nullptr);
      CHECK(data4.TLink2::etl_right  == nullptr);

      CHECK(data2.TLink2::etl_left   == &data5);
      CHECK(data2.TLink2::etl_right  == &data6);
      CHECK(data5.TLink2::etl_parent == &data2);
      CHECK(data5.TLink2::etl_left   == nullptr);
      CHECK(data5.TLink2::etl_right  == nullptr);
      CHECK(data6.TLink2::etl_parent == &data2);
      CHECK(data6.TLink2::etl_left   == nullptr);
      CHECK(data6.TLink2::etl_right  == nullptr);
    }

    //*************************************************************************
    TEST(test_tree_link_rotate_left)
    {
      TLink0 r;
      TLink0 a;
      TLink0 b;
      TLink0 c;
      TLink0 d;
      TLink0 e;

      r.clear();
      c.clear();
      d.clear();
      e.clear();
      etl::link_left(r, b);
      etl::link_right(b, a);
      etl::link_left(b, d);
      etl::link_right(a, c);
      etl::link_left(a, e);

      etl::link_rotate_left(b, a);

      CHECK(a.etl_parent == &r);
      CHECK(b.etl_parent == &a);
      CHECK(e.etl_parent == &b);
      CHECK(d.etl_parent == &b);
      CHECK(c.etl_parent == &a);
      CHECK(a.etl_left   == &b);
      CHECK(a.etl_right  == &c);
      CHECK(b.etl_left   == &d);
      CHECK(b.etl_right  == &e);
    }

    //*************************************************************************
    TEST(test_tree_link_rotate_left_nullptr)
    {
      TLink0 r;
      TLink0 a;
      TLink0 b;
      TLink0 c;
      TLink0 d;

      r.clear();
      a.clear();
      c.clear();
      d.clear();
      etl::link_left(r, b);
      etl::link_right(b, a);
      etl::link_left(b, d);
      etl::link_right(a, c);

      etl::link_rotate_left(b, a);

      CHECK(a.etl_parent == &r);
      CHECK(b.etl_parent == &a);
      CHECK(d.etl_parent == &b);
      CHECK(c.etl_parent == &a);
      CHECK(a.etl_left   == &b);
      CHECK(a.etl_right  == &c);
      CHECK(b.etl_left   == &d);
      CHECK(b.etl_right  == nullptr);
    }

    //*************************************************************************
    TEST(test_tree_link_rotate_right)
    {
      TLink0 r;
      TLink0 a;
      TLink0 b;
      TLink0 c;
      TLink0 d;
      TLink0 e;

      r.clear();
      c.clear();
      d.clear();
      e.clear();
      etl::link_left(r, a);
      etl::link_left(a, b);
      etl::link_left(b, d);
      etl::link_right(a, c);
      etl::link_right(b, e);

      etl::link_rotate_right(a, b);

      CHECK(b.etl_parent == &r);
      CHECK(d.etl_parent == &b);
      CHECK(a.etl_parent == &b);
      CHECK(e.etl_parent == &a);
      CHECK(c.etl_parent == &a);
      CHECK(b.etl_left   == &d);
      CHECK(b.etl_right  == &a);
      CHECK(a.etl_left   == &e);
      CHECK(a.etl_right  == &c);
    }

    //*************************************************************************
    TEST(test_tree_link_rotate_right_nullptr)
    {
      TLink0 r;
      TLink0 a;
      TLink0 b;
      TLink0 c;
      TLink0 d;

      r.clear();
      b.clear();
      c.clear();
      d.clear();
      etl::link_left(r, a);
      etl::link_left(a, b);
      etl::link_left(b, d);
      etl::link_right(a, c);

      etl::link_rotate_right(a, b);

      CHECK(b.etl_parent == &r);
      CHECK(d.etl_parent == &b);
      CHECK(a.etl_parent == &b);
      CHECK(c.etl_parent == &a);
      CHECK(b.etl_left   == &d);
      CHECK(b.etl_right  == &a);
      CHECK(a.etl_left   == nullptr);
      CHECK(a.etl_right  == &c);
    }
  };
}
