/* -*- mia-c++  -*-
 *
 * This file is part of MIA - a toolbox for medical image analysis
 * Copyright (c) Leipzig, Madrid 1999-2017 Gert Wollny
 *
 * MIA is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MIA; if not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <mia/internal/autotest.hh>
#include <mia/3d/imagedraw.hh>
#include <mia/3d/imageio.hh>
#include <set>

using namespace mia;
using std::set;

class SimpleBitImageDrawFixture
{

protected:
       SimpleBitImageDrawFixture();

       C3DBitImageDrawTarget output;

};


BOOST_FIXTURE_TEST_CASE( test_simple_draw_point, SimpleBitImageDrawFixture )
{
       output.draw_point(C3DFVector(4, 5, 6));
       auto& img = output.get_image();
       auto i = img.begin_range(C3DBounds::_0, img.get_size());
       auto e = img.end_range(C3DBounds::_0, img.get_size());

       for (; i != e; ++i) {
              BOOST_CHECK_EQUAL(*i, i.pos() == C3DBounds(4, 5, 6));
       }
}

BOOST_FIXTURE_TEST_CASE( test_simple_draw_point_outside, SimpleBitImageDrawFixture )
{
       output.draw_point(C3DFVector(-1, 5, 6));
       output.draw_point(C3DFVector(1, -5, 6));
       output.draw_point(C3DFVector(1, 5, -6));
       output.draw_point(C3DFVector(10, 5, 6));
       output.draw_point(C3DFVector(1, 11, 6));
       output.draw_point(C3DFVector(1, 5, 12));
       auto& img = output.get_image();
       auto i = img.begin_range(C3DBounds::_0, img.get_size());
       auto e = img.end_range(C3DBounds::_0, img.get_size());

       while (i != e) {
              if (*i) {
                     cvdebug() << "Unexpected pixel at " << i.pos() << "\n";
              }

              BOOST_CHECK(!*i);
              ++i;
       }
}

BOOST_FIXTURE_TEST_CASE( test_simple_draw_point_corners, SimpleBitImageDrawFixture )
{
       output.draw_point(C3DFVector(-.4, -.4, -.4));
       output.draw_point(C3DFVector(9.4, -.4, -.4));
       output.draw_point(C3DFVector(-.4, 10.4, -.4));
       output.draw_point(C3DFVector(-.4, -.4, 11.4));
       output.draw_point(C3DFVector(9.4, 10.4, 11.4));
       output.draw_point(C3DFVector(-.4, 10.4, 11.4));
       output.draw_point(C3DFVector(9.4, -.4,  11.4));
       output.draw_point(C3DFVector(9.4, 10.4, -.4));
       auto& img = output.get_image();
       auto i = img.begin_range(C3DBounds::_0, img.get_size());
       auto e = img.end_range(C3DBounds::_0, img.get_size());

       for (; i != e; ++i) {
              if (i.pos() == C3DBounds::_0 ||
                  i.pos() == C3DBounds(9, 0, 0) ||
                  i.pos() == C3DBounds(0, 10, 0) ||
                  i.pos() == C3DBounds(0, 0, 11) ||
                  i.pos() == C3DBounds(9, 10, 0) ||
                  i.pos() == C3DBounds(9,  0, 11) ||
                  i.pos() == C3DBounds(0, 10, 11) ||
                  i.pos() == C3DBounds(9, 10, 11))
                     BOOST_CHECK(*i);
              else
                     BOOST_CHECK(!*i);
       }
}


struct compare_coordinate  {
       bool operator () (const C3DBounds& lhs, const C3DBounds& rhs)
       {
              return (lhs.z < rhs.z) ||
                     ((lhs.z == rhs.z) && ((lhs.y < rhs.y) ||
                                           ((lhs.y == rhs.y) && (lhs.x < rhs.x))));
       }
};

BOOST_FIXTURE_TEST_CASE( test_simple_draw_line_z_pivot, SimpleBitImageDrawFixture )
{
       output.draw_line(C3DFVector(0, 0, 0), C3DFVector(10, 11, 12));
       auto& img = output.get_image();
       auto i = img.begin_range(C3DBounds::_0, img.get_size());
       auto e = img.end_range(C3DBounds::_0, img.get_size());
       set<C3DBounds, compare_coordinate> pixels;
       pixels.insert(C3DBounds::_0);
       pixels.insert(img.get_size() - C3DBounds::_1);
       C3DFVector dir(10.0 / 24, 11.0 / 24, 0.5f);
       C3DFVector p(0, 0, 0);

       for (int k = 0; k < 24; ++k, p += dir) {
              C3DBounds ip(static_cast<signed>(roundf(p.x)),
                           static_cast<signed>(roundf(p.y)),
                           static_cast<signed>(roundf(p.z)));
              cvdebug() << "test about to draw " << ip << " from " << p << "\n";
              pixels.insert(ip);
       }

       cvdebug() << "Expect " << pixels.size() << " pixels to be set\n";

       for (; i != e; ++i) {
              cvdebug() << i.pos() << " = "  << *i << "\n";

              if (pixels.find(i.pos()) == pixels.end())
                     BOOST_CHECK(!*i);
              else
                     BOOST_CHECK(*i);
       }
}

BOOST_FIXTURE_TEST_CASE( test_simple_draw_line_pivot_x, SimpleBitImageDrawFixture )
{
       output.draw_line(C3DFVector(0.1, 4.2, 5.3), C3DFVector(10.1, 10.2, 12.3));
       auto& img = output.get_image();
       auto i = img.begin_range(C3DBounds::_0, img.get_size());
       auto e = img.end_range(C3DBounds::_0, img.get_size());
       set<C3DBounds, compare_coordinate> pixels;
       C3DFVector dir(0.5, 0.3, 0.35f);
       C3DFVector p(0.1, 4.2, 5.3);

       for (int k = 0; k < 22; ++k, p += dir) {
              C3DBounds ip(static_cast<signed>(roundf(p.x)),
                           static_cast<signed>(roundf(p.y)),
                           static_cast<signed>(roundf(p.z)));
              cvdebug() << "test about to draw " << ip << " from " << p << "\n";
              pixels.insert(ip);
       }

       cvdebug() << "Expect " << pixels.size() << " pixels to be set\n";

       for (; i != e; ++i) {
              cvdebug() << i.pos() << " = "  << *i << "\n";

              if (pixels.find(i.pos()) == pixels.end())
                     BOOST_CHECK(!*i);
              else
                     BOOST_CHECK(*i);
       }
}

BOOST_FIXTURE_TEST_CASE( test_simple_draw_line_pivot_y, SimpleBitImageDrawFixture )
{
       output.draw_line(C3DFVector(4, 0.1, 5.2), C3DFVector(10, 10.1, 9.2));
       auto& img = output.get_image();
       auto i = img.begin_range(C3DBounds::_0, img.get_size());
       auto e = img.end_range(C3DBounds::_0, img.get_size());
       set<C3DBounds, compare_coordinate> pixels;
       C3DFVector dir(0.3, .5, 0.2f);
       C3DFVector p(4, 0.1, 5.2);

       for (int k = 0; k < 22; ++k, p += dir) {
              C3DBounds ip(static_cast<signed>(roundf(p.x)),
                           static_cast<signed>(roundf(p.y)),
                           static_cast<signed>(roundf(p.z)));
              cvdebug() << "test about to draw " << ip << " from " << p << "\n";
              pixels.insert(ip);
       }

       cvdebug() << "Expect " << pixels.size() << " pixels to be set\n";

       for (; i != e; ++i) {
              cvdebug() << i.pos() << " = "  << *i << "\n";

              if (pixels.find(i.pos()) == pixels.end())
                     BOOST_CHECK(!*i);
              else
                     BOOST_CHECK(*i);
       }
}

BOOST_FIXTURE_TEST_CASE( test_draw_line_pivot_x_outside_ends, SimpleBitImageDrawFixture )
{
       output.draw_line(C3DFVector(-4, 2, 5), C3DFVector(16, 14, 9));
       auto& img = output.get_image();
       auto i = img.begin_range(C3DBounds::_0, img.get_size());
       auto e = img.end_range(C3DBounds::_0, img.get_size());
       set<C3DBounds, compare_coordinate> pixels;
       C3DFVector dir(0.5, 0.3, 0.1f);
       C3DFVector p(-4, 2, 5);

       for (int k = 0; k < 40; ++k, p += dir) {
              if (p.x >= 0 || p.x < img.get_size().x) {
                     C3DBounds ip(static_cast<signed>(roundf(p.x)),
                                  static_cast<signed>(roundf(p.y)),
                                  static_cast<signed>(roundf(p.z)));
                     cvdebug() << "test about to draw " << ip << " from " << p << "\n";

                     if (ip.x < img.get_size().x)
                            pixels.insert(ip);
              }
       }

       cvdebug() << "Expect " << pixels.size() << " pixels to be set\n";

       for (; i != e; ++i) {
              cvdebug() << i.pos() << " = "  << *i << "\n";

              if (pixels.find(i.pos()) == pixels.end())
                     BOOST_CHECK(!*i);
              else
                     BOOST_CHECK(*i);
       }
}



BOOST_FIXTURE_TEST_CASE( test_draw_line_pivot_y_outside_ends, SimpleBitImageDrawFixture )
{
       output.draw_line(C3DFVector(4, -6, 5), C3DFVector(10, 14, 9));
       auto& img = output.get_image();
       auto i = img.begin_range(C3DBounds::_0, img.get_size());
       auto e = img.end_range(C3DBounds::_0, img.get_size());
       set<C3DBounds, compare_coordinate> pixels;
       C3DFVector dir(0.15, 0.5, 0.1f);
       C3DFVector p(4, -6, 5);

       for (int k = 0; k < 42; ++k, p += dir) {
              C3DBounds ip(static_cast<signed>(roundf(p.x)),
                           static_cast<signed>(roundf(p.y)),
                           static_cast<signed>(roundf(p.z)));
              cvdebug() << "test about to draw " << ip << " from " << p << "\n";

              if (ip.y < img.get_size().y)
                     pixels.insert(ip);
       }

       cvdebug() << "Expect " << pixels.size() << " pixels to be set\n";

       for (; i != e; ++i) {
              cvdebug() << i.pos() << " = "  << *i << "\n";

              if (pixels.find(i.pos()) == pixels.end())
                     BOOST_CHECK(!*i);
              else
                     BOOST_CHECK(*i);
       }
}


BOOST_FIXTURE_TEST_CASE( test_draw_line_pivot_y_outside_ends_dx_is_zero, SimpleBitImageDrawFixture )
{
       output.draw_line(C3DFVector(4, -6, 5), C3DFVector(4, 14, 9));
       auto& img = output.get_image();
       auto i = img.begin_range(C3DBounds::_0, img.get_size());
       auto e = img.end_range(C3DBounds::_0, img.get_size());
       set<C3DBounds, compare_coordinate> pixels;
       C3DFVector dir(0.0, 0.5, 0.1f);
       C3DFVector p(4, -6, 5);

       for (int k = 0; k < 42; ++k, p += dir) {
              C3DBounds ip(static_cast<signed>(roundf(p.x)),
                           static_cast<signed>(roundf(p.y)),
                           static_cast<signed>(roundf(p.z)));
              cvdebug() << "test about to draw " << ip << " from " << p << "\n";

              if (ip.y < img.get_size().y)
                     pixels.insert(ip);
       }

       cvdebug() << "Expect " << pixels.size() << " pixels to be set\n";

       for (; i != e; ++i) {
              cvdebug() << i.pos() << " = "  << *i << "\n";

              if (pixels.find(i.pos()) == pixels.end())
                     BOOST_CHECK(!*i);
              else
                     BOOST_CHECK(*i);
       }
}


BOOST_FIXTURE_TEST_CASE( test_draw_line_dN_is_zero_and_N_outside, SimpleBitImageDrawFixture )
{
       // x outside and parallel
       output.draw_line(C3DFVector(-1, -6, 5), C3DFVector(-1, 14, 9));
       output.draw_line(C3DFVector(30, -3, 2), C3DFVector(30, 6, 5));
       // y outside and parallel
       output.draw_line(C3DFVector(1, -6, 5), C3DFVector(1, -6, 9));
       output.draw_line(C3DFVector(1, 40, 5), C3DFVector(1, 40, 9));
       // z outside and parallel
       output.draw_line(C3DFVector(1, 6, -5), C3DFVector(8, 7, -5));
       output.draw_line(C3DFVector(1, 6, 22), C3DFVector(1, 14, 22));
       auto& img = output.get_image();
       auto i = img.begin_range(C3DBounds::_0, img.get_size());
       auto e = img.end_range(C3DBounds::_0, img.get_size());
       cvdebug() << "Expect 0  pixels to be set\n";

       for (; i != e; ++i) {
              cvdebug() << i.pos() << " = "  << *i << "\n";
              BOOST_CHECK(!*i);
       }
}

BOOST_FIXTURE_TEST_CASE( test_draw_line_pivot_z_outside_ends, SimpleBitImageDrawFixture )
{
       output.draw_line(C3DFVector(4, 4, -5), C3DFVector(10, 14, 15));
       auto& img = output.get_image();
       auto i = img.begin_range(C3DBounds::_0, img.get_size());
       auto e = img.end_range(C3DBounds::_0, img.get_size());
       set<C3DBounds, compare_coordinate> pixels;
       C3DFVector dir(0.15, 0.25, 0.5f);
       C3DFVector p(5.5, 6.5, 0);

       for (int k = 0; k < 41 && p.z < 12; ++k, p += dir) {
              C3DBounds ip(static_cast<signed>(roundf(p.x)),
                           static_cast<signed>(roundf(p.y)),
                           static_cast<signed>(roundf(p.z)));
              cvdebug() << "test about to draw " << ip << " from " << p << "\n";

              if (ip.z < img.get_size().z)
                     pixels.insert(ip);
       }

       cvdebug() << "Expect " << pixels.size() << " pixels to be set\n";

       for (; i != e; ++i) {
              cvdebug() << i.pos() << " = "  << *i << "\n";

              if (pixels.find(i.pos()) == pixels.end())
                     BOOST_CHECK(!*i);
              else
                     BOOST_CHECK(*i);
       }
}


BOOST_FIXTURE_TEST_CASE( test_draw_line_pivot_x_outside_ends_dz_is_zero, SimpleBitImageDrawFixture )
{
       output.draw_line(C3DFVector(-4, 2, 5), C3DFVector(16, 14, 5));
       auto& img = output.get_image();
       auto i = img.begin_range(C3DBounds::_0, img.get_size());
       auto e = img.end_range(C3DBounds::_0, img.get_size());
       set<C3DBounds, compare_coordinate> pixels;
       C3DFVector dir(0.5, 0.3, 0.0);
       C3DFVector p(-4, 2, 5);

       for (int k = 0; k < 42; ++k, p += dir) {
              C3DBounds ip(static_cast<signed>(roundf(p.x)),
                           static_cast<signed>(roundf(p.y)),
                           static_cast<signed>(roundf(p.z)));
              cvdebug() << "test about to draw " << ip << " from " << p << "\n";

              if (ip.x < img.get_size().x)
                     pixels.insert(ip);
       }

       cvdebug() << "Expect " << pixels.size() << " pixels to be set\n";

       for (; i != e; ++i) {
              cvdebug() << i.pos() << " = "  << *i << "\n";

              if (pixels.find(i.pos()) == pixels.end())
                     BOOST_CHECK(!*i);
              else
                     BOOST_CHECK(*i);
       }
}


BOOST_FIXTURE_TEST_CASE( test_draw_line_pivot_z_outside_ends_dy_is_zero, SimpleBitImageDrawFixture )
{
       output.draw_line(C3DFVector(4, 4, -5), C3DFVector(10, 4, 15));
       auto& img = output.get_image();
       auto i = img.begin_range(C3DBounds::_0, img.get_size());
       auto e = img.end_range(C3DBounds::_0, img.get_size());
       set<C3DBounds, compare_coordinate> pixels;
       C3DFVector dir(0.15, 0.0, 0.5f);
       C3DFVector p(5.5, 4, 0);

       for (int k = 0; k < 41 && p.z < 12; ++k, p += dir) {
              C3DBounds ip(static_cast<signed>(roundf(p.x)),
                           static_cast<signed>(roundf(p.y)),
                           static_cast<signed>(roundf(p.z)));
              cvdebug() << "test about to draw " << ip << " from " << p << "\n";

              if (ip.z < img.get_size().z)
                     pixels.insert(ip);
       }

       cvdebug() << "Expect " << pixels.size() << " pixels to be set\n";

       for (; i != e; ++i) {
              cvdebug() << i.pos() << " = "  << *i << "\n";

              if (pixels.find(i.pos()) == pixels.end())
                     BOOST_CHECK(!*i);
              else
                     BOOST_CHECK(*i);
       }
}


BOOST_FIXTURE_TEST_CASE( test_draw_line_triangle_inside, SimpleBitImageDrawFixture )
{
       C3DFVector a(4, 4, 4);
       C3DFVector b(9, 10, 11);
       C3DFVector c(5, 8, 1);
       output.draw_triangle(a, b, c);
       auto img = output.get_image();
       auto i = img.begin_range(C3DBounds::_0, img.get_size());
       auto e = img.end_range(C3DBounds::_0, img.get_size());
       // visually inspecting the pixels shows that this is
       // indeed the approximation of a triangle in 3D
       set<C3DBounds, compare_coordinate> pixels;
       pixels.insert(C3DBounds(9, 10, 11));
       pixels.insert(C3DBounds(8, 9, 10));
       pixels.insert(C3DBounds(8, 10, 10));
       pixels.insert(C3DBounds(9, 10, 10));
       pixels.insert(C3DBounds(8, 9, 9));
       pixels.insert(C3DBounds(8, 10, 9));
       pixels.insert(C3DBounds(7, 7, 8));
       pixels.insert(C3DBounds(7, 9, 8));
       pixels.insert(C3DBounds(8, 9, 8));
       pixels.insert(C3DBounds(7, 8, 8));
       pixels.insert(C3DBounds(6, 7, 7));
       pixels.insert(C3DBounds(7, 9, 7));
       pixels.insert(C3DBounds(7, 8, 7));
       pixels.insert(C3DBounds(7, 7, 7));
       pixels.insert(C3DBounds(7, 9, 6));
       pixels.insert(C3DBounds(6, 7, 6));
       pixels.insert(C3DBounds(6, 8, 6));
       pixels.insert(C3DBounds(6, 6, 6));
       pixels.insert(C3DBounds(5, 6, 6));
       pixels.insert(C3DBounds(7, 8, 6));
       pixels.insert(C3DBounds(6, 9, 5));
       pixels.insert(C3DBounds(7, 9, 5));
       pixels.insert(C3DBounds(6, 8, 5));
       pixels.insert(C3DBounds(6, 7, 5));
       pixels.insert(C3DBounds(5, 5, 5));
       pixels.insert(C3DBounds(5, 6, 5));
       pixels.insert(C3DBounds(4, 5, 4));
       pixels.insert(C3DBounds(6, 7, 4));
       pixels.insert(C3DBounds(6, 8, 4));
       pixels.insert(C3DBounds(6, 9, 4));
       pixels.insert(C3DBounds(5, 5, 4));
       pixels.insert(C3DBounds(5, 6, 4));
       pixels.insert(C3DBounds(5, 7, 4));
       pixels.insert(C3DBounds(4, 4, 4));
       pixels.insert(C3DBounds(5, 6, 3));
       pixels.insert(C3DBounds(5, 8, 3));
       pixels.insert(C3DBounds(4, 5, 3));
       pixels.insert(C3DBounds(4, 6, 3));
       pixels.insert(C3DBounds(5, 7, 3));
       pixels.insert(C3DBounds(6, 8, 3));
       pixels.insert(C3DBounds(5, 6, 2));
       pixels.insert(C3DBounds(5, 7, 2));
       pixels.insert(C3DBounds(5, 8, 2));
       pixels.insert(C3DBounds(5, 8, 1));
       cvdebug() << "Expect " << pixels.size() << " pixels to be set\n";

       for (; i != e; ++i) {
              cvdebug() << i.pos() << " = "  << *i << "\n";

              if (pixels.find(i.pos()) == pixels.end()) {
                     BOOST_CHECK(!*i);
              } else
                     BOOST_CHECK(*i);
       }
}




SimpleBitImageDrawFixture::SimpleBitImageDrawFixture():
       output(C3DBounds(10, 11, 12), C3DFVector::_0, C3DFVector::_1)
{
}
