//
//                              libieeep1788
//
//   An implementation of the preliminary IEEE P1788 standard for
//   interval arithmetic
//
//
//   Copyright 2013 - 2015
//
//   Marco Nehmeier (nehmeier@informatik.uni-wuerzburg.de)
//   Department of Computer Science,
//   University of Wuerzburg, Germany
//
//   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
//
//   UnF<double>::less 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.

#define BOOST_TEST_MODULE "Decorations [p1788/decoration/decoration]"
#include "test/util/boost_test_wrapper.hpp"

#include <sstream>

#include <boost/test/output_test_stream.hpp>

#include "p1788/decoration/decoration.hpp"
#include "p1788/io/io_manip.hpp"

typedef p1788::decoration::decoration DEC;

BOOST_AUTO_TEST_CASE(minimal_decoration_equal_test)
{
    BOOST_CHECK( (DEC::trv == DEC::trv) );
    BOOST_CHECK( !(DEC::trv == DEC::def) );
    BOOST_CHECK( !(DEC::trv == DEC::dac) );
    BOOST_CHECK( !(DEC::trv == DEC::com) );
    BOOST_CHECK( !(DEC::trv == DEC::ill) );

    BOOST_CHECK( !(DEC::def == DEC::trv) );
    BOOST_CHECK( (DEC::def == DEC::def) );
    BOOST_CHECK( !(DEC::def == DEC::dac) );
    BOOST_CHECK( !(DEC::def == DEC::com) );
    BOOST_CHECK( !(DEC::def == DEC::ill) );

    BOOST_CHECK( !(DEC::dac == DEC::trv) );
    BOOST_CHECK( !(DEC::dac == DEC::def) );
    BOOST_CHECK( (DEC::dac == DEC::dac) );
    BOOST_CHECK( !(DEC::dac == DEC::com) );
    BOOST_CHECK( !(DEC::dac == DEC::ill) );

    BOOST_CHECK( !(DEC::com == DEC::trv) );
    BOOST_CHECK( !(DEC::com == DEC::def) );
    BOOST_CHECK( !(DEC::com == DEC::dac) );
    BOOST_CHECK( (DEC::com == DEC::com) );
    BOOST_CHECK( !(DEC::com == DEC::ill) );

    BOOST_CHECK( !(DEC::ill == DEC::trv) );
    BOOST_CHECK( !(DEC::ill == DEC::def) );
    BOOST_CHECK( !(DEC::ill == DEC::dac) );
    BOOST_CHECK( !(DEC::ill == DEC::com) );
    BOOST_CHECK( (DEC::ill == DEC::ill) );
}

BOOST_AUTO_TEST_CASE(minimal_decoration_not_equal_test)
{
    BOOST_CHECK( !(DEC::trv != DEC::trv) );
    BOOST_CHECK( (DEC::trv != DEC::def) );
    BOOST_CHECK( (DEC::trv != DEC::dac) );
    BOOST_CHECK( (DEC::trv != DEC::com) );
    BOOST_CHECK( (DEC::trv != DEC::ill) );

    BOOST_CHECK( (DEC::def != DEC::trv) );
    BOOST_CHECK( !(DEC::def != DEC::def) );
    BOOST_CHECK( (DEC::def != DEC::dac) );
    BOOST_CHECK( (DEC::def != DEC::com) );
    BOOST_CHECK( (DEC::def != DEC::ill) );

    BOOST_CHECK( (DEC::dac != DEC::trv) );
    BOOST_CHECK( (DEC::dac != DEC::def) );
    BOOST_CHECK( !(DEC::dac != DEC::dac) );
    BOOST_CHECK( (DEC::dac != DEC::com) );
    BOOST_CHECK( (DEC::dac != DEC::ill) );

    BOOST_CHECK( (DEC::com != DEC::trv) );
    BOOST_CHECK( (DEC::com != DEC::def) );
    BOOST_CHECK( (DEC::com != DEC::dac) );
    BOOST_CHECK( !(DEC::com != DEC::com) );
    BOOST_CHECK( (DEC::com != DEC::ill) );

    BOOST_CHECK( (DEC::ill != DEC::trv) );
    BOOST_CHECK( (DEC::ill != DEC::def) );
    BOOST_CHECK( (DEC::ill != DEC::dac) );
    BOOST_CHECK( (DEC::ill != DEC::com) );
    BOOST_CHECK( !(DEC::ill != DEC::ill) );
}

BOOST_AUTO_TEST_CASE(minimal_decoration_lower_test)
{
    BOOST_CHECK( !(DEC::trv < DEC::trv) );
    BOOST_CHECK( (DEC::trv < DEC::def) );
    BOOST_CHECK( (DEC::trv < DEC::dac) );
    BOOST_CHECK( (DEC::trv < DEC::com) );
    BOOST_CHECK( !(DEC::trv < DEC::ill) );

    BOOST_CHECK( !(DEC::def < DEC::trv) );
    BOOST_CHECK( !(DEC::def < DEC::def) );
    BOOST_CHECK( (DEC::def < DEC::dac) );
    BOOST_CHECK( (DEC::def < DEC::com) );
    BOOST_CHECK( !(DEC::def < DEC::ill) );

    BOOST_CHECK( !(DEC::dac < DEC::trv) );
    BOOST_CHECK( !(DEC::dac < DEC::def) );
    BOOST_CHECK( !(DEC::dac < DEC::dac) );
    BOOST_CHECK( (DEC::dac < DEC::com) );
    BOOST_CHECK( !(DEC::dac < DEC::ill) );

    BOOST_CHECK( !(DEC::com < DEC::trv) );
    BOOST_CHECK( !(DEC::com < DEC::def) );
    BOOST_CHECK( !(DEC::com < DEC::dac) );
    BOOST_CHECK( !(DEC::com < DEC::com) );
    BOOST_CHECK( !(DEC::com < DEC::ill) );

    BOOST_CHECK( (DEC::ill < DEC::trv) );
    BOOST_CHECK( (DEC::ill < DEC::def) );
    BOOST_CHECK( (DEC::ill < DEC::dac) );
    BOOST_CHECK( (DEC::ill < DEC::com) );
    BOOST_CHECK( !(DEC::ill < DEC::ill) );
}

BOOST_AUTO_TEST_CASE(minimal_decoration_lower_equal_test)
{
    BOOST_CHECK( (DEC::trv <= DEC::trv) );
    BOOST_CHECK( (DEC::trv <= DEC::def) );
    BOOST_CHECK( (DEC::trv <= DEC::dac) );
    BOOST_CHECK( (DEC::trv <= DEC::com) );
    BOOST_CHECK( !(DEC::trv <= DEC::ill) );

    BOOST_CHECK( !(DEC::def <= DEC::trv) );
    BOOST_CHECK( (DEC::def <= DEC::def) );
    BOOST_CHECK( (DEC::def <= DEC::dac) );
    BOOST_CHECK( (DEC::def <= DEC::com) );
    BOOST_CHECK( !(DEC::def <= DEC::ill) );

    BOOST_CHECK( !(DEC::dac <= DEC::trv) );
    BOOST_CHECK( !(DEC::dac <= DEC::def) );
    BOOST_CHECK( (DEC::dac <= DEC::dac) );
    BOOST_CHECK( (DEC::dac <= DEC::com) );
    BOOST_CHECK( !(DEC::dac <= DEC::ill) );

    BOOST_CHECK( !(DEC::com <= DEC::trv) );
    BOOST_CHECK( !(DEC::com <= DEC::def) );
    BOOST_CHECK( !(DEC::com <= DEC::dac) );
    BOOST_CHECK( (DEC::com <= DEC::com) );
    BOOST_CHECK( !(DEC::com <= DEC::ill) );

    BOOST_CHECK( (DEC::ill <= DEC::trv) );
    BOOST_CHECK( (DEC::ill <= DEC::def) );
    BOOST_CHECK( (DEC::ill <= DEC::dac) );
    BOOST_CHECK( (DEC::ill <= DEC::com) );
    BOOST_CHECK( (DEC::ill <= DEC::ill) );
}

BOOST_AUTO_TEST_CASE(minimal_decoration_greater_test)
{
    BOOST_CHECK( !(DEC::trv > DEC::trv) );
    BOOST_CHECK( !(DEC::trv > DEC::def) );
    BOOST_CHECK( !(DEC::trv > DEC::dac) );
    BOOST_CHECK( !(DEC::trv > DEC::com) );
    BOOST_CHECK( (DEC::trv > DEC::ill) );

    BOOST_CHECK( (DEC::def > DEC::trv) );
    BOOST_CHECK( !(DEC::def > DEC::def) );
    BOOST_CHECK( !(DEC::def > DEC::dac) );
    BOOST_CHECK( !(DEC::def > DEC::com) );
    BOOST_CHECK( (DEC::def > DEC::ill) );

    BOOST_CHECK( (DEC::dac > DEC::trv) );
    BOOST_CHECK( (DEC::dac > DEC::def) );
    BOOST_CHECK( !(DEC::dac > DEC::dac) );
    BOOST_CHECK( !(DEC::dac > DEC::com) );
    BOOST_CHECK( (DEC::dac > DEC::ill) );

    BOOST_CHECK( (DEC::com > DEC::trv) );
    BOOST_CHECK( (DEC::com > DEC::def) );
    BOOST_CHECK( (DEC::com > DEC::dac) );
    BOOST_CHECK( !(DEC::com > DEC::com) );
    BOOST_CHECK( (DEC::com > DEC::ill) );

    BOOST_CHECK( !(DEC::ill > DEC::trv) );
    BOOST_CHECK( !(DEC::ill > DEC::def) );
    BOOST_CHECK( !(DEC::ill > DEC::dac) );
    BOOST_CHECK( !(DEC::ill > DEC::com) );
    BOOST_CHECK( !(DEC::ill > DEC::ill) );
}

BOOST_AUTO_TEST_CASE(minimal_decoration_greater_equal_test)
{
    BOOST_CHECK( (DEC::trv >= DEC::trv) );
    BOOST_CHECK( !(DEC::trv >= DEC::def) );
    BOOST_CHECK( !(DEC::trv >= DEC::dac) );
    BOOST_CHECK( !(DEC::trv >= DEC::com) );
    BOOST_CHECK( (DEC::trv >= DEC::ill) );

    BOOST_CHECK( (DEC::def >= DEC::trv) );
    BOOST_CHECK( (DEC::def >= DEC::def) );
    BOOST_CHECK( !(DEC::def >= DEC::dac) );
    BOOST_CHECK( !(DEC::def >= DEC::com) );
    BOOST_CHECK( (DEC::def >= DEC::ill) );

    BOOST_CHECK( (DEC::dac >= DEC::trv) );
    BOOST_CHECK( (DEC::dac >= DEC::def) );
    BOOST_CHECK( (DEC::dac >= DEC::dac) );
    BOOST_CHECK( !(DEC::dac >= DEC::com) );
    BOOST_CHECK( (DEC::dac >= DEC::ill) );

    BOOST_CHECK( (DEC::com >= DEC::trv) );
    BOOST_CHECK( (DEC::com >= DEC::def) );
    BOOST_CHECK( (DEC::com >= DEC::dac) );
    BOOST_CHECK( (DEC::com >= DEC::com) );
    BOOST_CHECK( (DEC::com >= DEC::ill) );

    BOOST_CHECK( !(DEC::ill >= DEC::trv) );
    BOOST_CHECK( !(DEC::ill >= DEC::def) );
    BOOST_CHECK( !(DEC::ill >= DEC::dac) );
    BOOST_CHECK( !(DEC::ill >= DEC::com) );
    BOOST_CHECK( (DEC::ill >= DEC::ill) );
}

BOOST_AUTO_TEST_CASE(minimal_decoration_is_valid_test)
{
    p1788::exception::clear();

    BOOST_CHECK( p1788::decoration::is_valid(DEC::ill) );
    BOOST_CHECK( p1788::decoration::is_valid(DEC::trv) );
    BOOST_CHECK( p1788::decoration::is_valid(DEC::def) );
    BOOST_CHECK( p1788::decoration::is_valid(DEC::dac) );
    BOOST_CHECK( p1788::decoration::is_valid(DEC::com) );

    BOOST_CHECK(!p1788::exception::invalid_operand());

    DEC bad_dec = static_cast<p1788::decoration::decoration>(13);

    BOOST_CHECK( !p1788::decoration::is_valid(bad_dec) );
    BOOST_CHECK(p1788::exception::invalid_operand());
    p1788::exception::clear();

    p1788::exception::set_throw_exception_cwd(p1788::exception::invalid_operand_bit);
    BOOST_CHECK_THROW(p1788::decoration::is_valid(bad_dec), p1788::exception::invalid_operand_exception);
    BOOST_CHECK(p1788::exception::invalid_operand());
    p1788::exception::clear();
    p1788::exception::set_throw_exception_cwd(p1788::exception::none_bit);
}

