/* -*- 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/>.
 *
 */

#define BOOST_TEST_DYN_LINK

#include <climits>
#include <stdexcept>
#include <iostream>
#include <ostream>
#include <stdexcept>
#include <climits>

#include <mia/internal/autotest.hh>


#include <mia/core/attributes.hh>
#include <mia/core/msgstream.hh>

#include <boost/mpl/vector.hpp>

NS_MIA_USE
using namespace std;
using namespace boost::unit_test;
namespace bmpl = boost::mpl;

template <typename T>
ostream& operator << (ostream& os, const vector<T>& v)
{
       os << "vector(" << v.size() << ") = [";
       copy(v.begin(), v.end(), ostream_iterator<T>(os, ", "));
       os << "]";
       return os;
}


template <typename T>
void check_translate_type(const string& key, T value, string svalue)
{
       cvdebug() << "check_translate_type: key=" << key << ", value=" << value << ", svalue='" << svalue << endl;
       PAttribute attr =  CStringAttrTranslatorMap::instance().to_attr(key, svalue);
       BOOST_REQUIRE(attr);
       const TAttribute<T> *test_value = dynamic_cast<const TAttribute<T> *>(attr.get());

       if (!test_value)  {
              cverr() << "returned type is " << typeid(*attr.get()).name() << "\n";
              cverr() << "expected type is " << typeid(T).name() << "\n";
              return;
       }

       BOOST_REQUIRE(test_value);
       T v = *test_value;
       BOOST_CHECK_EQUAL(v, value);
       BOOST_CHECK_EQUAL(attr->as_string(), svalue);
}

template <typename T>
void check_translate_type(const string& key, const vector<T>& value, string svalue)
{
       //cvdebug() << "check_translate_type: key=" << key << ", value="<< value << ", svalue='" << svalue<<endl;
       PAttribute attr =  CStringAttrTranslatorMap::instance().to_attr(key, svalue);
       BOOST_REQUIRE(attr);
       const TAttribute<vector<T>> *test_value = dynamic_cast<const TAttribute<vector<T>> *>(attr.get());

       if (!test_value)  {
              cverr() << "returned type is " << typeid(*attr.get()).name() << "\n";
              cverr() << "expected type is " << typeid(const TAttribute<vector<T>> *).name() << "\n";
              return;
       }

       BOOST_REQUIRE(test_value);
       vector<T> v = *test_value;
       BOOST_CHECK_EQUAL(v.size(), value.size());
       BOOST_REQUIRE(v.size() == value.size());
       BOOST_CHECK(equal(v.begin(), v.end(), value.begin()));
       BOOST_CHECK_EQUAL(attr->as_string(), svalue);
}


BOOST_AUTO_TEST_CASE( test_translator )
{
       CUSTranslator::register_for("ushort");
       CSSTranslator::register_for("sshort");
       CUBTranslator::register_for("ubyte");
       CSBTranslator::register_for("sbyte");
       CDoubleTranslator::register_for("double");
       CFloatTranslator::register_for("float");
       CBitTranslator::register_for("bit");
       // registering the key to the same result type is allowed
       BOOST_CHECK_NO_THROW(CBitTranslator::register_for("bit"));
       // registering the key to a different result type will throw
       BOOST_CHECK_THROW(CUSTranslator::register_for("bit"), invalid_argument);
       check_translate_type("bit", true, "1");
       check_translate_type("double", 1.8, "1.8");
       check_translate_type("float", 1.75f, "1.75");
       check_translate_type("ubyte", (uint8_t)129, "129");
       check_translate_type("sbyte", (int8_t) - 120, "-120");
       check_translate_type("sshort", (int16_t) - 1231, "-1231");
       check_translate_type("ushort", (uint16_t)3213, "3213");
       CULTranslator::register_for("ulong");
       CSLTranslator::register_for("slong");
       check_translate_type("slong", (int64_t) - 1212321, "-1212321");
       check_translate_type("ulong", (uint64_t)1238763, "1238763");
}

BOOST_AUTO_TEST_CASE( test_vector_translator )
{
       CVUSTranslator::register_for("vushort");
       vector<unsigned short> values(3);
       values[0] = 0;
       values[1] = 1;
       values[2] = 3;
       check_translate_type("vushort", values, "3 0 1 3");
}

template <typename T>
void test_type_attribute()
{
       CAttributeMap attr_list;
       attr_list["zero"] = PAttribute(new TAttribute<T>(0));
       attr_list["one"] = PAttribute(new TAttribute<T>(1));
       TAttribute<T> *attr = dynamic_cast<TAttribute<T>*>(attr_list["zero"].get());
       BOOST_REQUIRE(attr);
       BOOST_REQUIRE(*attr == 0);
       attr = dynamic_cast<TAttribute<T>*>(attr_list["one"].get());
       BOOST_REQUIRE(attr);
       BOOST_REQUIRE(*attr == 1);
}



typedef bmpl::vector<bool,
        int8_t,
        uint8_t,
        int16_t,
        uint16_t,
        int32_t,
        uint32_t,
        int64_t,
        uint64_t,
        float,
        double
        > test_types;

