/* -*- 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 <boost/filesystem/path.hpp>

#include <mia/core/attribute_names.hh>
#include <mia/core/msgstream.hh>
#include <mia/2d/imageio.hh>
#include <mia/2d/rgbimageio.hh>

NS_MIA_USE
using namespace std;
using namespace boost::unit_test;
namespace bfs = ::boost::filesystem;

static P2DImage create_test_image(int acquisition, int instance, const string& protocol)
{
       C2DSIImage *image = new C2DSIImage(C2DBounds(1, 2));
       P2DImage result(image);
       (*image)(0, 0) = acquisition;
       (*image)(0, 1) = instance;
       image->set_attribute(IDProtocolName, protocol);
       image->set_attribute(IDAcquisitionNumber, PAttribute(new CIntAttribute(acquisition)));
       image->set_attribute(IDSliceLocation, PAttribute(new CFloatAttribute(instance)));
       return result;
}

BOOST_AUTO_TEST_CASE( test_load_series )
{
       C2DImageIOPluginHandler::Instance::Data test_image_list1;
       test_image_list1.push_back(create_test_image(1, 0, "proto1"));
       test_image_list1.push_back(create_test_image(1, 0, "proto2"));
       test_image_list1.push_back(create_test_image(1, 2, "proto1"));
       test_image_list1.push_back(create_test_image(1, 3, "proto1"));
       test_image_list1.push_back(create_test_image(2, 0, "proto2"));
       test_image_list1.push_back(create_test_image(1, 0, "proto3"));
       C2DImageIOPluginHandler::Instance::Data test_image_list2;
       test_image_list2.push_back(create_test_image(2, 4, "proto1"));
       test_image_list2.push_back(create_test_image(1, 1, "proto2"));
       test_image_list2.push_back(create_test_image(1, 5, "proto1"));
       test_image_list2.push_back(create_test_image(1, 6, "proto1"));
       test_image_list2.push_back(create_test_image(3, 2, "proto2"));
       test_image_list2.push_back(create_test_image(1, 1, "proto3"));
       BOOST_REQUIRE(C2DImageIOPluginHandler::instance().save("test0.@", test_image_list1));
       BOOST_REQUIRE(C2DImageIOPluginHandler::instance().save("test1.@", test_image_list2));
       vector<string> filenames = {"test0.@", "test1.@"};
       auto images = load_image_series(filenames);
       // test three protocol types
       BOOST_CHECK_EQUAL(images.size(), 3u);
       // check first protocol
       auto proto1 = images.find("proto1");
       BOOST_REQUIRE(proto1 !=  images.end());
       // proto 1 has 2 acquisitions
       BOOST_CHECK_EQUAL(proto1->second.size(), 2u);
       BOOST_CHECK_EQUAL(proto1->second[0].size(), 5u);
       BOOST_CHECK_EQUAL(proto1->second[1].size(), 1u);
       auto proto2 = images.find("proto2");
       BOOST_REQUIRE(proto2 !=  images.end());
       BOOST_CHECK_EQUAL(proto2->second.size(), 3u);
       BOOST_CHECK_EQUAL(proto2->second[0].size(), 2u);
       BOOST_CHECK_EQUAL(proto2->second[1].size(), 1u);
       BOOST_CHECK_EQUAL(proto2->second[2].size(), 1u);
       auto proto3 = images.find("proto3");
       BOOST_REQUIRE(proto3 !=  images.end());
       BOOST_CHECK_EQUAL(proto3->second.size(), 1u);
       BOOST_CHECK_EQUAL(proto3->second[0].size(), 2u);
       BOOST_CHECK(images.find("proto4") ==  images.end());
}


BOOST_AUTO_TEST_CASE( test_load_bmp_8_uncompressed )
{
       string filename(MIA_SOURCE_ROOT"/testdata/gray2x3.bmp");
       auto test_image = load_image2d(filename);
       const C2DUBImage& img = dynamic_cast<const C2DUBImage&>(*test_image);
       BOOST_CHECK_EQUAL(img.get_size().x, 2u);
       BOOST_CHECK_EQUAL(img.get_size().y, 3u);
       BOOST_CHECK_EQUAL(img(0, 0),   0u);
       BOOST_CHECK_EQUAL(img(1, 0),  63u);
       BOOST_CHECK_EQUAL(img(0, 1), 128u);
       BOOST_CHECK_EQUAL(img(1, 1), 190u);
       BOOST_CHECK_EQUAL(img(0, 2), 229u);
       BOOST_CHECK_EQUAL(img(1, 2), 255u);
       save_image("test_image-8u.bmp", test_image);
       auto test2_image = load_image2d("test_image-8u.bmp");
       const C2DUBImage& img2 = dynamic_cast<const C2DUBImage&>(*test2_image);
       BOOST_CHECK_EQUAL(img2.get_size().x, 2u);
       BOOST_CHECK_EQUAL(img2.get_size().y, 3u);
       BOOST_CHECK_EQUAL(img2(0, 0),   0u);
       BOOST_CHECK_EQUAL(img2(1, 0),  63u);
       BOOST_CHECK_EQUAL(img2(0, 1), 128u);
       BOOST_CHECK_EQUAL(img2(1, 1), 190u);
       BOOST_CHECK_EQUAL(img2(0, 2), 229u);
       BOOST_CHECK_EQUAL(img2(1, 2), 255u);
       unlink("test_image-8u.bmp");
}

BOOST_AUTO_TEST_CASE( test_load_bmp_8_compressed )
{
       auto test_image = load_image2d(MIA_SOURCE_ROOT"/testdata/gray100x2c.bmp");
       const C2DUBImage& img = dynamic_cast<const C2DUBImage&>(*test_image);
       BOOST_CHECK_EQUAL(img.get_size().x, 100u);
       BOOST_CHECK_EQUAL(img.get_size().y, 2u);
       auto p = img.begin();

       for (unsigned x = 0; x < 52; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 163u);

       for (unsigned x = 52; x < 100; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 139u);

       for (unsigned x = 0; x < 47; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 100u);

       for (unsigned x = 47; x < 100; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 211u);
}

BOOST_AUTO_TEST_CASE( test_load_bmp_4bit_compressed )
{
       auto test_image = load_image2d(MIA_SOURCE_ROOT"/testdata/gray100x2c-4bit.bmp");
       const C2DUBImage& img = dynamic_cast<const C2DUBImage&>(*test_image);
       BOOST_CHECK_EQUAL(img.get_size().x, 100u);
       BOOST_CHECK_EQUAL(img.get_size().y, 2u);
       auto p = img.begin();

       for (unsigned x = 0; x < 52; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 2u);

       BOOST_CHECK_EQUAL(*p++, 1u);
       BOOST_CHECK_EQUAL(*p++, 3u);
       BOOST_CHECK_EQUAL(*p++, 1u);
       BOOST_CHECK_EQUAL(*p++, 0u);
       BOOST_CHECK_EQUAL(*p++, 1u);
       BOOST_CHECK_EQUAL(*p++, 0u);

       for (unsigned x = 58; x < 100; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 1u);

       for (unsigned x = 0; x < 47; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 0u);

       for (unsigned x = 47; x < 100; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 3u);
}

BOOST_AUTO_TEST_CASE( test_load_bmp_4bit_uncompressed )
{
       auto test_image = load_image2d(MIA_SOURCE_ROOT"/testdata/gray100x2uc-4bit.bmp");
       const C2DUBImage& img = dynamic_cast<const C2DUBImage&>(*test_image);
       BOOST_CHECK_EQUAL(img.get_size().x, 100u);
       BOOST_CHECK_EQUAL(img.get_size().y, 2u);
       auto p = img.begin();

       for (unsigned x = 0; x < 52; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 2u);

       for (unsigned x = 52; x < 100; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 1u);

       for (unsigned x = 0; x < 47; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 0u);

       for (unsigned x = 47; x < 100; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 3u);
}

BOOST_AUTO_TEST_CASE( test_load_save_bmp_1bit_uc )
{
       auto test_image = load_image2d(MIA_SOURCE_ROOT"/testdata/binary100x2uc.bmp");
       const C2DBitImage& img = dynamic_cast<const C2DBitImage&>(*test_image);
       BOOST_CHECK_EQUAL(img.get_size().x, 100u);
       BOOST_CHECK_EQUAL(img.get_size().y, 2u);
       auto p = img.begin();

       for (unsigned x = 0; x < 55; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 0u);

       BOOST_CHECK_EQUAL(*p++, 1u);
       BOOST_CHECK_EQUAL(*p++, 0u);
       BOOST_CHECK_EQUAL(*p++, 1u);

       for (unsigned x = 58; x < 100; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 0u);

       for (unsigned x = 0; x < 47; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 1u);

       for (unsigned x = 47; x < 100; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 0u);

       save_image("test_image-bit.bmp", test_image);
       auto test2_image = load_image2d("test_image-bit.bmp");
       const C2DBitImage& img2 = dynamic_cast<const C2DBitImage&>(*test2_image);
       BOOST_CHECK_EQUAL(img2.get_size().x, 100u);
       BOOST_CHECK_EQUAL(img2.get_size().y, 2u);
       p = img2.begin();

       for (unsigned x = 0; x < 55; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 0u);

       BOOST_CHECK_EQUAL(*p++, 1u);
       BOOST_CHECK_EQUAL(*p++, 0u);
       BOOST_CHECK_EQUAL(*p++, 1u);

       for (unsigned x = 58; x < 100; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 0u);

       for (unsigned x = 0; x < 47; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 1u);

       for (unsigned x = 47; x < 100; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 0u);

       unlink("test_image-bit.bmp");
}

BOOST_AUTO_TEST_CASE( test_load_bmp_8_compressed_with_jumps )
{
       // this test should check whether the jump inside a bitmap file is propperly
       // executed, but currently the image is not encoded with such a jump.
       auto test_image = load_image2d(MIA_SOURCE_ROOT"/testdata/gray20x20c.bmp");
       const C2DUBImage& img = dynamic_cast<const C2DUBImage&>(*test_image);
       BOOST_CHECK_EQUAL(img.get_size().x, 20u);
       BOOST_CHECK_EQUAL(img.get_size().y, 20u);
       auto p = img.begin();
       unsigned x;

       for (x = 0; x < 9; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 99u);

       for (; x < 8 * 20 + 6; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 0u);

       for (; x < 9 * 20; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 121u);

       for (; x < 14 * 20; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 0u);

       for (; x < 14 * 20 + 10; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 138u);

       for (; x < 20 * 20; ++x, ++p)
              BOOST_CHECK_EQUAL(*p, 0u);
}

BOOST_AUTO_TEST_CASE( test_load_save_rgb8_bmp )
{
       vector<unsigned char> test_data{206, 89, 97, 71, 99, 67, 192, 205, 52,
                                       28, 31, 98, 94, 27, 204, 232, 18, 214};
       const auto& io = C2DRGBImageIOPluginPluginHandler::instance();
       auto test_image = io.load(MIA_SOURCE_ROOT"/testdata/rgb3x2-24bit.bmp");
       const CRGB2DImage& img = *test_image;
       BOOST_CHECK_EQUAL(img.get_size().x, 3);
       BOOST_CHECK_EQUAL(img.get_size().y, 2);
       auto pixels = img.pixel();

       for (int i = 0; i < 18; ++i) {
              cvdebug() << i << ":" <<  (int)pixels[i] << " exp " << (int)test_data[i] << "\n";
              BOOST_CHECK_EQUAL(pixels[i], test_data[i]);
       }

       BOOST_REQUIRE(save_image("test_image_rgb.bmp", *test_image));
       auto test_image2 = io.load("test_image_rgb.bmp");
       const CRGB2DImage& img2 = *test_image2;
       BOOST_CHECK_EQUAL(img2.get_size().x, 3);
       BOOST_CHECK_EQUAL(img2.get_size().y, 2);
       pixels = img2.pixel();

       for (int i = 0; i < 18; ++i) {
              BOOST_CHECK_EQUAL(pixels[i], test_data[i]);
       }

       unlink("test_image_rgb.bmp");
}