BOOST_AUTO_TEST_CASE(minimal_decoration_output_test)
{
    boost::test_tools::output_test_stream output;

    output << DEC::trv;
    BOOST_CHECK( output.is_equal( "trv" ) );

    output << DEC::def;
    BOOST_CHECK( output.is_equal( "def" ) );

    output << DEC::dac;
    BOOST_CHECK( output.is_equal( "dac" ) );

    output << DEC::com;
    BOOST_CHECK( output.is_equal( "com" ) );

    output << DEC::ill;
    BOOST_CHECK( output.is_equal( "ill" ) );

    output << p1788::io::dec_numeric;

    output << DEC::trv;
    BOOST_CHECK( output.is_equal( "4" ) );

    output << DEC::def;
    BOOST_CHECK( output.is_equal( "8" ) );

    output << DEC::dac;
    BOOST_CHECK( output.is_equal( "12" ) );

    output << DEC::com;
    BOOST_CHECK( output.is_equal( "16" ) );

    output << DEC::ill;
    BOOST_CHECK( output.is_equal( "0" ) );

    output << p1788::io::dec_alpha;
    output << p1788::io::upper_case;

    output << DEC::com;
    BOOST_CHECK( output.is_equal( "COM" ) );

    output << DEC::ill;
    BOOST_CHECK( output.is_equal( "ILL" ) );

    output << DEC::trv;
    BOOST_CHECK( output.is_equal( "TRV" ) );

    output << DEC::def;
    BOOST_CHECK( output.is_equal( "DEF" ) );

    output << DEC::dac;
    BOOST_CHECK( output.is_equal( "DAC" ) );

    output << p1788::io::lower_case;
    output << DEC::com;
    BOOST_CHECK( output.is_equal( "com" ) );

    output << DEC::ill;
    BOOST_CHECK( output.is_equal( "ill" ) );
}

BOOST_AUTO_TEST_CASE(minimal_decoration_input_test)
{
    {
        DEC dec;
        std::istringstream is("ill");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::ill);
        BOOST_CHECK(is);
    }

    {
        DEC dec;
        std::istringstream is("0");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::ill);
        BOOST_CHECK(is);
    }

    {
        DEC dec;
        std::istringstream is("trv");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::trv);
        BOOST_CHECK(is);
    }

    {
        DEC dec;
        std::istringstream is("4");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::trv);
        BOOST_CHECK(is);
    }

    {
        DEC dec;
        std::istringstream is("def");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::def);
        BOOST_CHECK(is);
    }

    {
        DEC dec;
        std::istringstream is("8");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::def);
        BOOST_CHECK(is);
    }

    {
        DEC dec;
        std::istringstream is("dac");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::dac);
        BOOST_CHECK(is);
    }

    {
        DEC dec;
        std::istringstream is("12");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::dac);
        BOOST_CHECK(is);
    }

    {
        DEC dec;
        std::istringstream is("com");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::com);
        BOOST_CHECK(is);
    }

    {
        DEC dec;
        std::istringstream is("16");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::com);
        BOOST_CHECK(is);
    }

    {
        DEC dec;
        std::istringstream is("dac4");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::dac);
        BOOST_CHECK(is);
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::trv);
        BOOST_CHECK(is);
    }

    {
        DEC dec;
        std::istringstream is("8 dac 4");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::def);
        BOOST_CHECK(is);
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::dac);
        BOOST_CHECK(is);
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::trv);
        BOOST_CHECK(is);
    }

    {
        DEC dec;
        std::istringstream is("  DAC  16");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::dac);
        BOOST_CHECK(is);
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::com);
        BOOST_CHECK(is);
    }

    {
        DEC dec = DEC::com;
        std::istringstream is("  def");
        is >> std::noskipws;
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::com);
        BOOST_CHECK(is.fail());
    }

    {
        DEC dec = DEC::trv;
        std::istringstream is("  12");
        is >> std::noskipws;
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::trv);
        BOOST_CHECK(is.fail());
    }

    {
        DEC dec;
        std::istringstream is("\n 4 \t DEF \n");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::trv);
        BOOST_CHECK(is);
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::def);
        BOOST_CHECK(is);
    }

    {
        DEC dec = DEC::trv;
        std::istringstream is("foo com");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::trv);
        BOOST_CHECK(is.fail());
    }

    {
        DEC dec = DEC::dac;
        std::istringstream is(" \t \n \t ");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::dac);
        BOOST_CHECK(is.fail());
    }

    {
        DEC dec = DEC::com;
        std::istringstream is("7");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::com);
        BOOST_CHECK(is.fail());
    }

    {
        DEC dec = DEC::def;
        std::istringstream is("-2");
        is >> dec;
        BOOST_CHECK_EQUAL(dec, DEC::def);
        BOOST_CHECK(is.fail());
    }
}


#include <any>
#include <string>
#include <vector>

#include <boost/test/unit_test.hpp>

#include "BinarySearchTree.hpp"
#include "Traversal.hpp"

using namespace CppSampleCode;

// Test data to build a tree
const std::vector<std::pair<int32_t, std::string>> TEST_DATA{
    {23, "23"}, {4, "4"},   {30, "30"}, {11, "11"}, {7, "7"}, {34, "34"},
    {20, "20"}, {24, "24"}, {22, "22"}, {15, "15"}, {1, "1"}};

BOOST_AUTO_TEST_SUITE(BinarySearchTreeTestsSuite)

BOOST_AUTO_TEST_CASE(BasicTest)
{
    BinarySearchTree tree;

    for (const auto &item : TEST_DATA)
    {
        tree.insertNode(item.first, std::make_any<std::string>(item.second));
    }

    BOOST_CHECK(!tree.empty()); // NOLINT

    BOOST_CHECK_EQUAL(tree.getLeftmost(tree.getRoot())->key, 1);
    BOOST_CHECK_EQUAL(
        std::any_cast<std::string>(tree.getLeftmost(tree.getRoot())->data), "1");

    BOOST_CHECK_EQUAL(tree.getRightmost(tree.getRoot())->key, 34);
    BOOST_CHECK_EQUAL(
        std::any_cast<std::string>(tree.getRightmost(tree.getRoot())->data), "34");

    BOOST_CHECK_EQUAL(std::any_cast<std::string>(tree.search(24)->data), "24");

    BOOST_CHECK_EQUAL(tree.getPredecessor(tree.getRoot())->key, 22);
    BOOST_CHECK_EQUAL(tree.getSuccessor(tree.getRoot())->key, 24);

    tree.deleteNode(15); // NOLINT
    tree.deleteNode(22); // NOLINT
    tree.deleteNode(7);  // NOLINT
    tree.deleteNode(20); // NOLINT

    BOOST_CHECK(!tree.search(15)); // NOLINT
}

BOOST_AUTO_TEST_CASE(DeletionTest)
{
    BinarySearchTree_p tree{std::make_shared<BinarySearchTree>()};

    for (const auto &item : TEST_DATA)
    {
        tree->insertNode(item.first, std::make_any<std::string>(item.second));
    }

    // No child
    tree->deleteNode(15); // NOLINT
    {
        const std::vector<std::pair<int32_t, std::string>> expectedData{
            {23, "23"}, {4, "4"},   {30, "30"}, {1, "1"},   {11, "11"},
            {24, "24"}, {34, "34"}, {7, "7"},   {20, "20"}, {22, "22"}};

        TraversalOutput output = levelOrderTraverse(tree);

        for (size_t index = 0; index < output.size(); ++index)
        {
            BOOST_CHECK(output.at(index).first == expectedData.at(index).first); // NOLINT
            BOOST_CHECK(std::any_cast<std::string>(output.at(index).second) ==   // NOLINT
                        expectedData.at(index).second);
        }
    }

    // One right child
    tree->deleteNode(20); // NOLINT
    {
        const std::vector<std::pair<int32_t, std::string>> expectedData{
            {23, "23"}, {4, "4"},   {30, "30"}, {1, "1"},  {11, "11"},
            {24, "24"}, {34, "34"}, {7, "7"},   {22, "22"}};
        TraversalOutput output = levelOrderTraverse(tree);

        for (size_t index = 0; index < output.size(); ++index)
        {
            BOOST_CHECK(output.at(index).first == expectedData.at(index).first); // NOLINT
            BOOST_CHECK(std::any_cast<std::string>(output.at(index).second) ==   // NOLINT
                        expectedData.at(index).second);
        }
    }

    // One left child
    tree->insertNode(17, std::make_any<std::string>("17")); // NOLINT
    tree->deleteNode(22); // NOLINT
    {
        const std::vector<std::pair<int32_t, std::string>> expectedData{
            {23, "23"}, {4, "4"},   {30, "30"}, {1, "1"},  {11, "11"},
            {24, "24"}, {34, "34"}, {7, "7"},   {17, "17"}};
        TraversalOutput output = levelOrderTraverse(tree);

        for (size_t index = 0; index < output.size(); ++index)
        {
            BOOST_CHECK(output.at(index).first == expectedData.at(index).first); // NOLINT

            BOOST_CHECK(std::any_cast<std::string>(output.at(index).second) ==   // NOLINT
                        expectedData.at(index).second);
        }
    }

    // Two children
    tree->deleteNode(11); // NOLINT
    {
        const std::vector<std::pair<int32_t, std::string>> expectedData{
            {23, "23"}, {4, "4"},   {30, "30"}, {1, "1"},
            {17, "17"}, {24, "24"}, {34, "34"}, {7, "7"}};
        TraversalOutput output = levelOrderTraverse(tree);

        for (size_t index = 0; index < output.size(); ++index)
        {
            BOOST_CHECK(output.at(index).first == expectedData.at(index).first); // NOLINT
            BOOST_CHECK(std::any_cast<std::string>(output.at(index).second) ==   // NOLINT
                        expectedData.at(index).second);
        }
    }
}

BOOST_AUTO_TEST_SUITE_END()


/*! \file demo_1d_x_external.cpp
  \brief 1D plot from two vectors of doubles, showing axis label variation.
  \author Jacob Voytko and Paul A. Bristow
  \date 2009
*/

// Copyright Jacob Voytko 2007
// Copyright Paul A. Bristow 2009

// Distributed under the Boost Software License, Version 1.0.
// For more information, see http://www.boost.org

#include <boost/svg_plot/svg_1d_plot.hpp>
#include <vector>

using std::vector;
using namespace boost::svg;

//enum x_axis_intersect
//{ //! \enum x_axis_intersect If and how the X axes intersects Y axis.
//  bottom = -1, //!< X-axis free below bottom of end of Y-axis (case of all Y definitely < 0).
//  x_intersects_y = 0, //!< x_intersects_y when Y values include zero, so X intersects the Y axis.
//  top = +1 //!< X-axis free above top of X-axis (case of all Y definitely > 0).
//  };

int main()
{
//[demo_1d_x_external_1

/*`Following previous examples, we set up two containers for two data series.
*/
  vector<double> dan_times;
  vector<double> elaine_times;

  dan_times.push_back(3.1);
  dan_times.push_back(4.2);
  elaine_times.push_back(2.1);
  elaine_times.push_back(7.8);

  svg_1d_plot my_plot;

  // Adding some generic settings.
  my_plot.background_border_color(black)
         .legend_on(true)
         .plot_window_on(true)
         .title("Race Times")
         .x_range(-1, 10);

/*`We add tastelessly color the grids for both major and minor ticks, and switch both grids on.
*/
  my_plot.x_major_grid_color(pink)
         .x_minor_grid_color(lightgray);

  my_plot.x_major_grid_on(true)
         .x_minor_grid_on(true);

/*`Also we specify the position of the labelling of the X-axis.
It can be controlled using values in the `enum x_axis_intersect`.
``
  enum x_axis_intersect
  { //! \enum x_axis_intersect
    bottom = -1, // On the bottom of the plot window.
    x_intersects_y = 0, // On the Y axis (in the middle of the plot window).
    top = +1 // On the top of the plot window.
  };
``
For this example, we choose to show the X axis and tick value labels at the top of the plot window.
*/
  my_plot.x_ticks_on_window_or_axis(top); // on top, not on axis.

  // Write to plot.
  my_plot.plot(dan_times, "Dan").stroke_color(blue);
  my_plot.plot(elaine_times, "Elaine").stroke_color(orange);

  // Write to file.
  my_plot.write("./demo_1d_x_external.svg");
  return 0;
} // int main()
//] [/demo_1d_x_external_1]

/*
Output:

Compiling...
demo_1d_x_external.cpp
Linking...
Embedding manifest...
Autorun "j:\Cpp\SVG\debug\demo_1d_x_external.exe"
*/


/**
 * \file boost/numeric/ublasx/operation/num_rows.hpp
 *
 * \brief The \c num_rows operation.
 *
 * Copyright (c) 2009, Marco Guazzone
 *
 * Distributed under the Boost Software License, Version 1.0. (See
 * accompanying file LICENSE_1_0.txt or copy at
 * http://www.boost.org/LICENSE_1_0.txt)
 *
 * \author Marco Guazzone, marco.guazzone@gmail.com
 */

#ifndef BOOST_NUMERIC_UBLASX_OPERATION_NUM_ROWS_HPP
#define BOOST_NUMERIC_UBLASX_OPERATION_NUM_ROWS_HPP

/*
#include <boost/version.hpp>

#if BOOST_VERSION > 105100L

#include <boost/numeric/ublas/operation/num_rows.hpp>

namespace boost { namespace numeric { namespace ublasx {

using ::boost::numeric::ublas::num_rows;

}}} // Namespace boost::numeric::ublasx

#else // BOOST_VERSION

#include <boost/numeric/ublas/detail/config.hpp>
#include <boost/numeric/ublas/expression_types.hpp>
#include <boost/numeric/ublas/traits.hpp>

namespace boost { namespace numeric { namespace ublasx {

template <typename MatrixExprT>
BOOST_UBLAS_INLINE
typename matrix_traits<MatrixExprT>::size_type num_rows(matrix_expression<MatrixExprT> const& me)
{
	return me().size1();
}

}}} // Namespace boost::numeric::ublasx

#endif // BOOST_VERSION
*/
#include <boost/numeric/ublas/operation/num_rows.hpp>

