/**
 * @ccflags -Wall -O3 -pedantic
 * @ldflags -lm -lopencv_core -lopencv_highgui -lopencv_imgproc
 */
#include <vpu/cvimage.hh>
#include "test.hh"

using namespace std;
using namespace cv;
using namespace sw::utest;

using sw::im::cvimage;
typedef sw::im::cvimage::color_t color_t;
typedef sw::im::cvimage::point_t point_t;
typedef sw::im::cvimage::line_t line_t;
typedef sw::im::cvimage::rect_t rect_t;
typedef sw::im::cvimage::circle_t circle_t;
typedef sw::im::cvimage::rotated_rect_t rotated_rect_t;
typedef sw::im::cvimage::ellipse_t ellipse_t;
typedef sw::im::cvimage::polynom_t polynom_t;
typedef sw::im::cvimage::contour_t contour_t;
typedef sw::im::cvimage::line_list_t line_list_t;
typedef sw::im::cvimage::circle_list_t circle_list_t;
typedef sw::im::cvimage::contour_list_t contour_list_t;
typedef sw::im::cvimage::contour_ccomp_list_t contour_ccomp_list_t;
typedef sw::im::cvimage::contour_nested_list_t contour_nested_list_t;
typedef sw::im::cvimage::histogram_t histogram_t;



void test_load_save()
{
  cvimage im;
  test_expect_noexcept( im.load("../res/cvimage/im1.png") );
  test_comment( "loaded: " << im.width() << "x" << im.height() );
  if(!test_expect_cond(!im.empty() )) return;
  test_expect( im.max_quant() == 255 );
  test_expect( im.width() == 100 );
  test_expect( im.height() == 75 );
  test_expect( im.area() == im.width()*im.height() );
  test_expect(!im.is_gray() );
  test_expect(!im.is_hsv() );
  test_expect( im.is_rgb() );
  test_expect( im.color_space() == cvimage::csp_rgb );
  test_expect_noexcept( im.to_hsv() );
  test_expect( im.width() == 100 );
  test_expect( im.height() == 75 );
  test_expect( im.area() == im.width()*im.height() );
  test_expect(!im.is_gray() );
  test_expect( im.is_hsv() );
  test_expect(!im.is_rgb() );
  test_expect( im.color_space() == cvimage::csp_hsv );
  test_expect_noexcept( im.to_grayscale() );
  test_expect( im.width() == 100 );
  test_expect( im.height() == 75 );
  test_expect( im.area() == im.width()*im.height() );
  test_expect( im.is_gray() );
  test_expect(!im.is_hsv() );
  test_expect(!im.is_rgb() );
  test_expect( im.color_space() == cvimage::csp_gray );

  // Save, reload ...
  test_expect( !im.file().empty() );
  tmp_file f1("im.png"), f2("im.png");
  im.save(f1);
  cvimage im2(f1.path());
  test_expect( im2.file() == f1.path() );
  im2.save(f2);
  test_expect( im2.file() == f1.path() );
}