BOOST_AUTO_TEST_CASE_TEMPLATE( test_type_attributes, T, test_types )
{
       test_type_attribute<T>();
}

BOOST_AUTO_TEST_CASE( test_attributes )
{
       CAttributeMap attr_list;
       string test_string(" test string");
       attr_list["int_attr"] = PAttribute(new CIntAttribute(1));
       attr_list["float_attr"] = PAttribute(new CFloatAttribute(1.0f));
       attr_list["string_attr"] = PAttribute(new CStringAttribute(test_string));
       CIntAttribute *int_attr = dynamic_cast<CIntAttribute *>(attr_list["int_attr"].get());
       BOOST_REQUIRE(int_attr);
       BOOST_CHECK(*int_attr == 1);
       CFloatAttribute *float_attr = dynamic_cast<CFloatAttribute *>(attr_list["float_attr"].get());
       BOOST_REQUIRE(float_attr);
       BOOST_CHECK(*float_attr == 1.0f);
       CStringAttribute *string_attr = dynamic_cast<CStringAttribute *>(attr_list["string_attr"].get());
       BOOST_REQUIRE(string_attr);
       string s =  *string_attr;
       BOOST_CHECK(test_string ==  s);
       CAttributeMap sub_list;
       sub_list["val1"] = PAttribute(new CIntAttribute(1));
       attr_list["sublist_attr"] = PAttribute(new CAttributeList(sub_list));
       CAttributedData data1(PAttributeMap(new CAttributeMap(attr_list)));
       int_attr = dynamic_cast<CIntAttribute *>(data1.get_attribute("int_attr").get());
       BOOST_REQUIRE(int_attr);
       BOOST_CHECK(*int_attr == 1);
       CAttributedData data2(data1);
       int_attr = dynamic_cast<CIntAttribute *>(data2.get_attribute("int_attr").get());
       BOOST_REQUIRE(int_attr);
       BOOST_CHECK(*int_attr == 1);
       int kk = get_attribute_as<int>(*data2.get_attribute("int_attr"));
       BOOST_CHECK_EQUAL(kk, 1);
       data2.set_attribute("int_attr", PAttribute(new CIntAttribute(2)));
       int_attr = dynamic_cast<CIntAttribute *>(data2.get_attribute("int_attr").get());
       BOOST_REQUIRE(int_attr);
       BOOST_CHECK(*int_attr == 2);
       int_attr = dynamic_cast<CIntAttribute *>(data1.get_attribute("int_attr").get());
       BOOST_REQUIRE(int_attr);
       BOOST_CHECK(*int_attr == 1);
       CAttributedData data3;
       BOOST_CHECK(!data3.get_attribute("int_attr"));
}


void  add_an_attribute(CAttributedData& data, const string& key, const string& value)
{
       data.set_attribute(key, CStringAttrTranslatorMap::instance().to_attr(key, value));
}

BOOST_AUTO_TEST_CASE( test_attributes_equal )
{
       PAttribute a(new TAttribute<float>(1.3f));
       PAttribute c(new TAttribute<float>(1.4f));
       PAttribute b(new TAttribute<double>(1.2f));
       BOOST_CHECK(a->is_equal(*a));
       BOOST_CHECK(!a->is_equal(*b));
       BOOST_CHECK(!a->is_equal(*c));
}

BOOST_AUTO_TEST_CASE( test_lists_equal )
{
       CAttributedData data1;
       CBitTranslator::register_for("bit0");
       CBitTranslator::register_for("bit1");
       CDoubleTranslator::register_for("double0");
       CDoubleTranslator::register_for("double1");
       add_an_attribute(data1, "bit0", "1");
       add_an_attribute(data1, "bit1", "0");
       add_an_attribute(data1, "double0", "1.12");
       add_an_attribute(data1, "double1", "1.43");
       add_an_attribute(data1, "string", "some string");
       CAttributedData data2(data1);
       BOOST_REQUIRE(data2 == data1);
       add_an_attribute(data1, "double0", "2.0");
       BOOST_CHECK(!(data2 == data1));
       data2 = data1;
       BOOST_REQUIRE(data2 == data1);
       add_an_attribute(data1, "funny", "funny");
       BOOST_CHECK(!(data2 == data1));
       data2 = data1;
       data1.set_attribute("type", PAttribute(new TAttribute<float>(1.5f)));
       data1.set_attribute("type", PAttribute(new TAttribute<double>(1.5f)));
       BOOST_CHECK(!(data2 == data1));
}


template <typename T>
void check_add_and_read(CAttributeMap& map, const char *name, T value, int id)
{
       typedef const TAttribute<T> *PAttr;
       add_attribute(map, name, value);
       PAttr attr = dynamic_cast<PAttr>(map[name].get());
       BOOST_REQUIRE(attr);
       T test_value = *attr;
       BOOST_CHECK_EQUAL(value, test_value);
       BOOST_CHECK_EQUAL(attr->type_id(), id);
}