namespace boost { namespace numeric { namespace ublasx {

using ::boost::numeric::ublas::num_rows;

}}} // Namespace boost::numeric::ublasx

#endif // BOOST_NUMERIC_UBLASX_OPERATION_NUM_ROWS_HPP


/* Copyright (C) 2020 IBM Corp.
 * This program is 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. See accompanying LICENSE file.
 */

/**
 * @file matmul.h
 * @brief some matrix / linear algebra stuff
 */

#include <numeric>

#include <NTL/BasicThreadPool.h>

#include <helib/matmul.h>
#include <helib/fhe_stats.h>
#include <helib/debugging.h>

#if (defined(__unix__) || defined(__unix) || defined(unix))
#include <sys/time.h>
#include <sys/resource.h>
#endif

#include "gtest/gtest.h"
#include "test_common.h"

namespace {

struct Parameters
{
  Parameters(long m,
             long r,
             long bits,
             long nt,
             int force_bsgs,
             int force_hoist,
             int ks_strategy) :
      m(m),
      r(r),
      bits(bits),
      nt(nt),
      force_bsgs(force_bsgs),
      force_hoist(force_hoist),
      ks_strategy(ks_strategy){};

  const long m;          // defines the cyclotomic polynomial Phi_m(X)
  const long r;          // Bit precision
  const long bits;       // # bits in the modulus chain
  const long nt;         // # threads
  const int force_bsgs;  // 1 to force on, -1 to force off
  const int force_hoist; // -1 to force off
  const int ks_strategy; // 0: default, 1: full, 2: bsgs, 3: minimal

  friend std::ostream& operator<<(std::ostream& os, const Parameters& params)
  {
    return os << "{"
              << "m=" << params.m << ","
              << "r=" << params.r << ","
              << "bits=" << params.bits << ","
              << "nt=" << params.nt << ","
              << "force_bsgs=" << params.force_bsgs << ","
              << "force_hoist=" << params.force_hoist << ","
              << "ks_strategy=" << params.ks_strategy << "}";
  }
};

class TestMatmulCKKS : public ::testing::TestWithParam<Parameters>
{
protected:
  static void setGlobals(long force_bsgs, long force_hoist)
  {
    helib::fhe_test_force_bsgs = force_bsgs;
    helib::fhe_test_force_hoist = force_hoist;
  }

  const long m;
  const long r;
  const long bits;
  const long nt;

  helib::Context context;
  helib::SecKey secretKey;
  const helib::PubKey publicKey;
  const helib::EncryptedArray& ea;

  TestMatmulCKKS() :
      m(GetParam().m),
      r(GetParam().r),
      bits(GetParam().bits),
      nt(GetParam().nt),
      context(m, /*p=*/-1, r),
      secretKey((buildModChain(context, bits), context)),
      publicKey(keySetup(secretKey, GetParam().ks_strategy)),
      ea(*(context.ea))
  {}

  static helib::SecKey& keySetup(helib::SecKey& secretKey, int ks_strategy)
  {
    secretKey.GenSecKey();
    // We call addSomeFrbMatrices for all strategies except minimal
    switch (ks_strategy) {
    case 0:
      addSome1DMatrices(secretKey);
      addSomeFrbMatrices(secretKey);
      break;
    case 1:
      add1DMatrices(secretKey);
      addSomeFrbMatrices(secretKey);
      break;
    case 2:
      addBSGS1DMatrices(secretKey);
      addSomeFrbMatrices(secretKey);
      break;
    case 3:
      addMinimal1DMatrices(secretKey);
      addMinimalFrbMatrices(secretKey);
      break;

    default:
      NTL::Error("bad ks_strategy");
    }
    return secretKey;
  }

  virtual void SetUp() override
  {
    if (helib_test::verbose) {
      context.zMStar.printout();
      std::cout << "# small primes = " << context.smallPrimes.card() << "\n"
                << "# ctxt primes = " << context.ctxtPrimes.card() << "\n"
                << "# bits in ctxt primes = "
                << long(context.logOfProduct(context.ctxtPrimes) / log(2.0) +
                        0.5)
                << "\n"
                << "# special primes = " << context.specialPrimes.card() << "\n"
                << "# bits in special primes = "
                << long(context.logOfProduct(context.specialPrimes) / log(2.0) +
                        0.5)
                << "\n";

      helib::fhe_stats = true;
    }
    helib::setupDebugGlobals(&secretKey, context.ea);
  }

  virtual void TearDown() override
  {
    if (helib_test::verbose) {
      helib::printAllTimers();
#if (defined(__unix__) || defined(__unix) || defined(unix))
      struct rusage rusage;
      getrusage(RUSAGE_SELF, &rusage);
      std::cout << "  rusage.ru_maxrss=" << rusage.ru_maxrss << std::endl;
#endif
      helib::print_stats(std::cout);
    }
    helib::cleanupDebugGlobals();
  }
};

TEST_P(TestMatmulCKKS, vectorToMatrixMultiplication)
{
  std::vector<double> v(ea.size());
  std::iota(v.begin(), v.end(), 1);

  helib::MatMul_CKKS_Complex mat(context, [&v](long i, long j) {
    return ((i + j) % v.size()) / double(v.size());
  });
  // Note the use of a "lambda": this allows for quite
  // general ways to describe a matrix with minimal fuss

  helib::Ctxt ctxt(publicKey);
  // Initialize ptxt with the vector v
  helib::PtxtArray ptxt(context, v);

  // Encrypt ptxt. We have to supply *some* upper bound on the magnitude
  // of the slots.
  ptxt.encrypt(ctxt);

  // Perform the linear transformation on the encrypted data
  helib::EncodedMatMul_CKKS emat(mat);
  // emat.upgrade();
  ctxt *= emat;

  // We can also do it this way:
  // ctxt *= mat;

  // Perform the linear transformation on the plaintext data
  ptxt *= mat;

  helib::PtxtArray ptxt1(context);
  ptxt1.decrypt(ctxt, secretKey);

  // w1 is the result of performing the transformation on
  // the encrypted data
  std::vector<double> w1;
  ptxt1.store(w1);

  // w is the result of performing the transformation on
  // the encrypted data
  std::vector<double> w;
  ptxt.store(w);

  for (long i = 0; i < ea.size(); ++i) {
    EXPECT_NEAR(w[i], w1[i], 0.01);
  }
}

// clang-format off
INSTANTIATE_TEST_SUITE_P(typicalParameters, TestMatmulCKKS, ::testing::Values(
      Parameters(/*m=*/16, /*r=*/10, /*bits=*/200, /*nt=*/1, /*force_bsgs=*/0, /*force_hoist=*/0, /*ks_strategy=*/0)
      ));
// clang-format on

} // namespace


<<<<<<< HEAD
/*    Copyright (c) 2010-2018, Delft University of Technology
=======
/*    Copyright (c) 2010-2019, Delft University of Technology
>>>>>>> origin/master
 *    All rigths reserved
 *
 *    This file is part of the Tudat. Redistribution and use in source and
 *    binary forms, with or without modification, are permitted exclusively
 *    under the terms of the Modified BSD license. You should have received
 *    a copy of the license with this file. If not, please or visit:
 *    http://tudat.tudelft.nl/LICENSE.
 */

#define BOOST_TEST_MAIN

#include <boost/test/unit_test.hpp>
#include <boost/make_shared.hpp>

#include "Tudat/Basics/testMacros.h"

#include "Tudat/External/SpiceInterface/spiceInterface.h"
#include "Tudat/InputOutput/basicInputOutput.h"

#include "Tudat/Astrodynamics/Ephemerides/frameManager.h"
#include "Tudat/Astrodynamics/Ephemerides/constantEphemeris.h"

namespace tudat
{
namespace unit_tests
{

using namespace tudat::ephemerides;
using namespace tudat::spice_interface;

BOOST_AUTO_TEST_SUITE( test_frame_manager )

BOOST_AUTO_TEST_CASE( test_FrameManager )
{

    //Load spice kernels.
    spice_interface::loadStandardSpiceKernels( );

    std::map< std::string, std::shared_ptr< Ephemeris > > ephemerisList;

    Eigen::Vector6d barycentricSunState = getBodyCartesianStateAtEpoch( "Sun", getBaseFrameName( ), "ECLIPJ2000", "NONE", 0.0 );
    ephemerisList[ "Sun" ] = std::make_shared< ConstantEphemeris >( barycentricSunState, getBaseFrameName( ), "ECLIPJ2000" );

    Eigen::Vector6d sunCentricEarthState = getBodyCartesianStateAtEpoch( "Earth", "Sun", "ECLIPJ2000", "NONE", 0.0 );
    ephemerisList[ "Earth" ] = std::make_shared< ConstantEphemeris >( sunCentricEarthState, "Sun", "ECLIPJ2000" );

    Eigen::Vector6d earthCentricMoonState = getBodyCartesianStateAtEpoch( "Moon", "Earth", "ECLIPJ2000", "NONE", 0.0 );
    ephemerisList[ "Moon" ] = std::make_shared< ConstantEphemeris >( earthCentricMoonState, "Earth", "ECLIPJ2000" );

    Eigen::Vector6d earthCentricLageosState = Eigen::Vector6d::Zero( );
    earthCentricLageosState( 1 ) = 2.5E6;
    earthCentricLageosState( 2 ) = 4.0E6;
    ephemerisList[ "LAGEOS" ] = std::make_shared< ConstantEphemeris >( earthCentricLageosState, "Earth", "ECLIPJ2000" );

    Eigen::Vector6d moonCentricLroState = Eigen::Vector6d::Zero( );
    moonCentricLroState( 0 ) = 1.0E6;
    moonCentricLroState( 1 ) = 2.0E6;

    ephemerisList[ "LRO" ] = std::make_shared< ConstantEphemeris >( moonCentricLroState, "Moon", "ECLIPJ2000" );

    Eigen::Vector6d sunCentricMarsState = getBodyCartesianStateAtEpoch( "Mars", "Sun", "ECLIPJ2000", "NONE", 0.0 );
    ephemerisList[ "Mars" ] = std::make_shared< ConstantEphemeris >( sunCentricMarsState, "Sun", "ECLIPJ2000" );

    Eigen::Vector6d marsCentricPhobosState = Eigen::Vector6d::Zero( );
    marsCentricPhobosState( 0 ) = 2.3E5;
    marsCentricPhobosState( 1 ) = 2.9E4;
    marsCentricPhobosState( 2 ) = 600;

    ephemerisList[ "Phobos" ] = std::make_shared< ConstantEphemeris >( marsCentricPhobosState, "Mars", "ECLIPJ2000" );

    std::shared_ptr< ReferenceFrameManager > frameManager = std::make_shared< ReferenceFrameManager >( ephemerisList );

    std::map< std::string, int > expectedFrameLevel;
    expectedFrameLevel[ "Sun" ] = 0;
    expectedFrameLevel[ "Earth" ] = 1;
    expectedFrameLevel[ "Mars" ] = 1;
    expectedFrameLevel[ "LAGEOS" ] = 2;
    expectedFrameLevel[ "Moon" ] = 2;
    expectedFrameLevel[ "Phobos" ] = 2;
    expectedFrameLevel[ "LRO" ] = 3;

    for( std::map< std::string, int >::iterator it = expectedFrameLevel.begin( ); it != expectedFrameLevel.end( ); it++ )
    {
        if( frameManager->getFrameLevel( it->first ).first != it->second )
        {
            throw std::runtime_error(
                        "Error when identifying frame level of " + it->first + " found " +
                        std::to_string( frameManager->getFrameLevel( it->first ).first ) + " expected" +
                        std::to_string( it->second ) );
        }
    }

    std::vector< std::string > frames;
    frames.push_back( "Earth" );
    frames.push_back( "Mars" );
    std::pair< std::string, int > commonFrame = frameManager->getNearestCommonFrame( frames );
    BOOST_CHECK_EQUAL( commonFrame.first, "Sun" );
    BOOST_CHECK_EQUAL( commonFrame.second, 0 );

    frames.clear( );
    frames.push_back( "Moon" );
    frames.push_back( "Sun" );

    commonFrame = frameManager->getNearestCommonFrame( frames );

    BOOST_CHECK_EQUAL( commonFrame.first, "Sun" );
    BOOST_CHECK_EQUAL( commonFrame.second, 0 );

    frames.clear( );
    frames.push_back( "Earth" );
    frames.push_back( "LRO" );

    commonFrame = frameManager->getNearestCommonFrame( frames );

    BOOST_CHECK_EQUAL( commonFrame.first, "Earth" );
    BOOST_CHECK_EQUAL( commonFrame.second, 1 );

    Eigen::Vector6d testState = frameManager->getEphemeris< >( "Moon", "LAGEOS" )->getCartesianState( 0.0 );
    Eigen::Vector6d expectedState = earthCentricLageosState - earthCentricMoonState;
    TUDAT_CHECK_MATRIX_CLOSE_FRACTION( testState, expectedState, std::numeric_limits< double >::epsilon( ) );

    testState = frameManager->getEphemeris( "Phobos", "Sun" )->getCartesianState( 0.0 );
    expectedState = sunCentricMarsState + marsCentricPhobosState;
    TUDAT_CHECK_MATRIX_CLOSE_FRACTION( testState, ( -1.0 * expectedState ), std::numeric_limits< double >::epsilon( ) );

    testState = frameManager->getEphemeris( "Sun", "Phobos" )->getCartesianState( 0.0 );
    TUDAT_CHECK_MATRIX_CLOSE_FRACTION( testState, expectedState, std::numeric_limits< double >::epsilon( ) );

    testState = frameManager->getEphemeris( "Sun", "Earth" )->getCartesianState( 0.0 );
    expectedState = sunCentricEarthState;
    TUDAT_CHECK_MATRIX_CLOSE_FRACTION( testState, expectedState, std::numeric_limits< double >::epsilon( ) );

    testState = frameManager->getEphemeris( getBaseFrameName( ), "Earth" )->getCartesianState( 0.0 );
    expectedState = barycentricSunState + sunCentricEarthState;
    TUDAT_CHECK_MATRIX_CLOSE_FRACTION( testState, expectedState, std::numeric_limits< double >::epsilon( ) );

    testState = frameManager->getEphemeris( "Earth", getBaseFrameName( ) )->getCartesianState( 0.0 );
    TUDAT_CHECK_MATRIX_CLOSE_FRACTION( testState, ( -1.0 * expectedState ), std::numeric_limits< double >::epsilon( ) );

}

BOOST_AUTO_TEST_SUITE_END( )

}

}