void test_color()
{
  test_comment("Default constructor, ==, !=, !");
  test_expect( color_t() == color_t::black );
  test_expect(!color_t::black );
  test_expect( color_t::black == color_t::black );
  test_expect(!(color_t::black != color_t::black) );
  test_expect( color_t::black != color_t::white );

  test_comment("Black components");
  test_expect( color_t::black.r() == 0 );
  test_expect( color_t::black.g() == 0 );
  test_expect( color_t::black.b() == 0 );
  test_expect( color_t::black.a() == 0 );

  test_comment("White components");
  test_expect( color_t::white.r() == color_t::max_val() );
  test_expect( color_t::white.g() == color_t::max_val() );
  test_expect( color_t::white.b() == color_t::max_val() );
  test_expect( color_t::white.a() == 0 );

  test_comment("to cv::scalar");
  test_expect( ((cv::Scalar)color_t::black)[0] == 0 );
  test_expect( ((cv::Scalar)color_t::white)[0] == 255 );
  test_expect( ((cv::Scalar)color_t::red) == cv::Scalar(0,0,255,0) ); // BGRA

  test_comment("from string");
  test_expect( color_t::white == color_t("#ffffff") );
  test_expect( color_t::white == color_t("#fff") );
  test_expect( color_t::white == color_t("ffffff") );
  test_expect( color_t::white == color_t("fff") );
  test_expect( color_t::black == color_t("#000000") );
  test_expect( color_t::black == color_t("#000") );
  test_expect( color_t::black == color_t("000000") );
  test_expect( color_t::black == color_t("000") );
  test_expect( color_t::red == color_t("#f00") );
  test_expect( color_t::green == color_t("#0f0") );
  test_expect( color_t::blue == color_t("#00f") );

  test_comment("to string");
  test_expect( (std::string) color_t::black == "#000000" );
  test_expect( (std::string) color_t::white == "#ffffff" );
  test_expect( (std::string) color_t::red   == "#ff0000" );
  test_expect( (std::string) color_t::green == "#00ff00" );
  test_expect( (std::string) color_t::blue  == "#0000ff" );

  test_comment("From RGB float components");
  test_expect( color_t::rgb(0, 0, 0) == color_t::black );
  test_expect( color_t::rgb(1, 1, 1) == color_t::white );
  test_expect( color_t::rgb(1, 0, 0) == color_t::red );
  test_expect( color_t::rgb(0, 1, 0) == color_t::green );
  test_expect( color_t::rgb(0, 0, 1) == color_t::blue );
  test_expect(color_t::rgb(.5, .5, .5) == "#7f7f7f" );

  test_comment("From HSV float components");
  test_expect( color_t::hsv(0.0 / 3, 1, 1.) == color_t::red );
  test_expect( color_t::hsv(1.0 / 3, 1, 1.) == color_t::green );
  test_expect( color_t::hsv(2.0 / 3, 1, 1.) == color_t::blue );

  test_comment("From HSL float components");
  test_expect( color_t::hsl(0.0 / 3, 1, .5) == color_t::red );
  test_expect( color_t::hsl(1.0 / 3, 1, .5) == color_t::green );
  test_expect( color_t::hsl(2.0 / 3, 1, .5) == color_t::blue );

  test_comment("Heatmap colors");
  test_expect( color_t::heatmap(0.0) == "#33003f" );
  test_expect( color_t::heatmap(0.1) == "#1a0052" );
  test_expect( color_t::heatmap(0.2) == "#001066" );
  test_expect( color_t::heatmap(0.3) == "#004d79" );
  test_expect( color_t::heatmap(0.4) == "#008c7b" );
  test_expect( color_t::heatmap(0.5) == "#009f3f" );
  test_expect( color_t::heatmap(0.6) == "#0eb200" );
  test_expect( color_t::heatmap(0.7) == "#6ec500" );
  test_expect( color_t::heatmap(0.8) == "#d8d000" );
  test_expect( color_t::heatmap(0.9) == "#eb7100" );
  test_expect( color_t::heatmap(1.0) == "#ff0000" );
}

