/*
 * These tests are of limited usefulness.  In fact, you might even say that
 * they're not really tests at all.  But I felt that it would be useful to have
 * some basic usage of most functions just to verify that things compile and
 * work generally
 */

#include <cfloat>
#include <stdexcept>
#include <iostream>
#include <boost/test/unit_test.hpp>
#include <boost/test/test_tools.hpp>
#include <boost/test/tools/floating_point_comparison.hpp>
using namespace boost::unit_test;
#include <cairomm/fontface.h>
#include <cairomm/scaledfont.h>
#include <cairomm/surface.h>
#include <cairomm/context.h>

using namespace Cairo;

// little utility helper classes
struct TestSetup
{
  TestSetup()
  {
    surface = ImageSurface::create(Cairo::Surface::Format::ARGB32, 100, 100);
    cr = Cairo::Context::create(surface);
  }

  RefPtr<Context> cr;
  RefPtr<Surface> surface;
};

// a no-op-render user font base class
class NullRenderUserFont : public UserFontFace
{
public:
  ErrorStatus
    render_glyph(const RefPtr<ScaledFont>& /*scaled_font*/,
                 unsigned long /*glyph*/,
                 const RefPtr<Context>& /*cr*/,
                 TextExtents& /*metrics*/) override
    { ++count_render_glyph; return CAIRO_STATUS_SUCCESS; }

  int count_render_glyph;

protected:
  NullRenderUserFont() : UserFontFace(), count_render_glyph(0) {}
};

/******************************
 * test_implement_text
 ******************************/
class ImplTextUserFont: public NullRenderUserFont
{
public:
  static RefPtr<ImplTextUserFont> create() { return make_refptr_for_instance<ImplTextUserFont>(new ImplTextUserFont());};
  ErrorStatus text_to_glyphs(const RefPtr<ScaledFont>& /*scaled_font*/,
                                     const std::string& /*utf8*/,
                                     std::vector<Glyph>& glyphs,
                                     std::vector<TextCluster>& /*clusters*/,
                                     TextClusterFlags& /*cluster_flags*/) override
  {
    ++count_text_to_glyphs;
    // return an arbitrary glyph
    Glyph g = {84, 0, 0};
    glyphs.push_back(g);
    return CAIRO_STATUS_SUCCESS;
  }
  int count_text_to_glyphs;

protected:
  ImplTextUserFont() : count_text_to_glyphs(0) {}
};

BOOST_AUTO_TEST_SUITE( Cairo_UserFontFace )

BOOST_AUTO_TEST_CASE(test_implement_text)
{
  TestSetup setup;
  auto font = ImplTextUserFont::create();
  setup.cr->set_font_face(font);
  setup.cr->show_text("hello");
  BOOST_REQUIRE(font->count_text_to_glyphs > 0);
  BOOST_REQUIRE(font->count_render_glyph > 0);
}

/******************************
 * test_implement_unicode
 ******************************/
class ImplUnicodeUserFont: public NullRenderUserFont
{
public:
  static RefPtr<ImplUnicodeUserFont> create() { return make_refptr_for_instance<ImplUnicodeUserFont>(new ImplUnicodeUserFont());};
  ErrorStatus unicode_to_glyph(const RefPtr<ScaledFont>& /*scaled_font*/,
                                       unsigned long /*unicode*/,
                                       unsigned long& /*glyph*/) override
  { ++count_unicode_to_glyph;  return CAIRO_STATUS_SUCCESS;}
  int count_unicode_to_glyph;

protected:
  ImplUnicodeUserFont() : NullRenderUserFont(), count_unicode_to_glyph(0) {}
};

BOOST_AUTO_TEST_CASE(test_implement_unicode)
{
  TestSetup setup;
  auto font = ImplTextUserFont::create();
  setup.cr->set_font_face(font);
  setup.cr->show_text("hello");
  BOOST_REQUIRE(font->count_text_to_glyphs > 0);
  BOOST_REQUIRE(font->count_render_glyph > 0);
}

/******************************
 * test_implement_both
 ******************************/