/* Daniel R. Reynolds
   SMU Mathematics
   6 August 2020 */

// Inclusions
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <armadillo>
using namespace std;

// prototypes of other functions
int GramSchmidt(arma::mat& X);

// Example routine to test the Mat class
int main(int argc, char* argv[]) {

  // create a row vector of length 5
  arma::rowvec a(5);
  a.fill(0.0);

  // create a row vec with an existing data array
  double dat1[5] = {0.1, 0.2, 0.3, 0.4, 0.5};
  arma::rowvec b(dat1, 5);

  // create a column vec with an existing vector
  double dat2[5] = {0.1, 0.2, 0.3, 0.4, 0.5};
  arma::vec b2(dat2, 5);

  // create a row vector using linspace
  arma::rowvec c = arma::linspace<arma::rowvec>(1.0, 5.0, 5);

  // create a column vector using the single integer constructor
  arma::vec h(7);

  // output vectors above to screen
  cout << "writing array of zeros:\n";
  cout << a << endl;
  cout << "writing array of 0.1,0.2,0.3,0.4,0.5:\n";
  cout << b << endl;
  cout << "writing (column) array of 0.1,0.2,0.3,0.4,0.5:\n";
  cout << b2 << endl;
  cout << "writing array of 1,2,3,4,5:\n";
  cout << c << endl;
  cout << "writing a column vector of 7 zeros:\n";
  cout << h << endl;

  // verify that b has size 5
  if (b.n_elem != 5)
    cerr << "error: incorrect matrix size\n";
  if (b.n_cols != 5)
    cerr << "error: incorrect matrix columns\n";
  if (b.n_rows != 1)
    cerr << "error: incorrect matrix rows\n";

  // edit entries of a in both matrix forms, and write each entry of a to screen
  a(0)  = 10.0;
  a(1)  = 15.0;
  a[2] = 20.0;
  a(0,3) = 25.0;
  a.at(4) = 30.0;
  cout << "entries of a, one at a time: should give 10, 15, 20, 25, 30\n";
  for (size_t i=0; i<a.n_elem; i++)
    cout << "  " << a[i] << endl;

  // write the values to file
  cout << "writing this same vector to the file 'a_data':\n";
  a.save("a_data", arma::raw_ascii);

  // Testing MatrixRead() constructor
  double tol = 2.0e-15;
  arma::mat read_test1a = arma::randu(3,4);
  read_test1a.save("tmp.txt", arma::raw_ascii);
  arma::mat read_test1b;
  read_test1b.load("tmp.txt");
  arma::mat read_test1_error = read_test1a - read_test1b;
  if (norm(read_test1_error,"inf") < tol)
    cout << "save/load test 1 passed\n";
  else {
    cout << "save/load test 1 failed, ||error|| = " << norm(read_test1_error,"inf") << endl;
    cout << "  read_test1a = \n" << read_test1a << endl;
    cout << "  read_test1b = \n" << read_test1b << endl;
  }

  arma::mat read_test2a = arma::randu(12,1);
  read_test2a.save("tmp.txt", arma::raw_ascii);
  arma::mat read_test2b;
  read_test2b.load("tmp.txt");
  arma::mat read_test2_error = read_test2a - read_test2b;
  if (norm(read_test2_error,"inf") < tol)
    cout << "save/load test 2 passed\n";
  else {
    cout << "save/load test 2 failed, ||error|| = " << norm(read_test2_error,"inf") << endl;
    cout << "  read_test2a = \n" << read_test2a << endl;
    cout << "  read_test2b = \n" << read_test2b << endl;
  }

  arma::mat read_test3a = arma::randu(1,7);
  read_test3a.save("tmp.txt", arma::raw_ascii);
  arma::mat read_test3b;
  read_test3b.load("tmp.txt");
  arma::mat read_test3_error = read_test3a - read_test3b;
  if (norm(read_test3_error,"inf") < tol)
    cout << "save/load test 3 passed\n";
  else {
    cout << "save/load test 3 failed, ||error|| = " << norm(read_test3_error,"inf") << endl;
    cout << "  read_test3a = \n" << read_test3a << endl;
    cout << "  read_test3b = \n" << read_test3b << endl;
  }

  // Testing copy constructor
  arma::mat B = a;
  cout << "arma::mat B = a uses copy constructor, should give 10, 15, 20, 25, 30\n";
  cout << B << endl;
  // update one entry of a
  cout << "updating the 5th entry of a to be 31:\n";
  a(4) = 31.0;
  cout << "   a = " << a << endl;

  cout << "B should not have changed" << endl;
  cout << "   B = " << B << endl;
  a(4) = 30.0;  // reset to original

  // Testing submatrix copy constructor
  arma::mat B2 = a.submat(0,1,0,3);  // B2 = a(0:0,1:3)
  cout << "arma::mat B2 = a.submat(0,1,0,3) uses submatrix copy constructor" << endl;
  cout << B2 << endl;
  // update entries of B2
  B2(0) = 4.0;
  B2(1) = 3.0;
  B2(2) = 2.0;
  // copy B2 back into a using submatrix copy
  a(0,arma::span(1,3)) = B2;  // a(0,1:3) = B2
  cout << "span copy back into a, should have entries 10 4 3 2 30" << endl;
  cout << a << endl;
  a(1) = 15.0;  // reset to original
  a(2) = 20.0;
  a(3) = 25.0;

  // Test arithmetic operators
  cout << "Testing vector add, should give 1.1, 2.2, 3.3, 4.4, 5.5\n";
  b += c;  // b = b + c
  cout << b << endl;

  cout << "Testing scalar add, should give 2, 3, 4, 5, 6\n";
  c += 1.0;  // c = c + 1
  cout << c << endl;

  cout << "Testing vector subtract, should be 8, 12, 16, 20, 24\n";
  a -= c;  // a = a - c
  cout << a << endl;

  cout << "Testing scalar subtract, should be 0, 1, 2, 3, 4\n";
  c -= 2.0;  // c = c - 2
  cout << c << endl;

  cout << "Testing vector fill, should all be -1\n";
  b.fill(-1.0);  // b = -1*ones(size(b))
  cout << b << endl;

  cout << "Testing vector copy, should be 0, 1, 2, 3, 4\n";
  a = c;
  cout << a << endl;

  cout << "Testing scalar multiply, should be 0, 5, 10, 15, 20\n";
  c *= 5.0;  // c = c * 5
  cout << c << endl;

  cout << "Testing deep copy, should be 0, 1, 2, 3, 4\n";
  cout << a << endl;

  cout << "Testing vector multiply, should be 0, -1, -2, -3, -4\n";
  b %= a;   // b = b.*a
  cout << b << endl;

  cout << "Testing vector divide, should be 0, -2.5, -3.3333, -3.75, -4\n";
  arma::mat j(c);  // j = c
  b += -1.0;
  j /= b;   // j = j ./ b
  b += 1.0;
  cout << j << endl;

  cout << "Testing vector +=, should be 0, 4, 8, 12, 16\n";
  b += c;
  cout << b << endl;

  cout << "Testing scalar +=, should be 1, 6, 11, 16, 21\n";
  c += 1.0;
  cout << c << endl;

  cout << "Testing vector -=, should be 1, 2, 3, 4, 5\n";
  c -= b;
  cout << c << endl;

  cout << "Testing scalar -=, should be -2, -1, 0, 1, 2\n";
  a -= 2.0;
  cout << a << endl;

  cout << "Testing vector %=, should be 0, -4, 0, 12, 32\n";
  a %= b;
  cout << a << endl;

  cout << "Testing scalar *=, should be 2, 4, 6, 8, 10\n";
  c *= 2.0;
  cout << c << endl;

  cout << "Testing vector /=, should be 0, -1, 0, 1.5, 3.2\n";
  j = a;
  j /= c;
  cout << j << endl;

  cout << "Testing scalar /=, should be 1, 2, 3, 4, 5\n";
  j = c;
  j /= 2.0;
  cout << j << endl;

  cout << "Testing vector =, should be 2, 4, 6, 8, 10\n";
  b = c;
  cout << b << endl;

  cout << "Testing scalar fill, should be 3, 3, 3, 3, 3\n";
  a.fill(3.0);
  cout << a << endl;

  cout << "Testing vector norm, should be 14.8324\n";
  cout << "  " << arma::norm(b) << endl;

  cout << "Testing vector infinity norm, should be 10\n";
  cout << "  " << arma::norm(b,"inf") << endl;

  cout << "Testing vector one norm, should be 30\n";
  cout << "  " << arma::norm(b,1) << endl;

  cout << "Testing vector min, should be 2\n";
  cout << "  " << b.min() << endl;

  cout << "Testing vector max, should be 10\n";
  cout << "  " << b.max() << endl;

  B = arma::mat(2,5);
  B(0,arma::span(0,4)) = c;   // B(0,:) = c
  B(1,arma::span(0,4)) = a;   // B(1,:) = a
  B += 2.0;

  cout << "Testing matrix infinity norm, should be 40\n";
  cout << "  " << arma::norm(B,"inf") << endl;

  cout << "Testing matrix one norm, should be 17\n";
  cout << "  " << arma::norm(B,1) << endl;

  cout << "Testing matrix two norm, should be 21.7821\n";
  cout << "  " << arma::norm(B,2) << endl;

  cout << "Testing matrix min, should be 4\n";
  cout << "  " << B.min() << endl;

  cout << "Testing matrix max, should be 12\n";
  cout << "  " << B.max() << endl;

  cout << "Testing dot, should be 90\n";
  cout << "  " << dot(a, c) << endl;

  cout << "Testing logspace, should be 0.01 0.1 1 10 100\n";
  arma::mat e = arma::logspace<arma::rowvec>(-2.0, 2.0, 5);
  cout << e << endl;

  ofstream out;
  out.open("e.txt");
  if(out.is_open())
  {
    out << e;
    cout << "Wrote to file e.txt:\n" << e;
  }
  out.close();

  cout << "Testing randu\n";
  arma::mat f = arma::randu(3,3);
  cout << "f = " << f << endl;
  cout << "Testing write with a temporary result" << endl;
  cout << (f*f+f) << endl;
  cout << "f should be unchanged from above" << endl;
  cout << "f = " << f << endl;
  cout << "Testing f==f, should be 3x3 matrix of ones\n" << (f==f) << endl;
  b.fill(1.0);
  cout << "Testing e==b, should be 0 0 1 0 0\n  " << (e==b) << endl;

  // create and fill in a 10x5 matrix
  arma::mat Y(10,5);
  for (size_t i=0; i<10; i++) {
    Y(i,0) = 1.0*i;
    Y(i,1) = -5.0 + 1.0*i;
    Y(i,2) = 2.0 + 2.0*i;
    Y(i,3) = 20.0 - 1.0*i;
    Y(i,4) = -20.0 + 1.0*i;
  }

  // extract columns from matrix (both ways)
  arma::vec Y0 = Y.col(0);
  arma::mat Y1(Y.col(1));
  arma::vec Y2(Y(arma::span(0,9),2));
  arma::vec Y3 = Y.col(3);
  arma::vec Y4 = Y(arma::span(0,9),4);

  // check the LinearSum routine
  Y4 += Y3;
  cout << "Testing column extraction, should be all zeros:\n";
  cout << Y4 << endl;

  // check linear sum
  arma::mat d = arma::linspace<arma::rowvec>(0.0, 4.0, 5);
  cout << "Testing LinearSum, should be 0.02 1.2 4 23 204:\n";
  arma::mat g = 1.0*d + 2.0*e;
  cout << g << endl;

  // check the pow routine
  d = arma::pow(d,2.0);   // d = d.^2
  cout << "Testing pow, should be 0 1 4 9 16:\n";
  cout << d << endl;
  d = arma::pow(d,0.5);   // d = sqrt(d)
  cout << "Testing pow, should be 0 1 2 3 4:\n";
  cout << d << endl;

  // check the abs routine
  Y1 = arma::abs(Y1);
  cout << "Testing abs, should be the column 5 4 3 2 1 0 1 2 3 4:\n";
  cout << Y1 << endl;

  // check the inplace_trans routine
  cout << "Testing inplace_trans, should be the row 5 4 3 2 1 0 1 2 3 4:\n";
  inplace_trans(Y1);
  cout << Y1 << endl;

  // check the copy-based transpose routine
  cout << "Testing copy-based transpose, should be the column 5 4 3 2 1 0 1 2 3 4:\n";
  Y2 = Y1.t();
  cout << Y2 << endl;

  cout << "Testing GramSchmidt, should work\n";
  arma::mat X = arma::randu(20,3);
  int iret = GramSchmidt(X);
  cout << "  GramSchmidt returned " << iret << ", dot-products are:\n";
  cout << "     <X0,X0> = " << arma::dot(X.col(0),X.col(0)) << endl;
  cout << "     <X0,X1> = " << arma::dot(X.col(0),X.col(1)) << endl;
  cout << "     <X0,X2> = " << arma::dot(X.col(0),X.col(2)) << endl;
  cout << "     <X1,X1> = " << arma::dot(X.col(1),X.col(1)) << endl;
  cout << "     <X1,X2> = " << arma::dot(X.col(1),X.col(2)) << endl;
  cout << "     <X2,X2> = " << arma::dot(X.col(2),X.col(2)) << endl << endl;

  cout << "Testing GramSchmidt, should fail\n";
  arma::mat V = arma::randu(20,3);
  V.col(2) = 2.0*V.col(1);
  iret = GramSchmidt(V);
  cout << "  GramSchmidt returned " << iret << ", dot-products are:\n";
  cout << "     <V0,V0> = " << arma::dot(V.col(0),V.col(0)) << endl;
  cout << "     <V0,V1> = " << arma::dot(V.col(0),V.col(1)) << endl;
  cout << "     <V0,V2> = " << arma::dot(V.col(0),V.col(2)) << endl;
  cout << "     <V1,V1> = " << arma::dot(V.col(1),V.col(1)) << endl;
  cout << "     <V1,V2> = " << arma::dot(V.col(1),V.col(2)) << endl;
  cout << "     <V2,V2> = " << arma::dot(V.col(2),V.col(2)) << endl << endl;

  cout << "Testing matrix product, should be: 9 -1 9 -8 11 6\n";
  arma::mat A_ = arma::eye(6,6);
  A_(0,3) = 2.0;
  A_(1,2) = -1.0;
  A_(2,5) = 1.0;
  A_(3,5) = -2.0;
  A_(4,5) = 1.0;
  arma::mat xtrue_ = arma::linspace(1.0, 6.0, 6);
  arma::mat b_ = A_*xtrue_;
  cout << b_ << endl;

  cout << "Testing backwards substitution solve with provided solution array:\n";
  arma::vec x_(6);
  if (arma::solve(x_, A_, b_)) {
    cout << "  solve succeeded\n";
  } else {
    cout << "  solve failed\n";
  }
  cout << "  ||x - xtrue|| = " << arma::norm(x_ - xtrue_, "inf") << "\n\n";

  cout << "Testing forwards substitution with provided solution array:\n";
  A_.eye();
  A_(3,0) = 2.0;
  A_(2,1) = -1.0;
  A_(5,2) = 1.0;
  A_(5,3) = -2.0;
  A_(5,4) = 1.0;
  b_ = A_*xtrue_;
  x_ = 0.0;
  if (arma::solve(x_, A_, b_)) {
    cout << "  solve succeeded\n";
  } else {
    cout << "  solve failed\n";
  }
  cout << "  ||x - xtrue|| = " << arma::norm(x_ - xtrue_, "inf") << "\n\n";

  cout << "Testing general solver:\n";
  arma::mat C_ = 100.0*arma::eye(9,9) + arma::randu(9,9);
  arma::vec z_ = arma::logspace(-4.0, 4.0, 9);
  arma::mat f_ = C_*z_;
  arma::mat g_ = arma::solve(C_, f_);
  cout << "  ||x - xtrue|| = " << arma::norm(g_ - z_, "inf") << "\n\n";

  cout << "Testing copy-into-col, should be: \n";
  cout << "    0.01   0.02   0.04   0.08\n";
  cout << "    0.1    0.2    0.4    0.8\n";
  cout << "    1      2      4      8\n";
  cout << "   10     20     40     80\n";
  cout << " Actually is:\n";
  arma::mat z2_ = arma::logspace(-2.0, 1.0, 4);
  arma::mat B_(4,4);
  B_.col(0) = z2_;
  z2_ *= 2.0;
  B_.col(1) = z2_;
  z2_ *= 2.0;
  B_.col(2) = z2_;
  z2_ *= 2.0;
  B_.col(3) = z2_;
  cout << B_ << endl;

  cout << "Testing general solver with matrix-valued rhs:\n";
  arma::mat E_ = 100.0*arma::eye(4,4) + arma::randu(4,4);
  arma::mat F_ = E_*B_;
  arma::mat X_ = arma::solve(E_, F_);
  cout << "  ||X - Xtrue|| = " << arma::norm(X_ - B_,"inf") << "\n\n";

  cout << "Testing matrix inverse:\n";
  arma::mat D_ = 10.0*arma::eye(8,8) + arma::randu(8,8);
  arma::mat DDinv_(D_);
  arma::mat Dinv_ = D_.i();
  DDinv_ = D_*Dinv_;
  cout << "  ||I - D*Dinv|| = " << arma::norm(arma::eye(8,8) - DDinv_,"inf") << endl;
  DDinv_ = Dinv_*D_;
  cout << "  ||I - Dinv*D|| = " << arma::norm(arma::eye(8,8) - DDinv_,"inf") << endl;

  return 0;
} // end main