template <>
void check_add_and_read(CAttributeMap& map, const char *name, const char *value, int id)
{
       typedef const TAttribute<string>& PAttr;
       add_attribute(map, name, value);
       cvdebug() << typeid(PAttr).name() << " vs " << typeid(*map[name].get()).name() << "\n";
       PAttr attr = dynamic_cast<PAttr>(*map[name].get());
       string test_value = attr;
       BOOST_CHECK_EQUAL(string(value), test_value);
       BOOST_CHECK_EQUAL(map[name]->type_id(), id);
}

BOOST_AUTO_TEST_CASE( test_add_attribute_int )
{
       CAttributeMap map;
       check_add_and_read(map, "int", 10, EAttributeType::attr_sint);
}

BOOST_AUTO_TEST_CASE( test_add_attribute_short )
{
       CAttributeMap map;
       check_add_and_read(map, "short", (short)20, EAttributeType::attr_sshort);
}
BOOST_AUTO_TEST_CASE( test_add_attribute_cstring )
{
       CAttributeMap map;
       check_add_and_read(map, "char*", "char*_value", EAttributeType::attr_string);
}
BOOST_AUTO_TEST_CASE( test_add_attribute_string )
{
       CAttributeMap map;
       check_add_and_read(map, "string", string("string_value"), EAttributeType::attr_string);
}
BOOST_AUTO_TEST_CASE( test_add_attribute_float )
{
       CAttributeMap map;
       check_add_and_read(map, "float", 1.0f, EAttributeType::attr_float);
}

BOOST_AUTO_TEST_CASE( test_add_attribute )
{
       CAttributeMap map;
       check_add_and_read(map, "double", 1.0, EAttributeType::attr_double);
}

BOOST_AUTO_TEST_CASE( test_attribute_data )
{
       CAttributedData data;
       BOOST_CHECK(!data.has_attribute("some"));
       data.set_attribute( "some", PAttribute(new TAttribute<float>(1.5f)));
       BOOST_CHECK(data.has_attribute("some"));
       BOOST_CHECK_EQUAL(data.get_attribute_as_string("some"), "1.5");
       CFloatTranslator::register_for("floatfromstring");
       data.set_attribute( "floatfromstring", "2.5");
       PAttribute floatfromstring = data.get_attribute("floatfromstring");
       BOOST_REQUIRE(floatfromstring);
       const TAttribute<float> *test_value = dynamic_cast<const TAttribute<float> *>(floatfromstring.get());
       BOOST_REQUIRE(test_value);
       float tv = *test_value;
       BOOST_CHECK_EQUAL(2.5f, tv);
       BOOST_CHECK_EQUAL(2.5f, data.get_attribute_as<float>("floatfromstring"));
       data.delete_attribute("floatfromstring");
       BOOST_CHECK(!data.has_attribute("floatfromstring"));
}

bool operator==(PAttribute const& a, PAttribute const& b)
{
       return *a == *b;
}

BOOST_AUTO_TEST_CASE( test_get_attribute_as_non_existent )
{
       CAttributedData data;
       BOOST_CHECK_THROW(data.get_attribute_as<int>("dummy"), invalid_argument);
}

BOOST_AUTO_TEST_CASE( test_get_attribute_as_wrong_type )
{
       CAttributedData data;
       data.set_attribute("dummy", PAttribute(new CFloatAttribute(1.0f)));
       BOOST_CHECK_THROW(data.get_attribute_as<int>("dummy"), bad_cast);
}


BOOST_AUTO_TEST_CASE( test_attribute_map_equal_test )
{
       CAttributeMap map1;
       map1["dummy"] = PAttribute(new CFloatAttribute(1.0f));
       CAttributeMap map2;
       map2["dummy"] = PAttribute(new CFloatAttribute(1.0f));
       BOOST_CHECK(map1 == map2);
       map1["bla"] = PAttribute(new CFloatAttribute(2.0f));
       map2["ble"] = PAttribute(new CFloatAttribute(2.0f));
       BOOST_CHECK(!(map1 == map2));
}

BOOST_AUTO_TEST_CASE( test_attribute_map_get_attribute_as_string_not_exist )
{
       CAttributedData data;
       auto s = data.get_attribute_as_string("test");
       BOOST_CHECK_EQUAL(s.size(), 0u);
}

BOOST_AUTO_TEST_CASE( Ensure_some_copy_on_write )
{
       CAttributedData data;
       data.set_attribute("dummy", PAttribute(new CFloatAttribute(2.0f)));
       CAttributedData data2(data);
       data.delete_attribute("dummy");
       BOOST_CHECK(!data.has_attribute("dummy"));
       BOOST_CHECK(data2.has_attribute("dummy"));
}

BOOST_AUTO_TEST_CASE( test_set_some_attribues )
{
       CAttributedData data;
       data.set_attribute("dummy", PAttribute(new CFloatAttribute(2.0f)));
       data.set_attribute("dummy2", PAttribute(new CIntAttribute(3)));
       CAttributedData data2;
       data2.set_attributes(data.begin_attributes(), data.end_attributes());
       BOOST_CHECK(data2.has_attribute("dummy"));
       BOOST_CHECK(data2.has_attribute("dummy2"));
}