void test_draw()
{
  cvimage im(100, 100, cvimage::csp_rgb);
  if(!test_expect_cond(!im.empty() )) return;

  test_expect( !im.mat().empty() );  // cv:Mat access
  test_expect( im.width() == 100 );
  test_expect( im.height() == 100 );
  test_expect( im.area() == 100*100 );
  test_expect( im.channels() == 3 );
  test_expect( im.num_nonzero_pixels() == 0 );
  test_expect( im.is_rgb() );
  test_expect( im.color_at(0,0) == color_t::black );
  test_comment("R" << (int) im.color_at(0,0).r() << ", " <<
               "G" << (int) im.color_at(0,0).g() << ", " <<
               "B" << (int) im.color_at(0,0).b() << ", " <<
               "A" << (int) im.color_at(0,0).a()
              );
  // channel extraction
  test_expect( im.channel_red().num_nonzero_pixels() == 0 );
  test_expect( im.channel_blue().num_nonzero_pixels() == 0 );
  test_expect( im.channel_green().num_nonzero_pixels() == 0 );

 // point draw, range check
  test_expect_noexcept( im.draw(point_t(50 , 50), color_t::white, 1) ); // ok
  test_expect_noexcept( im.draw(point_t( -1, -1), color_t::white, 1) ); // no effect
  test_expect_noexcept( im.draw(point_t( -1,  0), color_t::white, 1) ); // no effect
  test_expect_noexcept( im.draw(point_t(  0, -1), color_t::white, 1) ); // no effect
  test_expect_noexcept( im.draw(point_t(100,100), color_t::white, 1) ); // no effect
  test_expect_noexcept( im.draw(point_t(  0,100), color_t::white, 1) ); // no effect
  test_expect_noexcept( im.draw(point_t(100,  0), color_t::white, 1) ); // no effect
  test_expect( im.num_nonzero_pixels() == 1);

  // get color, point draw range check
  test_expect( im.color_at( 50, 50) == color_t::white);
  test_expect( im.color_at( -1,  0) == color_t::black);
  test_expect( im.color_at(  0, -1) == color_t::black);
  test_expect( im.color_at(100,  0) == color_t::black);
  test_expect( im.color_at(  0,100) == color_t::black);
  test_expect( !im.color_at( -1,  0));
  test_expect( !im.color_at(  0, -1));
  test_expect( !im.color_at(100,  0));
  test_expect( !im.color_at(  0,100));
  test_expect_noexcept( im.draw(rect_t(25,25,75,75), color_t::blue, 1) );
  test_expect( im.color_at(25, 25) == color_t::blue);
  test_expect_noexcept( im.draw(circle_t(50, 50, 50), color_t::red, 1) );
  test_expect( im.color_at(50,  0) == color_t::red);
  test_expect( im.color_at( 0, 50) == color_t::red);

  // fill (1)
  test_expect_noexcept( im.fill("#111") ); // whole image
  test_expect_noexcept( im.fill(circle_t(75, 25, 10), "#333") ); // circle
  test_expect_noexcept( im.fill(rect_t(25,25,10,10), "#555") ); // rect
  test_expect_noexcept( im.fill(ellipse_t(25, 75, 10, 20, -45), "#777") ); // ellipse
  test_expect_noexcept( im.fill(rotated_rect_t(75, 75, 10, 5, 10/*deg*/), "#999") ); // rotated rect

  polynom_t ply;
  ply.push_back(point_t(50,50));
  ply.push_back(point_t(50,60));
  ply.push_back(point_t(60,60));
  ply.push_back(point_t(60,70));
  ply.push_back(point_t(70,70));

  contour_t cnt;
  cnt.push_back(point_t(50,50));
  cnt.push_back(point_t(50,40));
  cnt.push_back(point_t(40,40));
  cnt.push_back(point_t(40,30));
  cnt.push_back(point_t(30,30));

  test_expect_noexcept( im.fill(ply, "#880") );
  test_expect_noexcept( im.draw(ply, "#fff", 1) );
  test_expect_noexcept( im.fill(cnt, "#088") );
  test_expect_noexcept( im.draw(cnt, "#fff", 1) );

  test_expect_noexcept( im.draw(line_t(0,0,0,100), "#fff") );
  test_expect_noexcept( im.draw(ellipse_t(50, 50, 100, 50, 45), color_t::red, 1) );
  test_expect_noexcept( im.draw(rotated_rect_t(50, 50, 50, 20, 45/*deg*/), "#777", 1) );

  test_expect_noexcept( im.draw(cvimage(im), rect_t(50,50,99,99)) );
  test_expect_noexcept( im.floodfill(point_t(0,0), "#ffff00", color_t(20) /*tolerance*/) );

  #if defined (__linux) && defined(DEBUG)
  test_expect_noexcept( im.save("/tmp/testimg.png") );
  #endif
}