/*
 * @file
 * @author University of Warwick
 * @version 1.0
 *
 * @section LICENSE
 *
 * @section DESCRIPTION
 *
 * Tests for the array driver functions from the utility operations
 *
 */

#define BOOST_TEST_MODULE ArrayDrivers
#include <boost/test/unit_test.hpp>
#include <boost/test/output_test_stream.hpp>

#include <stdexcept>
#include "ArrayDrivers.h"
#include "ArrayKernels.h"

using namespace cupcfd::utility::drivers;

// ======================= Array Copy Tests ==================================
// Test 1: Successful copy
BOOST_AUTO_TEST_CASE(copy_test1)
{
	int source[6] = {1, 4, 3, 2, 6, 1};
	int result[6] = {0 ,0 ,0 ,0 ,0 ,0};
	int resultCmp[6] = {1, 4, 3, 2, 6, 1};

	cupcfd::error::eCodes err = copy(source, 6, result, 6);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 5, resultCmp, resultCmp + 5);
}

// Test 2: Undersized destination array
BOOST_AUTO_TEST_CASE(copy_test2)
{
	int source[6] = {1, 4, 3, 2, 6, 1};
	int result[4] = {0 ,0 ,0 ,0};

	cupcfd::error::eCodes err = copy(source, 6, result, 4);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_ARRAY_SIZE_UNDERSIZED);
}

// ======================= Array Zero Tests ==================================
// Test 1: Zero the array
BOOST_AUTO_TEST_CASE(zero_test1)
{
	int source[6] = {1, 4, 3, 2, 6, 1};
	int resultCmp[6] = {0, 0, 0, 0, 0, 0};

	cupcfd::error::eCodes err = zero(source, 6);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(source, source + 5, resultCmp, resultCmp + 5);
}

// ==================== UniqueCount ==========================
// Test 1: Count the number of unique elements in an array
BOOST_AUTO_TEST_CASE(uniqueCount_test1)
{
	int source[11] = {1, 1, 1, 1, 2, 3, 3, 4, 4, 7, 325};
	int count;
	cupcfd::error::eCodes status;
	status = uniqueCount(source, 11, & count);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(count, 3);
}

// Test 2: Count zero unique elements in an array with only duplicates
BOOST_AUTO_TEST_CASE(uniqueCount_test2)
{
	int source[14] = {1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 7, 7, 325, 325};
	int count;
	cupcfd::error::eCodes status;
	status = uniqueCount(source, 14, & count);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(count, 0);
}

// Test 3: Count the number of unique elements in an array that is unsorted
BOOST_AUTO_TEST_CASE(uniqueCount_test3)
{
	int source[11] = {7, 5, 3, 2, 15, 2, 7, 21, 100, 200, 150};
	int sourceCmp[11] = {7, 5, 3, 2, 15, 2, 7, 21, 100, 200, 150};
	int count;
	cupcfd::error::eCodes status;
	status = uniqueCount(source, 11, & count);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	// Source should remain unmodified (non-destructive)
	BOOST_CHECK_EQUAL_COLLECTIONS(source, source + 11, sourceCmp, sourceCmp + 11);
	BOOST_CHECK_EQUAL(count, 7);
}

// Test 4: Count the number of unique elements when all elements are unique
BOOST_AUTO_TEST_CASE(uniqueCount_test4)
{
	int source[6] = {1, 2, 3, 4, 7, 325};
	int count;
	cupcfd::error::eCodes status;
	status = uniqueCount(source, 6, &count);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(count, 6);
}

// Test 5: Count the number of unique elements when only the first is unique
BOOST_AUTO_TEST_CASE(uniqueCount_test5)
{
	int source[11] = {1, 2, 2, 3, 3, 4, 4, 7, 7, 325, 325};
	int count;
	cupcfd::error::eCodes status;
	status = uniqueCount(source, 11, &count);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(count, 1);
}

// Test 6: Count the number of unique elements when only the last is unique
BOOST_AUTO_TEST_CASE(uniqueCount_test6)
{
	int source[11] = {1, 1, 2, 2, 3, 3, 4, 4, 7, 7, 325};
	int count;
	cupcfd::error::eCodes status;
	status = uniqueCount(source, 11, &count);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(count, 1);
}

// Test 7: Count the number of unique elements when only the last two are unique
BOOST_AUTO_TEST_CASE(uniqueCount_test7)
{
	int source[10] = {1, 1, 2, 2, 3, 3, 4, 4, 7, 325};
	int count;
	cupcfd::error::eCodes status;
	status = uniqueCount(source, 10, &count);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(count, 2);
}

// ================= Unique Array =======================
// Test 1: Find the unique elements in an arbitrary array
BOOST_AUTO_TEST_CASE(uniqueArray_test1)
{
	// The kernel expects sorted arrays only
	// Isn't this tested already in the kernels tests?
	int source[11] = {1, 1, 1, 1, 2, 3, 3, 4, 4, 7, 325};
	int count = cupcfd::utility::kernels::uniqueCount(source, 11);
	BOOST_CHECK_EQUAL(count, 3);

	int result[3];
	int result_cmp[3] = {2, 7, 325};

	cupcfd::error::eCodes err = uniqueArray(source, 11, result, 3);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 3, result_cmp, result_cmp + 3);
}

// Test 2: Find the unique elements in an unsorted array
BOOST_AUTO_TEST_CASE(uniqueArray_test2)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 2, 3, 3, 4, 4, 7, 325};
	int count = cupcfd::utility::kernels::uniqueCount(source, 11);
	BOOST_CHECK_EQUAL(count, 3);

	int result[3];
	int result_cmp[3] = {2, 7, 325};

	cupcfd::error::eCodes err = uniqueArray(source, 11, result, 3);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 3, result_cmp, result_cmp + 3);
}

// Test 3: Check that the method runs without error when there are no unique elements
BOOST_AUTO_TEST_CASE(uniqueArray_test3)
{
	// The kernel expects sorted arrays only
	int source[14] = {1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 7, 7, 325, 325};
	int count = cupcfd::utility::kernels::uniqueCount(source, 14);
	BOOST_CHECK_EQUAL(count, 0);

	int result[0];
	// Theoretically, it should never copy. If it does, it will go out of bounds in memory.
	cupcfd::error::eCodes err = uniqueArray(source, 14, result, 0);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
}

// Test 4: Test when all elements are unique
BOOST_AUTO_TEST_CASE(uniqueArray_test4)
{
	// The kernel expects sorted arrays only
	int source[6] = {1, 2, 3, 4, 7, 325};
	int count = cupcfd::utility::kernels::uniqueCount(source, 6);
	BOOST_CHECK_EQUAL(count, 6);

	int result[6];
	int result_cmp[6] = {1, 2, 3, 4, 7, 325};
	cupcfd::error::eCodes err = uniqueArray(source, 6, result, 6);

	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 6, result_cmp, result_cmp + 6);
	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
}

// Test 5: Test when only the first element is unique
BOOST_AUTO_TEST_CASE(uniqueArray_test5)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 2, 3, 3, 4, 4, 7, 7, 325, 325};
	int count = cupcfd::utility::kernels::uniqueCount(source, 11);
	BOOST_CHECK_EQUAL(count, 1);

	int result[1];
	int result_cmp[1] = {1};
	cupcfd::error::eCodes err = uniqueArray(source, 11, result, 1);

	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 1, result_cmp, result_cmp + 1);
	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
}

// Test 6: Test when the last element is unique
BOOST_AUTO_TEST_CASE(uniqueArray_test6)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 2, 2, 3, 3, 4, 4, 7, 7, 325};
	int count = cupcfd::utility::kernels::uniqueCount(source, 11);
	BOOST_CHECK_EQUAL(count, 1);

	int result[1];
	int result_cmp[1] = {325};
	cupcfd::error::eCodes err = uniqueArray(source, 11, result, 1);

	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 1, result_cmp, result_cmp + 1);
	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
}