class ImplBothUserFont: public NullRenderUserFont
{
public:
  static RefPtr<ImplBothUserFont> create() { return make_refptr_for_instance<ImplBothUserFont>(new ImplBothUserFont());};
  ErrorStatus unicode_to_glyph(const RefPtr<ScaledFont>& /*scaled_font*/,
                                       unsigned long /*unicode*/,
                                       unsigned long& /*glyph*/) override
  { ++count_unicode_to_glyph;  return CAIRO_STATUS_SUCCESS;}
  int count_unicode_to_glyph;

  ErrorStatus text_to_glyphs(const RefPtr<ScaledFont>& /*scaled_font*/,
                                     const std::string& /*utf8*/,
                                     std::vector<Glyph>& glyphs,
                                     std::vector<TextCluster>& /*clusters*/,
                                     TextClusterFlags& /*cluster_flags*/) override
  {
    ++count_text_to_glyphs;
    // return an arbitrary glyph
    Glyph g = {84, 0, 0};
    glyphs.push_back(g);
    return CAIRO_STATUS_SUCCESS;
  }
  int count_text_to_glyphs;

protected:
  ImplBothUserFont() : NullRenderUserFont(), count_unicode_to_glyph(0),
  count_text_to_glyphs(0) {}
};

BOOST_AUTO_TEST_CASE(test_implement_both)
{
  TestSetup setup;
  auto font = ImplBothUserFont::create();
  setup.cr->set_font_face(font);
  setup.cr->show_text("hello");
  // text_to_glyphs should take precedence
  BOOST_REQUIRE(font->count_text_to_glyphs > 0);
  BOOST_REQUIRE(font->count_unicode_to_glyph == 0);
  BOOST_REQUIRE(font->count_render_glyph > 0);
}

/******************************
 * test_implement_neither
 ******************************/
class ImplNeitherUserFont: public NullRenderUserFont
{
public:
  static RefPtr<ImplNeitherUserFont> create() { return make_refptr_for_instance<ImplNeitherUserFont>(new ImplNeitherUserFont());};

protected:
  ImplNeitherUserFont() : NullRenderUserFont() {}
};

BOOST_AUTO_TEST_CASE(test_implement_neither)
{
  TestSetup setup;
  auto font = ImplNeitherUserFont::create();
  setup.cr->set_font_face(font);
  setup.cr->show_text("hello");
  BOOST_REQUIRE(font->count_render_glyph > 0);
}

/******************************
 * test_implement_init
 ******************************/
class ImplInitUserFont: public NullRenderUserFont
{
public:
  static RefPtr<ImplInitUserFont> create() { return make_refptr_for_instance<ImplInitUserFont>(new ImplInitUserFont());};
  ErrorStatus init(const RefPtr<ScaledFont>& /*scaled_font*/,
                           const RefPtr<Context>& /*cr*/,
                           FontExtents& /*extents*/) override
  {++count_init; return CAIRO_STATUS_SUCCESS;}

  int count_init;

protected:
  ImplInitUserFont() : NullRenderUserFont(), count_init(0) {}
};

BOOST_AUTO_TEST_CASE(test_implement_init)
{
  TestSetup setup;
  auto font = ImplInitUserFont::create();
  setup.cr->set_font_face(font);
  setup.cr->show_text("hello");
  BOOST_REQUIRE(font->count_init > 0);
  BOOST_REQUIRE(font->count_render_glyph > 0);
}

class ExceptionUserFont : public UserFontFace
{
public:
  static RefPtr<ExceptionUserFont> create(int flags) { return make_refptr_for_instance<ExceptionUserFont>(new ExceptionUserFont(flags));};

  ErrorStatus
  render_glyph(const RefPtr<ScaledFont>& /*scaled_font*/,
               unsigned long /*glyph*/,
               const RefPtr<Context>& /*cr*/,
               TextExtents& /*metrics*/) override
  {
    count_render_glyph++;
    if (m_flags & FLAG_RENDER)
      throw std::logic_error("render-glyph exception");
    return CAIRO_STATUS_SUCCESS;
  }