void test_conv()
{
  cvimage im(50, 50, cvimage::csp_rgb);
  im.fill(color_t::red);
  test_expect( im.color_at(0,0) == color_t::red );
  test_expect( im.color_at(1,1) == color_t::red );
  test_expect_noexcept ( im.to_hsv() );
  test_expect( im.color_at(0,0) == "#ffff00" ); // hue=00, sat=ff, val=ff
  test_expect_noexcept ( im.to_rgb() );
  test_expect( im.color_at(0,0) == "#ff0000" ); // r=ff, g=00, b=00
  test_expect_noexcept ( im.to_grayscale() );
  test_expect( im.color_at(0,0) == "#4c4c4c" ); // 0xffffff / 3
}

void test_getters()
{
  cvimage im(100, 50, cvimage::csp_rgb);
  unsigned n = 0;
  for(int c=0; c<im.width(); c+=2) {
    for(int r=0; r<im.height(); r++, ++n) {
      im.draw(point_t(c, r), color_t::white);
      ;
    }
  }
  color_t im_min, im_max, im_mean;
  test_expect( im.num_nonzero_pixels() == n );
  test_expect_noexcept( im.negate() );
  test_expect( im.num_nonzero_pixels() == im.area()-n );
  test_expect_noexcept( im.negate() );
  test_expect_noexcept( im_min  = im.min()  );
  test_expect_noexcept( im_max  = im.max()  );
  test_expect_noexcept( im_mean = im.mean() );
  test_comment("image min  = " << im_min    );
  test_comment("image max  = " << im_max    );
  test_comment("image mean = " << im_mean   );
  test_comment("image mean.r = " << (int)im_mean.r());
  test_comment("image mean.g = " << (int)im_mean.g());
  test_comment("image mean.b = " << (int)im_mean.b());
  test_comment("image mean.a = " << (int)im_mean.a());
  test_expect( im_min  == "#000" );
  test_expect( im_max  == "#fff" );
  test_expect( im_mean == "#7f7f7f" );
  test_expect( im_mean.r() >= 126 && im_mean.r() <= 128 );
  test_expect( im_mean.g() >= 126 && im_mean.g() <= 128 );
  test_expect( im_mean.b() >= 126 && im_mean.b() <= 128 );
  test_expect( im_mean.a() == 255 || im_mean.a() == 0   );
  {
    unsigned long n=0,r=0,g=0,b=0,a=0, sum;
    for(int i=0; i<im.width(); i++) {
      for(int j=0; j<im.height(); j++) {
        color_t col = im.color_at(i,j);
        n ++;
        r += col.r();
        g += col.g();
        b += col.b();
        a += col.a();
      }
    }
    sum = r+g+b;
    test_comment("num px= " << n );
    test_comment("acc px = " << sum );
    test_comment("mean R = " << (r/=n) );
    test_comment("mean G = " << (g/=n) );
    test_comment("mean B = " << (b/=n) );
    test_comment("mean A = " << (a/=n) );
    test_expect( (int)im_mean.r() >= (int)r-1 && (int)im_mean.r() <= (int)r+1 );
    test_expect( (int)im_mean.g() >= (int)g-1 && (int)im_mean.g() <= (int)g+1 );
    test_expect( (int)im_mean.b() >= (int)b-1 && (int)im_mean.b() <= (int)b+1 );
    test_expect( (int)im_mean.a() >= (int)a-1 && (int)im_mean.a() <= (int)a+1 );
  }
  // Split channels
  test_expect_noexcept(
    im.fill(color_t::black)
      .draw(point_t(2,0), color_t::red).draw(point_t(2,1), color_t::red).draw(point_t(2,2), color_t::red)
      .draw(point_t(1,0), color_t::green).draw(point_t(1,1), color_t::green)
      .draw(point_t(0,0), color_t::blue)
  );
  {
    cvimage::mat_vector_t channels = im.split_channels();
    test_expect( cvimage(channels[0]).num_nonzero_pixels() == 3 ); // R
    test_expect( cvimage(channels[1]).num_nonzero_pixels() == 2 ); // G
    test_expect( cvimage(channels[2]).num_nonzero_pixels() == 1 ); // B
  }
  test_expect( im.bounding_rect() == rect_t(0,0, 2,2) );
}