// Test 7: Test when the last two elements are unique
BOOST_AUTO_TEST_CASE(uniqueArray_test7)
{
	// The kernel expects sorted arrays only
	int source[10] = {1, 1, 2, 2, 3, 3, 4, 4, 7, 325};
	int count = cupcfd::utility::kernels::uniqueCount(source, 10);
	BOOST_CHECK_EQUAL(count, 2);

	int result[2];
	int result_cmp[2] = {7, 325};
	cupcfd::error::eCodes err = uniqueArray(source, 10, result, 2);

	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 2, result_cmp, result_cmp + 2);
	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
}

// ================= Unique Array + Array Creation =======================

// Test 8: Test creating an array automatically when finding unique elements
BOOST_AUTO_TEST_CASE(uniqueArray_test8)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 2, 3, 3, 4, 4, 7, 325};
	int count;
	int * result;

	int result_cmp[3] = {2, 7, 325};

	cupcfd::error::eCodes err = uniqueArray(source, 11, &result, &count);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(count, 3);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 3, result_cmp, result_cmp + 3);

	// Cleanup
	free(result);
}

// ======================= Array Add Tests ===================================
// Test 1: Test adding with destructive storage in source1
BOOST_AUTO_TEST_CASE(add_test1)
{
	int source1[5] = {1,2,3,4,5};
	int source2[5] = {1,2,18,4,7};
	int resultCmp[5] = {2, 4, 21, 8, 12};
	int source2Cmp[5] = {1,2,18,4,7};

	cupcfd::error::eCodes err = add(source1, 5, source2, 5);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(source1, source1 + 5, resultCmp, resultCmp + 5);
	BOOST_CHECK_EQUAL_COLLECTIONS(source2, source2 + 5, source2Cmp, source2Cmp + 5);
}

// ======================= Array Add Non-Destructive Tests ===================================
// Test 2: Test adding with non-destructive storage
BOOST_AUTO_TEST_CASE(add_test2)
{
	int dest[5];

	int source1[5] = {1,2,3,4,5};
	int source2[5] = {1,2,18,4,7};

	int source1Cmp[5] = {1,2,3,4,5};
	int source2Cmp[5] = {1,2,18,4,7};
	int resultCmp[5] = {2, 4, 21, 8, 12};

	cupcfd::error::eCodes err = add(source1, 5, source2, 5, dest, 5);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 5, resultCmp, resultCmp + 5);
	BOOST_CHECK_EQUAL_COLLECTIONS(source1, source1 + 5, source1Cmp, source1Cmp + 5);
	BOOST_CHECK_EQUAL_COLLECTIONS(source2, source2 + 5, source2Cmp, source2Cmp + 5);
}

// ================= Distinct Count ===========================
// Test 1: Test counting distinct elements in arbitrary array
BOOST_AUTO_TEST_CASE(distinctCount_test1)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 2, 3, 3, 4, 4, 7, 325};
	int count;
	cupcfd::error::eCodes err = distinctCount(source, 11, &count);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(count, 6);
}

// Test 2: Test counting distinct elements with duplicates
BOOST_AUTO_TEST_CASE(distinctCount_test2)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 4};
	int count;
	cupcfd::error::eCodes err = distinctCount(source, 11, &count);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(count, 4);
}

// Test 3: Test counting with all elements being distinct
BOOST_AUTO_TEST_CASE(distinctCount_test3)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15};
	int count;
	cupcfd::error::eCodes err = distinctCount(source, 11, &count);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(count, 11);
}

// Test 4: Test counting where only first element is non-duplicate
BOOST_AUTO_TEST_CASE(distinctCount_test4)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
	int count;
	cupcfd::error::eCodes err = distinctCount(source, 11, &count);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(count, 2);
}

// Test 5: Test counting where only last element is non-duplicate
BOOST_AUTO_TEST_CASE(distinctCount_test5)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2};
	int count;
	cupcfd::error::eCodes err = distinctCount(source, 11, &count);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(count, 2);
}

// Test 6: Test counting where only first two elements are non-duplicate
BOOST_AUTO_TEST_CASE(distinctCount_test6)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3};
	int count;
	cupcfd::error::eCodes err = distinctCount(source, 11, &count);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(count, 3);
}

// Test 7: Test counting where only last two elements are non-duplicate
BOOST_AUTO_TEST_CASE(distinctCount_test7)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3};
	int count;
	cupcfd::error::eCodes err = distinctCount(source, 11, &count);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(count, 3);
}

// ================= Distinct Array  ===========================
// Test 1: Test finding distinct elements in arbitrary array
BOOST_AUTO_TEST_CASE(distinctArray_test1)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 2, 3, 3, 4, 4, 7, 325};
	int * dest = (int *) malloc(sizeof(int) * 6);
	int resultCmp[6] = {1,2,3,4,7,325};

	cupcfd::error::eCodes err = distinctArray(source, 11, dest, 6);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 6, resultCmp, resultCmp + 6);
}

// Test 2: Test finding distinct elements in arbitrary unsorted array
BOOST_AUTO_TEST_CASE(distinctArray_test2)
{
	// The kernel expects sorted arrays only
	int source[11] = {325, 1, 4, 1, 2, 7, 3, 1, 4, 3, 1};
	int * dest = (int *) malloc(sizeof(int) * 6);
	int resultCmp[6] = {1,2,3,4,7,325};

	cupcfd::error::eCodes err = distinctArray(source, 11, dest, 6);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 6, resultCmp, resultCmp + 6);
}

// Test 3: Test finding distinct elements where all elements have duplicates
BOOST_AUTO_TEST_CASE(distinctArray_test3)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 4};
	int * dest = (int *) malloc(sizeof(int) * 4);
	int resultCmp[4] = {1,2,3,4};

	cupcfd::error::eCodes err = distinctArray(source, 11, dest, 4);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 4, resultCmp, resultCmp + 4);
}

// Test 4: Test finding distinct elements when no element has a duplicate
BOOST_AUTO_TEST_CASE(distinctArray_test4)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15};
	int * dest = (int *) malloc(sizeof(int) * 11);
	int resultCmp[11] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15};

	cupcfd::error::eCodes err = distinctArray(source, 11, dest, 11);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 11, resultCmp, resultCmp + 11);
}

// Test 5: Test finding distinct elements when only first element has no duplicate
BOOST_AUTO_TEST_CASE(distinctArray_test5)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
	int * dest = (int *) malloc(sizeof(int) * 2);
	int resultCmp[2] = {1, 2};

	cupcfd::error::eCodes err = distinctArray(source, 11, dest, 2);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 2, resultCmp, resultCmp + 2);
}

// Test 6: Test finding distinct elements when only last element has no duplicate
BOOST_AUTO_TEST_CASE(distinctArray_test6)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2};
	int * dest = (int *) malloc(sizeof(int) * 2);
	int resultCmp[2] = {1, 2};

	cupcfd::error::eCodes err = distinctArray(source, 11, dest, 2);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 2, resultCmp, resultCmp + 2);
}

// Test 7: Test finding distinct elements when only first two elements have no duplicate
BOOST_AUTO_TEST_CASE(distinctArray_test7)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3};
	int * dest = (int *) malloc(sizeof(int) * 3);
	int resultCmp[6] = {1, 2, 3};

	cupcfd::error::eCodes err = distinctArray(source, 11, dest, 3);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 3, resultCmp, resultCmp + 3);
}

// Test 8: Test finding distinct elements when only last two elements have no duplicate
BOOST_AUTO_TEST_CASE(distinctArray_test8)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 7};
	int * dest = (int *) malloc(sizeof(int) * 3);
	int resultCmp[3] = {1, 2, 7};

	cupcfd::error::eCodes err = distinctArray(source, 11, dest, 3);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 3, resultCmp, resultCmp + 3);
}

// ================= Distinct Array + Array Creation ===========================

// ================= Distinct Array + Count ===========================
// Test 1: Test finding distinct elements with count also in arbitrary array
BOOST_AUTO_TEST_CASE(distinctArrayWithCount_test1)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 2, 3, 3, 4, 4, 7, 325};
	int * dest = (int *) malloc(sizeof(int) * 6);
	int * count = (int *) malloc(sizeof(int) * 6);

	int resultCmp[6] = {1,2,3,4,7,325};
	int countCmp[6] = {4,1,2,2,1,1};

	cupcfd::error::eCodes err = distinctArray(source, 11, dest, 6, count, 6);
	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 6, resultCmp, resultCmp + 6);
	BOOST_CHECK_EQUAL_COLLECTIONS(count, count + 6, countCmp, countCmp + 6);
}

// Test 2: Test finding distinct elements with count also in arbitrary array where all elements have duplicates
BOOST_AUTO_TEST_CASE(distinctArrayWithCount_test2)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 4};
	int * dest = (int *) malloc(sizeof(int) * 4);
	int * count = (int *) malloc(sizeof(int) * 4);

	int resultCmp[4] = {1,2,3,4};
	int countCmp[4] = {2, 3, 2, 4};

	cupcfd::error::eCodes err = distinctArray(source, 11, dest, 4, count, 4);
	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 4, resultCmp, resultCmp + 4);
	BOOST_CHECK_EQUAL_COLLECTIONS(count, count + 4, countCmp, countCmp + 4);
}

// Test 3: Test finding distinct elements with count also in arbitrary array where no elements has a duplicate
BOOST_AUTO_TEST_CASE(distinctArrayWithCount_test3)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15};
	int * dest = (int *) malloc(sizeof(int) * 11);
	int * count = (int *) malloc(sizeof(int) * 11);

	int resultCmp[11] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15};
	int countCmp[11] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};

	cupcfd::error::eCodes err = distinctArray(source, 11, dest, 11, count, 11);
	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 11, resultCmp, resultCmp + 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(count, count + 11, countCmp, countCmp + 11);
}

// Test 4: Test finding distinct elements with count where first element only has no duplicate
BOOST_AUTO_TEST_CASE(distinctArrayWithCount_test4)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
	int * dest = (int *) malloc(sizeof(int) * 2);
	int * count = (int *) malloc(sizeof(int) * 2);

	int resultCmp[2] = {1, 2};
	int countCmp[2] = {1, 10};

	cupcfd::error::eCodes err = distinctArray(source, 11, dest, 2, count, 2);
	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 2, resultCmp, resultCmp + 2);
	BOOST_CHECK_EQUAL_COLLECTIONS(count, count + 2, countCmp, countCmp + 2);
}

// Test 5: Test finding distinct elements with count where last element only has no duplicate
BOOST_AUTO_TEST_CASE(distinctArrayWithCount_test5)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2};
	int * dest = (int *) malloc(sizeof(int) * 2);
	int * count = (int *) malloc(sizeof(int) * 2);

	int resultCmp[2] = {1, 2};
	int countCmp[2] = {10, 1};

	cupcfd::error::eCodes err = distinctArray(source, 11, dest, 2, count, 2);
	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 2, resultCmp, resultCmp + 2);
	BOOST_CHECK_EQUAL_COLLECTIONS(count, count + 2, countCmp, countCmp + 2);
}

// Test 6: Test finding distinct elements with count where first two elements only have no duplicate
BOOST_AUTO_TEST_CASE(distinctArrayWithCount_test6)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3};
	int * dest = (int *) malloc(sizeof(int) * 3);
	int * count = (int *) malloc(sizeof(int) * 3);

	int resultCmp[6] = {1, 2, 3};
	int countCmp[3] = {1, 1, 9};

	cupcfd::error::eCodes err = distinctArray(source, 11, dest, 3, count, 3);
	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 3, resultCmp, resultCmp + 3);
	BOOST_CHECK_EQUAL_COLLECTIONS(count, count + 3, countCmp, countCmp + 3);
}

// Test 7: Test finding distinct elements with count where last two elements only have no duplicate
BOOST_AUTO_TEST_CASE(distinctArrayWithCount_test7)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 7};
	int * dest = (int *) malloc(sizeof(int) * 3);
	int * count = (int *) malloc(sizeof(int) * 3);

	int resultCmp[3] = {1, 2, 7};
	int countCmp[3] = {9, 1, 1};

	cupcfd::error::eCodes err = distinctArray(source, 11, dest, 3, count, 3);
	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 3, resultCmp, resultCmp + 3);
	BOOST_CHECK_EQUAL_COLLECTIONS(count, count + 3, countCmp, countCmp + 3);
}

// ================= Distinct Array + Count + Array Creation ===========================

//====================== Minus Count ===========================
// Test 1: Test MinusCount when array1 is larger than array2
BOOST_AUTO_TEST_CASE(minusCount_test1)
{
	int source1[7] = {1, 2, 2, 4, 8, 10, 12};
	int source2[4] = {2, 8, 21, 22};
	int count;

	minusCount(source1, 7, source2, 4, &count);

	BOOST_CHECK_EQUAL(count, 4);
}

// Test 2: Test MinusCount when array2 is larger than array1
BOOST_AUTO_TEST_CASE(minusCount_test2)
{
	int source1[7] = {1, 2, 2, 4, 8, 10, 12};
	int source2[8] = {2, 8, 21, 22, 100, 102, 200, 400};
	int count;

	minusCount(source1, 7, source2, 8, &count);

	BOOST_CHECK_EQUAL(count, 4);
}

// Test 3: Test MinusCount when array1 is all the same element
BOOST_AUTO_TEST_CASE(minusCount_test3)
{
	int source1[7] = {1, 1, 1, 1, 1, 1, 1};
	int source2[4] = {2, 8, 21, 22};
	int count;

	minusCount(source1, 7, source2, 4, &count);

	BOOST_CHECK_EQUAL(count, 7);
}