  ErrorStatus
  unicode_to_glyph(const RefPtr<ScaledFont>& /*scaled_font*/,
                                       unsigned long unicode,
                                       unsigned long& glyph) override
  {
    count_unicode_to_glyph++;
    if (m_flags & FLAG_UNICODE)
      throw std::logic_error("unicode-to-glyph exception");

    glyph = unicode;
    return CAIRO_STATUS_SUCCESS;
  }

  ErrorStatus
  init(const RefPtr<ScaledFont>& /*scaled_font*/,
                         const RefPtr<Context>& /*cr*/,
                         FontExtents& /*extents*/) override
  {
    count_init++;
    if (m_flags & FLAG_INIT)
      throw std::logic_error("init exception");
    return CAIRO_STATUS_SUCCESS;
  }

  int count_render_glyph;
  int count_text_to_glyphs;
  int count_unicode_to_glyph;
  int count_init;
  int m_flags;

  static const int FLAG_INIT = 1 << 0;
  static const int FLAG_UNICODE = 1 << 1;
  static const int FLAG_RENDER = 1 << 2;

protected:
  ExceptionUserFont(int flags) : UserFontFace(), count_render_glyph(0),
  count_text_to_glyphs(0), count_unicode_to_glyph(0), count_init(0),
  m_flags(flags) {}
};

BOOST_AUTO_TEST_CASE(test_user_font_exception)
{
  auto font =
    ExceptionUserFont::create(ExceptionUserFont::FLAG_INIT);
  BOOST_CHECK(font);

  // the init() callback will throw an exception, if this isn't handled in the
  // callback wrapper, the program will abort since an exception can't unwind
  // through C code.  However, due to the exception being thrown, the create()
  // function will fail and throw a new exception.  So if the executable doesn't
  // abort, we should get an exception here.
  Cairo::RefPtr<Cairo::ScaledFont> scaled_font;
  BOOST_CHECK_THROW (scaled_font = Cairo::ScaledFont::create(font,
                                                             Cairo::scaling_matrix(10, 10),
                                                             Cairo::identity_matrix(),
                                                             Cairo::FontOptions()),
                     Cairo::logic_error);
  BOOST_CHECK (font->count_init > 0);

  // now test when an exception is thrown in unicode_to_glyph
  font = ExceptionUserFont::create(ExceptionUserFont::FLAG_UNICODE);
  BOOST_CHECK_NO_THROW (scaled_font = Cairo::ScaledFont::create(font,
                                                                Cairo::scaling_matrix(10, 10),
                                                                Cairo::identity_matrix(),
                                                                Cairo::FontOptions()));
  TestSetup setup;
  setup.cr->set_font_face(font);
  // this call should throw an exception since the callback wrapper will return
  // an error status (that will be translated into an exception) but the test
  // shouldn't abort since the callback exceptions are handled by the callback
  // wrapper
  BOOST_REQUIRE_EQUAL (CAIRO_STATUS_SUCCESS, font->get_status());
  BOOST_CHECK_THROW(setup.cr->show_text("Hello, world"), Cairo::logic_error);
  BOOST_CHECK(font->count_unicode_to_glyph > 0);
  BOOST_CHECK_EQUAL(font->count_render_glyph, 0);

  // now test when an exception is thrown in render_glyph
  font = ExceptionUserFont::create(ExceptionUserFont::FLAG_RENDER);
  BOOST_CHECK_NO_THROW (scaled_font = Cairo::ScaledFont::create(font,
                                                                Cairo::scaling_matrix(10, 10),
                                                                Cairo::identity_matrix(),
                                                                Cairo::FontOptions()));
  // need a new setup since the old cr is now in an error state, so attemtping
  // to use it will throw an exception
  TestSetup setup2;
  BOOST_CHECK_NO_THROW(setup2.cr->set_font_face(font));
  BOOST_REQUIRE_EQUAL (CAIRO_STATUS_SUCCESS, font->get_status());
  BOOST_CHECK_THROW(setup2.cr->show_text("Hello, world"), Cairo::logic_error);
  BOOST_CHECK (font->count_unicode_to_glyph > 0);
  BOOST_CHECK (font->count_render_glyph > 0);
}

BOOST_AUTO_TEST_SUITE_END()