void test_modification()
{
  cvimage im(100,100, cvimage::csp_rgb);
  test_expect_noexcept(im.draw(rect_t(9,9,20,20)));
  test_expect_noexcept( im.crop(rect_t(10,10,19,19)) );
  test_expect(im.num_nonzero_pixels() == 0);
  test_comment("image size now:" << im.size());
  test_expect(im.width() == 10 && im.height() == 10);

  test_expect_noexcept( im.extend(1, cvimage::em_color, color_t::white) );
  test_expect(im.width() == 12 && im.height() == 12);
  test_expect(im.num_nonzero_pixels() == 4*11);

  test_expect_noexcept( im = im.subimage(rect_t(0,0,4,4)) );
  test_expect(im.width() == 5 && im.height() == 5);
  test_expect(im.num_nonzero_pixels() == 2*5-1);

  test_expect_noexcept( im.erode() );
  test_expect(im.num_nonzero_pixels() <= 1);

  test_expect_noexcept( im.fill(color_t::black).draw(point_t(2,2), color_t::white) );
  test_expect(im.num_nonzero_pixels() == 1);
  test_expect_noexcept( im.dilate() );
  test_expect(im.num_nonzero_pixels() >2 && im.num_nonzero_pixels() <= 9);
  test_expect_noexcept( im.dilate(10) );
  test_expect(im.num_nonzero_pixels() == im.area() );

  // resize / resample
  test_expect_noexcept( im.resample_up() );
  test_expect(im.size() == point_t(10,10) );
  test_expect_noexcept( im.resample_down() );
  test_expect(im.size() == point_t(5,5) );
  test_expect_noexcept( im.resize(20,20) );
  test_expect(im.size() == point_t(20,20) );
  test_expect_noexcept( im.resize_scale(2) );
  test_expect(im.size() == point_t(40,40) );
  test_expect_noexcept( im.resize_stretch(20,10) );
  test_expect(im.size() == point_t(20,10) );

  // noexcept checks
  test_expect_noexcept( im.blur() );
  test_expect_noexcept( im.blur(1) );
  test_expect_noexcept( im.blur(1, cvimage::blur_box ) );
  test_expect_noexcept( im.blur(1, cvimage::blur_gauss ) );
  test_expect_noexcept( im.blur(1, cvimage::blur_median ) );
  test_expect_noexcept( im.gradient() );
  test_expect_noexcept( im.gradient(2) );
  test_expect_noexcept( im.laplacian() );
  test_expect_noexcept( im.laplacian(1) );
  test_expect_noexcept( im.scharr(1,0) );
  test_expect_noexcept( im.scharr(0,1) );
  test_expect_noexcept( im.sobel(1,1) );
  test_expect_noexcept( im.sobel(0,1) );
  test_expect_noexcept( im.sobel(1,0) );
  test_expect_noexcept( im.sobel(1,0,2) );
  test_expect_noexcept( im.level() );
  test_expect_noexcept( im.level(0.1,0.9) );
  test_expect_noexcept( im.normalize(0.1,0.9) );
  test_expect_noexcept( im.invert() );
}