// Test 4: Test MinusCount when arrays have the same contents
BOOST_AUTO_TEST_CASE(minusCount_test4)
{
	int source1[4] = {2, 8, 21, 22};
	int source2[4] = {2, 8, 21, 22};
	int count;

	minusCount(source1, 4, source2, 4, &count);

	BOOST_CHECK_EQUAL(count, 0);
}

//====================== Minus Array ===========================
// Test 1: Test MinusArray when array1 is larger than array2
BOOST_AUTO_TEST_CASE(minusArray_test1)
{
	int source1[7] = {1, 2, 2, 4, 8, 10, 12};
	int source2[4] = {2, 8, 21, 22};
	int * result;
	int nResult;
	int resultCmp[4] = {1, 4, 10, 12};

	cupcfd::error::eCodes err = minusArray(source1, 7, source2, 4, &result, &nResult);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(nResult, 4);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 4, resultCmp, resultCmp + 4);
}

// Test 2: Test MinusArray when array2 is larger than array1
BOOST_AUTO_TEST_CASE(minusArray_test2)
{
	int source1[7] = {1, 2, 2, 4, 8, 10, 12};
	int source2[8] = {2, 8, 21, 22, 100, 102, 200, 400};
	int * result;
	int nResult;
	int resultCmp[4] = {1, 4, 10, 12};

	cupcfd::error::eCodes err = minusArray(source1, 7, source2, 8, &result, &nResult);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(nResult, 4);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 4, resultCmp, resultCmp + 4);
}

// Test 3: Test MinusArray when array1 is unsorted
BOOST_AUTO_TEST_CASE(minusArray_test3)
{
	int source1[7] = {12, 2, 4, 1, 8, 2, 10};
	int source2[4] = {2, 8, 21, 22};
	int * result;
	int nResult;
	int resultCmp[4] = {1, 4, 10, 12};

	cupcfd::error::eCodes err = minusArray(source1, 7, source2, 4, &result, &nResult);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(nResult, 4);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 4, resultCmp, resultCmp + 4);
}

// Test 4: Test MinusArray when array1 is all the same element
BOOST_AUTO_TEST_CASE(minusArray_test4)
{
	int source1[7] = {1, 1, 1, 1, 1, 1, 1};
	int source2[4] = {2, 8, 21, 22};
	int * result;
	int nResult;
	int resultCmp[7] = {1, 1, 1, 1, 1, 1, 1};

	cupcfd::error::eCodes err = minusArray(source1, 7, source2, 4, &result, &nResult);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(nResult, 7);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 7, resultCmp, resultCmp + 7);
}

// Test 5: Test MinusArray when the arrays have the same contents
BOOST_AUTO_TEST_CASE(minusArray_test5)
{
	int source1[4] = {2, 8, 21, 22};
	int source2[4] = {2, 8, 21, 22};
	int * result;
	int nResult;
	int resultCmp[0];

	cupcfd::error::eCodes err = minusArray(source1, 4, source2, 4, &result, &nResult);

	BOOST_CHECK_EQUAL(err, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(nResult, 0);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 0, resultCmp, resultCmp + 0);
}

//====================== Intersect Count ===========================
// Test 1: Test Intersect Count with array
BOOST_AUTO_TEST_CASE(intersectCount_test1)
{
	int source1[6] = {1, 2, 4, 8, 10, 12};
	int source2[4] = {2, 8, 21, 22};
	int count;
	cupcfd::error::eCodes status;
	status = intersectCount(source1, 6, source2, 4, &count);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	// Duplicates should not count, so only 2 and 8 are in both.
	BOOST_CHECK_EQUAL(count, 2);
}

// Test 2: Test Intersect Count with array
BOOST_AUTO_TEST_CASE(intersectCount_test2)
{
	int source1[6] = {1, 2, 4, 8, 10, 22};
	int source2[4] = {2, 8, 21, 22};
	int count;
	cupcfd::error::eCodes status;
	status = intersectCount(source1, 6, source2, 4, &count);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	// Duplicates should not count, so only 2 and 8 are in both.
	BOOST_CHECK_EQUAL(count, 3);
}

// Test 3: Test Intersect Count with array2 larger
BOOST_AUTO_TEST_CASE(intersectCount_test3)
{
	int source1[6] = {1, 2, 4, 8, 10, 200};
	int source2[8] = {2, 8, 21, 22, 100, 102, 200, 400};
	int count;
	cupcfd::error::eCodes status;
	status = intersectCount(source1, 7, source2, 8, &count);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	// Duplicates should not count, so only 2, 8 and 200 are in both.
	BOOST_CHECK_EQUAL(count, 3);
}

// Test 4: Test Intersect Count with no intersection elements
BOOST_AUTO_TEST_CASE(intersectCount_test4)
{
	int source1[1] = {1};
	int source2[4] = {2, 8, 21, 22};
	int count;
	cupcfd::error::eCodes status;
	status = intersectCount(source1, 1, source2, 4, &count);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(count, 0);
}

// Test 5: Test Intersect Count with same array contents
BOOST_AUTO_TEST_CASE(intersectCount_test5)
{
	int source1[4] = {2, 8, 21, 22};
	int source2[4] = {2, 8, 21, 22};
	int count;
	cupcfd::error::eCodes status;
	status = intersectCount(source1, 4, source2, 4, &count);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	// Arrays are same, so all elements are in intersect
	BOOST_CHECK_EQUAL(count, 4);
}

//====================== Intersect Array ===========================
// Test 1: Test Intersect Count with array
BOOST_AUTO_TEST_CASE(intersectArray_test1)
{
	int source1[6] = {1, 2, 4, 8, 10, 12};
	int source2[4] = {2, 8, 21, 22};

	int * result;
	int nResult;
	int cmp[2] = {2, 8};

	cupcfd::error::eCodes status;
	status = intersectArray(source1, 6, source2, 4, &result, &nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(nResult, 2);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 2, cmp, cmp + 2);
}

// Test 2: Test Intersect Count with array
BOOST_AUTO_TEST_CASE(intersectArray_test2)
{
	int source1[6] = {1, 2, 4, 8, 10, 22};
	int source2[4] = {2, 8, 21, 22};

	int * result;
	int nResult;
	int cmp[3] = {2, 8, 22};

	cupcfd::error::eCodes status;
	status = intersectArray(source1, 6, source2, 4, &result, &nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(nResult, 3);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 3, cmp, cmp + 3);
}

// Test 3: Test Intersect Count with array2 larger
BOOST_AUTO_TEST_CASE(intersectArray_test3)
{
	int source1[6] = {1, 2, 4, 8, 10, 200};
	int source2[8] = {2, 8, 21, 22, 100, 102, 200, 400};

	int * result;
	int nResult;
	int cmp[3] = {2, 8, 200};

	cupcfd::error::eCodes status;
	status = intersectArray(source1, 7, source2, 8, &result, &nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(nResult, 3);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 3, cmp, cmp + 3);
}

// Test 4: Test Intersect Count with no intersection elements
BOOST_AUTO_TEST_CASE(intersectArray_test4)
{
	int source1[1] = {1};
	int source2[4] = {2, 8, 21, 22};

	int * result;
	int nResult;
	int cmp[0];

	cupcfd::error::eCodes status;
	status = intersectArray(source1, 1, source2, 4, &result, &nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(nResult, 0);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 0, cmp, cmp + 0);
}

// Test 5: Test Intersect Count with same array contents
BOOST_AUTO_TEST_CASE(intersectArray_test5)
{
	int source1[4] = {2, 8, 21, 22};
	int source2[4] = {2, 8, 21, 22};

	int * result;
	int nResult;
	int cmp[4] = {2, 8, 21, 22};

	cupcfd::error::eCodes status;
	status = intersectArray(source1, 4, source2, 4, &result, &nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL(nResult, 4);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 4, cmp, cmp + 4);
}

// === randomUniform ===
// ToDo: Is it possible to generate a suitable test for this?


#include <stdexcept>
#include <boost/test/unit_test.hpp>
#include <initializer_list>
#include "composite-shape.hpp"
#include "circle.hpp"
#include "rectangle.hpp"
#include "base-types.hpp"

const double epsilon = 0.0000001;

BOOST_AUTO_TEST_SUITE(CompositeShapeConstructor)

BOOST_AUTO_TEST_CASE(ValidParameters_ValidShapeConstructed)
{
  const double radius = 34;
  const gadzhiev::point_t point{ 1, 5 };
  gadzhiev::CompositeShape cmpShape(std::make_shared<gadzhiev::Circle>(radius, point));
  BOOST_CHECK_EQUAL(cmpShape.getSize(), 1);
  BOOST_CHECK_CLOSE(cmpShape.getCenter().x, point.x, epsilon);
  BOOST_CHECK_CLOSE(cmpShape.getCenter().y, point.y, epsilon);
}

BOOST_AUTO_TEST_CASE(ValidInitialisationListParameters_ValidShapeConstructed)
{
  const double radius = 34;
  const gadzhiev::point_t point{ 1, 5 };
  gadzhiev::CompositeShape cmpShape{ std::make_shared<gadzhiev::Circle>(12, gadzhiev::point_t{ -67, 5 }),
      std::make_shared<gadzhiev::Circle>(32, gadzhiev::point_t { 24, 5 }),
      std::make_shared<gadzhiev::Circle>(radius, point),
      std::make_shared<gadzhiev::Circle>(1, gadzhiev::point_t { 33, 54 }) };

  BOOST_CHECK_EQUAL(cmpShape.getSize(), 4);
  BOOST_CHECK_CLOSE(cmpShape[2]->getCenter().x, point.x, epsilon);
  BOOST_CHECK_CLOSE(cmpShape[2]->getCenter().y, point.y, epsilon);
}

BOOST_AUTO_TEST_CASE(InValidInitialisationListParameters_ThrownInvalidArgument)
{
  gadzhiev::Shape::ShapePtr nullptrShape{ nullptr };
  const double radius = 34;
  const gadzhiev::point_t point{ 1, 5 };

  BOOST_CHECK_THROW(gadzhiev::CompositeShape cmpShape({ std::make_shared<gadzhiev::Circle>(radius, point),
      std::make_shared<gadzhiev::Circle>(radius, point),
      std::make_shared<gadzhiev::Circle>(radius, point),
      nullptrShape }),
      std::invalid_argument);
}

