    // Copyright (C) 2010  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.

    #include "tester.h"
    #include <dlib/matrix.h>
    #include <dlib/rand.h>
    #include <vector>
    #include <sstream>

    namespace
    {
        using namespace test;
        using namespace dlib;
        using namespace std;
        dlib::logger dlog("test.symmetric_matrix_cache");

        class test_symmetric_matrix_cache : public tester
        {
            /*!
                WHAT THIS OBJECT REPRESENTS
                    This object represents a unit test.  When it is constructed
                    it adds itself into the testing framework.
            !*/
        public:
            test_symmetric_matrix_cache (
            ) :
                tester (
                    "test_symmetric_matrix_cache",       // the command line argument name for this test
                    "Run tests on the symmetric_matrix_cache function.", // the command line argument description
                    0                     // the number of command line arguments for this test
                )
            {
            }

            dlib::rand rnd;

        // -----------------------------------

            template <typename EXP1, typename EXP2>
            void test_colm_exp (
                const matrix_exp<EXP1>& m1,
                const matrix_exp<EXP2>& m2
            )
            {
                for (long i = 0; i < m1.nc(); ++i)
                {

                    typename colm_exp<EXP1>::type c1 = colm(m1,i);
                    typename colm_exp<EXP2>::type c2 = colm(m2,i);

                    DLIB_TEST(equal(c1 , c2));
                    DLIB_TEST(equal(colm(m1,i) , c2));
                    DLIB_TEST(equal(c1 , colm(m2,i)));
                    DLIB_TEST(equal(colm(m1,i) , colm(m2,i)));
                }

                // Get a bunch of columns at once to test out the reference
                // counting and automatic cache expansion built into the symmetric_matrix_cache.
                // This test verifies that, for example, getting column 3 doesn't stomp on
                // any of the previous columns.
                typename colm_exp<EXP1>::type c1_0 = colm(m1,0);
                typename colm_exp<EXP1>::type c1_1 = colm(m1,1);
                typename colm_exp<EXP1>::type c1_2 = colm(m1,2);
                typename colm_exp<EXP1>::type c1_3 = colm(m1,3);
                typename colm_exp<EXP1>::type c1_4 = colm(m1,4);
                typename colm_exp<EXP1>::type c1_5 = colm(m1,5);

                typename colm_exp<EXP2>::type c2_0 = colm(m2,0);
                typename colm_exp<EXP2>::type c2_1 = colm(m2,1);
                typename colm_exp<EXP2>::type c2_2 = colm(m2,2);
                typename colm_exp<EXP2>::type c2_3 = colm(m2,3);
                typename colm_exp<EXP2>::type c2_4 = colm(m2,4);
                typename colm_exp<EXP2>::type c2_5 = colm(m2,5);

                DLIB_TEST(equal(c1_0, c2_0));
                DLIB_TEST(equal(c1_1, c2_1));
                DLIB_TEST(equal(c1_2, c2_2));
                DLIB_TEST(equal(c1_3, c2_3));
                DLIB_TEST(equal(c1_4, c2_4));
                DLIB_TEST(equal(c1_5, c2_5));
            }

        // -----------------------------------

            template <typename EXP1, typename EXP2>
            void test_rowm_exp (
                const matrix_exp<EXP1>& m1,
                const matrix_exp<EXP2>& m2
            )
            {
                for (long i = 0; i < m1.nc(); ++i)
                {

                    typename rowm_exp<EXP1>::type r1 = rowm(m1,i);
                    typename rowm_exp<EXP2>::type r2 = rowm(m2,i);

                    DLIB_TEST(equal(r1 , r2));
                    DLIB_TEST(equal(rowm(m1,i) , r2));
                    DLIB_TEST(equal(r1 , rowm(m2,i)));
                    DLIB_TEST(equal(rowm(m1,i) , rowm(m2,i)));
                }

                // Get a bunch of rows at once to test out the reference
                // counting and automatic cache expansion built into the symmetric_matrix_cache.
                // This test verifies that, for example, getting row 3 doesn't stomp on
                // any of the previous rows.
                typename rowm_exp<EXP1>::type r1_0 = rowm(m1,0);
                typename rowm_exp<EXP1>::type r1_1 = rowm(m1,1);
                typename rowm_exp<EXP1>::type r1_2 = rowm(m1,2);
                typename rowm_exp<EXP1>::type r1_3 = rowm(m1,3);
                typename rowm_exp<EXP1>::type r1_4 = rowm(m1,4);
                typename rowm_exp<EXP1>::type r1_5 = rowm(m1,5);

                typename rowm_exp<EXP2>::type r2_0 = rowm(m2,0);
                typename rowm_exp<EXP2>::type r2_1 = rowm(m2,1);
                typename rowm_exp<EXP2>::type r2_2 = rowm(m2,2);
                typename rowm_exp<EXP2>::type r2_3 = rowm(m2,3);
                typename rowm_exp<EXP2>::type r2_4 = rowm(m2,4);
                typename rowm_exp<EXP2>::type r2_5 = rowm(m2,5);

                DLIB_TEST(equal(r1_0, r2_0));
                DLIB_TEST(equal(r1_1, r2_1));
                DLIB_TEST(equal(r1_2, r2_2));
                DLIB_TEST(equal(r1_3, r2_3));
                DLIB_TEST(equal(r1_4, r2_4));
                DLIB_TEST(equal(r1_5, r2_5));
            }

        // -----------------------------------

            template <typename EXP1, typename EXP2>
            void test_diag_exp (
                const matrix_exp<EXP1>& m1,
                const matrix_exp<EXP2>& m2
            )
            {

                typename diag_exp<EXP1>::type c1 = diag(m1);
                typename diag_exp<EXP2>::type c2 = diag(m2);

                DLIB_TEST(equal(c1 , c2));
                DLIB_TEST(equal(diag(m1) , c2));
                DLIB_TEST(equal(c1 , diag(m2)));
                DLIB_TEST(equal(diag(m1) , diag(m2)));
            }

        // -----------------------------------

            void test_stuff (
                long csize
            )
            {
                print_spinner();
                dlog << LINFO << "csize: "<< csize;
                matrix<double> m = randm(10,10,rnd);

                m = make_symmetric(m);

                DLIB_TEST(equal(symmetric_matrix_cache<float>(m, csize), matrix_cast<float>(m)));
                DLIB_TEST(equal(symmetric_matrix_cache<double>(m, csize), matrix_cast<double>(m)));

                dlog << LINFO << "test colm/rowm";

                for (long i = 0; i < m.nr(); ++i)
                {
                    DLIB_TEST(equal(colm(symmetric_matrix_cache<float>(m, csize),i), colm(matrix_cast<float>(m),i)));
                    DLIB_TEST(equal(rowm(symmetric_matrix_cache<float>(m, csize),i), rowm(matrix_cast<float>(m),i)));
                    // things are supposed to be symmetric
                    DLIB_TEST(equal(colm(symmetric_matrix_cache<float>(m, csize),i), trans(rowm(matrix_cast<float>(m),i))));
                    DLIB_TEST(equal(rowm(symmetric_matrix_cache<float>(m, csize),i), trans(colm(matrix_cast<float>(m),i))));
                }

                dlog << LINFO << "test diag";
                DLIB_TEST(equal(diag(symmetric_matrix_cache<float>(m,csize)), diag(matrix_cast<float>(m))));

                test_colm_exp(symmetric_matrix_cache<float>(m,csize), matrix_cast<float>(m));
                test_rowm_exp(symmetric_matrix_cache<float>(m,csize), matrix_cast<float>(m));
                test_diag_exp(symmetric_matrix_cache<float>(m,csize), matrix_cast<float>(m));

                test_colm_exp(tmp(symmetric_matrix_cache<float>(m,csize)), tmp(matrix_cast<float>(m)));
                test_rowm_exp(symmetric_matrix_cache<float>(m,csize), tmp(matrix_cast<float>(m)));
                test_diag_exp(tmp(symmetric_matrix_cache<float>(m,csize)), tmp(matrix_cast<float>(m)));
            }

            void perform_test (
            )
            {

                for (int itr = 0; itr < 5; ++itr)
                {
                    test_stuff(0);
                    test_stuff(1);
                    test_stuff(2);
                }

            }
        };

        // Create an instance of this object.  Doing this causes this test
        // to be automatically inserted into the testing framework whenever this cpp file
        // is linked into the project.  Note that since we are inside an unnamed-namespace
        // we won't get any linker errors about the symbol a being defined multiple times.
        test_symmetric_matrix_cache a;

    }