void test_contours()
{
  cvimage im(128,128, cvimage::csp_gray);
  im.fill(circle_t(64,64,32), color_t::white);
  im.fill(circle_t(64,64,16), color_t::black);
  // only outmost contours
  {
    contour_list_t cnt = im.contours(true);
    if(test_expect_cond( cnt.size() == 1 )) {
      rect_t rct;
      test_expect_noexcept( rct = cnt.front().bounding_rect() );
      test_expect( rct.tl() >= point_t(31,31) && rct.br() <= point_t(97,97) );
    }
  }
  // inner and outer in one list
  {
    contour_list_t cnt = im.contours(false);
    if(test_expect_cond( cnt.size() == 2 )) {
      rect_t rct;
      test_expect_noexcept( rct = cnt[0].bounding_rect() );
      test_expect(
        (rct.tl() >= point_t(31,31) && rct.br() <= point_t(97,97)) ||
        (rct.tl() >= point_t(46,46) && rct.br() <= point_t(82,82))
      );
      test_expect_noexcept( rct = cnt[1].bounding_rect() );
      test_expect(
        (rct.tl() >= point_t(31,31) && rct.br() <= point_t(97,97)) ||
        (rct.tl() >= point_t(46,46) && rct.br() <= point_t(82,82))
      );
    }
  }
  // contour comp (inner, outer)
  {
    contour_ccomp_list_t cnt = im.contours_ccomp();
    if(test_expect_cond( cnt.inner.size() == 1 && cnt.outer.size() == 1 )) {
      rect_t rct;
      test_expect_noexcept( rct = cnt.inner.front().bounding_rect() );
      test_expect( rct.tl() >= point_t(46,46) && rct.br() <= point_t(82,82));
      test_expect_noexcept( rct = cnt.outer.front().bounding_rect() );
      test_expect(rct.tl() >= point_t(31,31) && rct.br() <= point_t(97,97));
    }
  }
  // contour tree
  {
    contour_nested_list_t cnt = im.contours_tree();
    if(test_expect_cond( !cnt.has_parent() && !cnt.empty() )) { // the whole image
      rect_t rct;
      test_expect_noexcept( rct = cnt.val().bounding_rect() );
      test_expect(rct.tl() == point_t(0,0) && rct.br() == point_t(im.width()-1,im.height()-1));
      if(test_expect_cond( cnt.has_children() && cnt.children().size() == 1 )) { // outer circle
        cnt = cnt.children().front();
        test_expect_noexcept( rct = cnt.val().bounding_rect() );
        test_expect(rct.tl() >= point_t(31,31) && rct.br() <= point_t(97,97));
        if(test_expect_cond( cnt.has_children() && cnt.children().size() == 1 )) { // inner circle
          cnt = cnt.children().front();
          test_expect_noexcept( rct = cnt.val().bounding_rect() );
          test_expect( rct.tl() >= point_t(46,46) && rct.br() <= point_t(82,82));
        }
      }
      test_comment(rct.tl() << "/" << rct.br() );
    }
  }
}

void test_detectors()
{
  cvimage imo(128,128, cvimage::csp_gray);
  test_expect_noexcept(
    imo.fill(rect_t(point_t(32,32), 64,64), color_t::white)
       .fill(rect_t(point_t(56,56), 16,16), color_t::black)
  );
  {
    cvimage im(imo);
    test_expect_noexcept( im.corners_harris() );
    test_expect( im.max() == color_t::white );
    test_expect( im.min() == color_t::black );
    test_expect_noexcept ( im.threshold(color_t(im.max())) );
    test_expect( im.num_nonzero_pixels() == 8 );
  }
  {
    cvimage im(imo);
    test_expect_noexcept( im.edges_canny().threshold() );
    test_expect( im.num_nonzero_pixels() >= (4*62+4*14) && im.num_nonzero_pixels() <= (4*64+4*16) );
  }
  {
    cvimage im(imo);
    test_expect_noexcept(im.fill(color_t::black).fill(circle_t(30,30,25), color_t::white));
    test_expect_noexcept(im.fill(circle_t(80,80,25), color_t::white).to_grayscale());
    cvimage::circle_list_t circles;
    test_expect_noexcept( circles = im.blur(5, cvimage::blur_gauss).hough_circles(30,20));
    if(test_expect_cond( circles.size() == 2 )) {
      test_expect( circles.front().center() >= point_t(29,29) && circles.front().center() <= point_t(31,31));
      test_expect( circles.front().r() <=27 && circles.front().r() >=22 );
      test_expect( circles.back().center() >= point_t(79,79) && circles.back().center() <= point_t(81,81));
      test_expect( circles.back().r() <=27 && circles.back().r() >=22 );
    }
  }
}

void test()
{
  test_load_save();
  test_color();
  test_draw();
  test_conv();
  test_getters();
  test_modification();
  test_contours();
  test_detectors();
}