BOOST_AUTO_TEST_CASE(InvalidParameters_ThrownInvalidArument)
{
  BOOST_CHECK_THROW(gadzhiev::CompositeShape(nullptr), std::invalid_argument);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(CompositeShapeAdd)

BOOST_AUTO_TEST_CASE(AddFigure_FiguresInCmpShape)
{
  gadzhiev::Shape::ShapePtr rectangle = std::make_shared<gadzhiev::Rectangle>(4, 9, gadzhiev::point_t { 3, 7 });

  gadzhiev::CompositeShape cmpShape(std::make_shared<gadzhiev::Circle>(32, gadzhiev::point_t { 1, 76 }));

  cmpShape.add(std::make_shared<gadzhiev::Circle>(1, gadzhiev::point_t { -2, 7 }));
  cmpShape.add(rectangle);

  BOOST_CHECK_EQUAL(cmpShape.getSize(), 3);
  BOOST_CHECK_EQUAL(cmpShape[2], rectangle);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(CompositeShapeRemove)

BOOST_AUTO_TEST_CASE(ValidRemoveFigure_ThereIsNoFigureInCmpShape)
{
  gadzhiev::Shape::ShapePtr rectangle = std::make_shared<gadzhiev::Rectangle>(11, 13, gadzhiev::point_t { 3, 7 });

  gadzhiev::CompositeShape cmpShape(std::make_shared<gadzhiev::Circle>(32, gadzhiev::point_t { 1, 76 }));

  cmpShape.add(std::make_shared<gadzhiev::Circle>(1, gadzhiev::point_t{ -2, 7 }));
  cmpShape.add(rectangle);
  cmpShape.remove(0);
  BOOST_CHECK_EQUAL(cmpShape[1], rectangle);
  BOOST_CHECK_EQUAL(cmpShape.getSize(), 2);
}

BOOST_AUTO_TEST_CASE(RemoveLastFigure_ThrownLengthError)
{
  gadzhiev::CompositeShape cmpShape(std::make_shared<gadzhiev::Circle>(32, gadzhiev::point_t { 1, 76 }));
  BOOST_CHECK_THROW(cmpShape.remove(0), std::length_error);
}

BOOST_AUTO_TEST_CASE(InvalidIndexToRemove_ThrownOutOfRange)
{
  gadzhiev::CompositeShape cmpShape(std::make_shared<gadzhiev::Circle>(32, gadzhiev::point_t{ 1, 76 }));
  BOOST_CHECK_THROW(cmpShape.remove(3), std::out_of_range);
}
BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(CompositeShapeIndex)

BOOST_AUTO_TEST_CASE(ValidIndex_ValidGetFigureFromCmpShape)
{
  gadzhiev::point_t point{ 3, 7 };

  gadzhiev::CompositeShape cmpShape(std::make_shared<gadzhiev::Circle>(32, point));

  gadzhiev::Shape::ShapePtr rectangle = std::make_shared<gadzhiev::Rectangle>(11, 13, gadzhiev::point_t { 3, 45 });
  cmpShape.add(rectangle);

  BOOST_CHECK_CLOSE(cmpShape[0]->getCenter().x, point.x, epsilon);
  BOOST_CHECK_CLOSE(cmpShape[0]->getCenter().y, point.y, epsilon);
  BOOST_CHECK_EQUAL(cmpShape[1], rectangle);
}

BOOST_AUTO_TEST_CASE(InvalidIndex_ThrownOutOfRange)
{
  gadzhiev::point_t point{ 3, 7 };

  gadzhiev::CompositeShape cmpShape(std::make_shared<gadzhiev::Circle>(32, gadzhiev::point_t { 1, 76 }));

  std::shared_ptr<gadzhiev::Shape> rectangle = std::make_shared<gadzhiev::Rectangle>(11, 13, point);
  cmpShape.add(rectangle);

  BOOST_CHECK_THROW(cmpShape[-1], std::out_of_range);
  BOOST_CHECK_THROW(cmpShape[2], std::out_of_range);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(CompositeShapeMove)

BOOST_AUTO_TEST_CASE(ValidMoveDxDy_CmpShapeCenterValidMoved)
{
  gadzhiev::CompositeShape cmpShape(std::make_shared<gadzhiev::Circle>(23, gadzhiev::point_t { -3, 8 }));
  const int dx = 4;
  const int dy = 9;
  cmpShape.move(dx, dy);
  BOOST_CHECK_CLOSE(cmpShape.getCenter().x, -3 + dx, epsilon);
  BOOST_CHECK_CLOSE(cmpShape.getCenter().y, 8 + dy, epsilon);

  const int dxZero = 0;
  const int dyZero = 0;
  const gadzhiev::point_t previousCenter = cmpShape.getCenter();
  cmpShape.move(dxZero, dyZero);
  BOOST_CHECK_CLOSE(cmpShape.getCenter().x, previousCenter.x, epsilon);
  BOOST_CHECK_CLOSE(cmpShape.getCenter().y, previousCenter.y, epsilon);
}

BOOST_AUTO_TEST_CASE(ValidMoveToPoint_CmpShapeCenterValidMoved)
{
  const gadzhiev::point_t center{ 2, 2 };
  const gadzhiev::point_t secondCenter{ 4, 4 };

  gadzhiev::CompositeShape cmpShape(std::make_shared<gadzhiev::Circle>(2, center));
  cmpShape.add(std::make_shared<gadzhiev::Circle>(2, secondCenter));

  gadzhiev::point_t previousCenter{ cmpShape.getCenter() };
  gadzhiev::point_t newCenter{ 7, 5 };

  cmpShape.move(newCenter);
  BOOST_CHECK_CLOSE(cmpShape.getCenter().x, newCenter.x, epsilon);
  BOOST_CHECK_CLOSE(cmpShape.getCenter().y, newCenter.y, epsilon);
  BOOST_CHECK_CLOSE(cmpShape[0]->getCenter().x, center.x + cmpShape.getCenter().x - previousCenter.x, epsilon);
  BOOST_CHECK_CLOSE(cmpShape[0]->getCenter().y, center.y + cmpShape.getCenter().y - previousCenter.y, epsilon);

  gadzhiev::point_t newCenterInZero{ 0, 0 };
  cmpShape.move(newCenterInZero);
  BOOST_CHECK_CLOSE(cmpShape.getCenter().x, newCenterInZero.x, epsilon);
  BOOST_CHECK_CLOSE(cmpShape.getCenter().y, newCenterInZero.y, epsilon);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(CompositeShapeFrameRectangle)

BOOST_AUTO_TEST_CASE(FrameRectangle_ValidWidthHeightCenterOfRectangle)
{
  gadzhiev::CompositeShape cmpShape(std::make_shared<gadzhiev::Circle>(10, gadzhiev::point_t { 1, 20 }));

  gadzhiev::rectangle_t rectangleStruct = cmpShape.getFrameRect();
  BOOST_CHECK_CLOSE(rectangleStruct.height, 20, epsilon);
  BOOST_CHECK_CLOSE(rectangleStruct.width, 20, epsilon);
  BOOST_CHECK_CLOSE(rectangleStruct.pos.x, 1, epsilon);
  BOOST_CHECK_CLOSE(rectangleStruct.pos.y, 20, epsilon);
}

BOOST_AUTO_TEST_CASE(FrameRectangleAfterAdd_ValidWidthHeightCenterOfRectangle)
{
  gadzhiev::CompositeShape cmpShape(std::make_shared<gadzhiev::Circle>(2, gadzhiev::point_t { 4, 6 }));
  cmpShape.add(std::make_shared<gadzhiev::Rectangle>(7, 5, gadzhiev::point_t { -1, 2 }));

  gadzhiev::rectangle_t rectangleStruct = cmpShape.getFrameRect();
  BOOST_CHECK_CLOSE(rectangleStruct.height, 8.5, epsilon);
  BOOST_CHECK_CLOSE(rectangleStruct.width, 10.5, epsilon);
  BOOST_CHECK_CLOSE(rectangleStruct.pos.x, 0.75, epsilon);
  BOOST_CHECK_CLOSE(rectangleStruct.pos.y, 3.75, epsilon);
}

BOOST_AUTO_TEST_CASE(FrameRectangleAfterScale_ValidWidthHeightCenterOfRectangle)
{
  gadzhiev::CompositeShape cmpShape(std::make_shared<gadzhiev::Circle>(10, gadzhiev::point_t { 1, 32 }));

  const double scaleCoef = 3.7;
  cmpShape.scale(scaleCoef);
  gadzhiev::rectangle_t rectangleStruct = cmpShape.getFrameRect();

  BOOST_CHECK_CLOSE(rectangleStruct.height, 20 * scaleCoef, epsilon);
  BOOST_CHECK_CLOSE(rectangleStruct.width, 20 * scaleCoef, epsilon);
  BOOST_CHECK_CLOSE(rectangleStruct.pos.x, 1, epsilon);
  BOOST_CHECK_CLOSE(rectangleStruct.pos.y, 32, epsilon);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(CompositeShapeScale)

BOOST_AUTO_TEST_CASE(InvalidScale_CoefLessThanZeroOrZero_ThrownInvalidArgument)
{
  gadzhiev::CompositeShape cmpShape(std::make_shared<gadzhiev::Rectangle>(13, 34, gadzhiev::point_t { -5, 18 }));
  BOOST_CHECK_THROW(cmpShape.scale(0), std::invalid_argument);
  BOOST_CHECK_THROW(cmpShape.scale(-12), std::invalid_argument);
}

BOOST_AUTO_TEST_CASE(ValidScale_CoefMoreThanZero_CmpShapeValidScaled)
{
  gadzhiev::CompositeShape cmpShape(std::make_shared<gadzhiev::Rectangle>(2, 2, gadzhiev::point_t { 1, 3 }));
  cmpShape.add(std::make_shared<gadzhiev::Rectangle>(4, 4, gadzhiev::point_t{ 3.5, 0.5 }));

  gadzhiev::rectangle_t rectangleStruct = cmpShape.getFrameRect();

  const double scaleCoef = 0.65;
  cmpShape.scale(scaleCoef);

  BOOST_CHECK_CLOSE(cmpShape.getFrameRect().height, rectangleStruct.height * scaleCoef, epsilon);
  BOOST_CHECK_CLOSE(cmpShape.getFrameRect().width, rectangleStruct.width * scaleCoef, epsilon);
  BOOST_CHECK_CLOSE(cmpShape.getFrameRect().pos.x, rectangleStruct.pos.x, epsilon);
  BOOST_CHECK_CLOSE(cmpShape.getFrameRect().pos.y, rectangleStruct.pos.y, epsilon);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(CompositeShapeArea)

BOOST_AUTO_TEST_CASE(DefaultArea_ValidGetArea)
{
  gadzhiev::CompositeShape cmpShape(std::make_shared<gadzhiev::Rectangle>(13, 34, gadzhiev::point_t{ -5, 18 }));
  BOOST_CHECK_CLOSE(cmpShape.getArea(), 13 * 34, 0.0000001);
}

BOOST_AUTO_TEST_CASE(AreaAfterAddingFigures_ValidGetArea)
{
  gadzhiev::Shape::ShapePtr rectangle = std::make_shared<gadzhiev::Rectangle>(21, 4, gadzhiev::point_t{ -5, 18 });
  gadzhiev::Shape::ShapePtr circle = std::make_shared<gadzhiev::Circle>(12, gadzhiev::point_t{ 1, 6 });
  gadzhiev::Shape::ShapePtr secondCircle = std::make_shared<gadzhiev::Circle>(10, gadzhiev::point_t{ -1, 3 });

  gadzhiev::CompositeShape cmpShape(rectangle);
  cmpShape.add(circle);
  cmpShape.add(secondCircle);

  const double areaOfFirstFigure = rectangle->getArea();
  const double areaOfSecondFigure = circle->getArea();
  const double areaOfThirdFigure = secondCircle->getArea();

  BOOST_CHECK_CLOSE(cmpShape.getArea(), areaOfFirstFigure + areaOfSecondFigure + areaOfThirdFigure, 0.0000001);
}

BOOST_AUTO_TEST_CASE(AreaAfterMoving_AreaNotChanged)
{
  gadzhiev::Shape::ShapePtr rectangle = std::make_shared<gadzhiev::Rectangle>(21, 4, gadzhiev::point_t{ -5, 18 });
  gadzhiev::CompositeShape cmpShape(rectangle);

  const gadzhiev::point_t newCenter{ 12, -33 };
  const double dx = 6;
  const double dy = -34;
  const double area = cmpShape.getArea();

  cmpShape.move(dx, dy);

  BOOST_CHECK_CLOSE(cmpShape.getArea(), area, epsilon);

  cmpShape.move(newCenter);

  BOOST_CHECK_CLOSE(cmpShape.getArea(), area, epsilon);
}

BOOST_AUTO_TEST_CASE(AreaAfterScale_AreaChangedToSquaredCoef)
{
  gadzhiev::Shape::ShapePtr rectangle = std::make_shared<gadzhiev::Rectangle>(21, 4, gadzhiev::point_t{ -5, 18 });
  gadzhiev::Shape::ShapePtr circle = std::make_shared<gadzhiev::Circle>(12, gadzhiev::point_t{ 1, 6 });
  gadzhiev::Shape::ShapePtr secondCircle = std::make_shared<gadzhiev::Circle>(10, gadzhiev::point_t{ -1, 3 });

  gadzhiev::CompositeShape cmpShape(rectangle);
  cmpShape.add(circle);
  cmpShape.add(secondCircle);

  const double FirstShapeArea = rectangle->getArea();
  const double SecondShapeArea = circle->getArea();
  const double ThirdShapeArea = secondCircle->getArea();

  const double scaleCoef = 1.4;
  cmpShape.scale(scaleCoef);
  BOOST_CHECK_CLOSE(cmpShape.getArea(), scaleCoef * scaleCoef * (FirstShapeArea + SecondShapeArea + ThirdShapeArea), epsilon);
}

BOOST_AUTO_TEST_CASE(AreaAfterRotate_AreaNotChanged)
{
  gadzhiev::Shape::ShapePtr rectangle = std::make_shared<gadzhiev::Rectangle>(21, 4, gadzhiev::point_t{ -5, 18 });
  gadzhiev::CompositeShape cmpShape(rectangle);

  const double rotateCoef = 45;
  const double area = cmpShape.getArea();

  cmpShape.rotate(rotateCoef);

  BOOST_CHECK_CLOSE(cmpShape.getArea(), area, epsilon);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(CompositeShapeRotate)

BOOST_AUTO_TEST_CASE(CmpShapeRotate_ValidParametersOfFiguresAndValidFrameRect)
{
  gadzhiev::CompositeShape cmpShape(std::make_shared<gadzhiev::Rectangle>(4, 4, gadzhiev::point_t{ 2, 7 }));
  cmpShape.add(std::make_shared<gadzhiev::Rectangle>(2, 6, gadzhiev::point_t{ 3, 3 }));

  gadzhiev::rectangle_t rectangleStruct = cmpShape.getFrameRect();

  const double rotateCoef = 90;
  cmpShape.rotate(rotateCoef);

  BOOST_CHECK_CLOSE(cmpShape[0]->getCenter().x, -0.5, epsilon);
  BOOST_CHECK_CLOSE(cmpShape[0]->getCenter().y, 4.5, epsilon);
  BOOST_CHECK_CLOSE(cmpShape[1]->getCenter().x, 3.5, epsilon);
  BOOST_CHECK_CLOSE(cmpShape[1]->getCenter().y, 5.5, epsilon);

  BOOST_CHECK_CLOSE(cmpShape.getFrameRect().width, 9, epsilon);
  BOOST_CHECK_CLOSE(cmpShape.getFrameRect().height, 4, epsilon);
  BOOST_CHECK_CLOSE(cmpShape.getFrameRect().width, 9, epsilon);
  BOOST_CHECK_CLOSE(cmpShape.getFrameRect().pos.x, rectangleStruct.pos.x, epsilon);
  BOOST_CHECK_CLOSE(cmpShape.getFrameRect().pos.y, rectangleStruct.pos.y, epsilon);
}

BOOST_AUTO_TEST_SUITE_END()


#include "testsuite.h"
#include <blitz/array.h>

BZ_USING_NAMESPACE(blitz)

int main()
{
    Array<int,1> A(1);
    A(0) = 5;
    Array<int,1> B(1);
    B = A;
}