// Copyright (C) 2000-2007, Luca Padovani <padovani@sti.uniurb.it>.
// 
// This file is part of GtkMathView, a flexible, high-quality rendering
// engine for MathML documents.
// 
// GtkMathView is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
// 
// GtkMathView 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
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <config.h>

#include <cassert>

#include "Configuration.hh"
#include "Gtk_AreaFactory.hh"
#include "Gtk_DefaultPangoShaper.hh"
#include "Gtk_RenderingContext.hh"
#include "MathMLElement.hh"
#include "MathGraphicDevice.hh"
#include "ShapingContext.hh"

static PangoStyle
parsePangoStyle(const String& s, PangoStyle v)
{
  if (s == "italic")
    return PANGO_STYLE_ITALIC;
  else if (s == "oblique")
    return PANGO_STYLE_OBLIQUE;
  else
    return v;
}

static PangoWeight
parsePangoWeight(const String& s, PangoWeight v)
{
  if (s == "light")
    return PANGO_WEIGHT_LIGHT;
  else if (s == "bold")
    return PANGO_WEIGHT_BOLD;
  else
    return v;
}

Gtk_DefaultPangoShaper::Gtk_DefaultPangoShaper(const SmartPtr<AbstractLogger>& logger, const SmartPtr<Configuration>& conf)
{
  static const DefaultPangoTextAttributes defaultVariantDesc[] =
    {
      { "normal", NORMAL_VARIANT, "Serif", "normal", PANGO_STYLE_NORMAL, "normal", PANGO_WEIGHT_NORMAL },
      { "bold", BOLD_VARIANT, "Serif", "normal", PANGO_STYLE_NORMAL, "bold", PANGO_WEIGHT_BOLD },
      { "italic", ITALIC_VARIANT, "Serif", "italic", PANGO_STYLE_ITALIC, "normal", PANGO_WEIGHT_NORMAL },
      { "bold-italic", BOLD_ITALIC_VARIANT, "Serif", "italic", PANGO_STYLE_ITALIC, "bold", PANGO_WEIGHT_BOLD }, 
      { "double-struck", DOUBLE_STRUCK_VARIANT, "Sans", "normal", PANGO_STYLE_NORMAL, "bold", PANGO_WEIGHT_BOLD },
      { "bold-fraktur", BOLD_FRAKTUR_VARIANT, "Serif", "normal", PANGO_STYLE_NORMAL, "bold", PANGO_WEIGHT_BOLD },
      { "script", SCRIPT_VARIANT, "Sans", "normal", PANGO_STYLE_NORMAL, "normal", PANGO_WEIGHT_NORMAL },
      { "bold-script", BOLD_SCRIPT_VARIANT, "Sans", "normal", PANGO_STYLE_NORMAL, "bold", PANGO_WEIGHT_BOLD },
      { "fraktur", FRAKTUR_VARIANT, "Serif", "normal", PANGO_STYLE_NORMAL,"bold",  PANGO_WEIGHT_BOLD },
      { "sans-serif", SANS_SERIF_VARIANT, "Sans", "normal", PANGO_STYLE_NORMAL, "normal", PANGO_WEIGHT_NORMAL },
      { "bold-sans-serif", BOLD_SANS_SERIF_VARIANT, "Sans", "normal", PANGO_STYLE_NORMAL, "bold", PANGO_WEIGHT_BOLD },
      { "sans-serif-italic", SANS_SERIF_ITALIC_VARIANT, "Sans", "italic", PANGO_STYLE_ITALIC, "normal", PANGO_WEIGHT_NORMAL },
      { "sans-serif-bold-italic", SANS_SERIF_BOLD_ITALIC_VARIANT, "Sans", "italic", PANGO_STYLE_ITALIC, "normal", PANGO_WEIGHT_NORMAL },
      { "monospace", MONOSPACE_VARIANT, "Monospace", "normal", PANGO_STYLE_NORMAL, "normal", PANGO_WEIGHT_NORMAL }
    };

  const String baseKey = "gtk-backend/pango-default-shaper/variants/";
  for (unsigned i = 0; i < sizeof(defaultVariantDesc) / sizeof(DefaultPangoTextAttributes); i++)
    {
      const String key = baseKey + defaultVariantDesc[i].variant;
      const String family = conf->getString(logger, key + "/family", defaultVariantDesc[i].family);
      const String style = conf->getString(logger, key + "/style", defaultVariantDesc[i].style);
      const String weight = conf->getString(logger, key + "/weight", defaultVariantDesc[i].weight);
      const PangoStyle pangoStyle = parsePangoStyle(style, PANGO_STYLE_NORMAL);
      const PangoWeight pangoWeight = parsePangoWeight(weight, PANGO_WEIGHT_NORMAL);
      variantDesc[i].variant = defaultVariantDesc[i].variantId;
      variantDesc[i].family = family;
      variantDesc[i].style = pangoStyle;
      variantDesc[i].weight = pangoWeight;
    }
}

Gtk_DefaultPangoShaper::~Gtk_DefaultPangoShaper()
{ }

SmartPtr<Gtk_DefaultPangoShaper>
Gtk_DefaultPangoShaper::create(const SmartPtr<AbstractLogger>& l, const SmartPtr<Configuration>& conf)
{ return new Gtk_DefaultPangoShaper(l, conf); }

void
Gtk_DefaultPangoShaper::registerShaper(const SmartPtr<class ShaperManager>&, unsigned)
{
  // normal characters are not registered because the Pango shaper is supposed to
  // be the default shaper. It will be called anyway as soon as there's a
  // Unicode char that cannot be shaped otherwise
}

void
Gtk_DefaultPangoShaper::unregisterShaper(const SmartPtr<class ShaperManager>&, unsigned)
{
  // nothing to do
}

void
Gtk_DefaultPangoShaper::shape(ShapingContext& context) const
{
  const unsigned n = context.chunkSize();
  assert(n > 0);
  // is it worth specializing this to the case when n == 1????
  gunichar* uni_buffer = new gunichar[n];
  for (unsigned i = 0; i < n; i++) uni_buffer[i] = context.data()[i];
  context.pushArea(n, shapeString(context, uni_buffer, n));
  delete [] uni_buffer;
}

AreaRef
Gtk_DefaultPangoShaper::shapeString(const ShapingContext& context, const gunichar* uni_buffer, unsigned n) const
{
  glong length;
  gchar* buffer = g_ucs4_to_utf8(uni_buffer, n, NULL, &length, NULL);
  PangoLayout* layout = createPangoLayout(buffer, length,
					  context.getSize(),
					  getDefaultTextAttributes());
  g_free(buffer);

  SmartPtr<Gtk_AreaFactory> factory = smart_cast<Gtk_AreaFactory>(context.getFactory());
  assert(factory);
  // LUCA: what is the difference between PangoLayout and PangoLayoutLine?
  return factory->pangoLayoutLine(layout);
}

const Gtk_DefaultPangoShaper::PangoTextAttributes&
Gtk_DefaultPangoShaper::getDefaultTextAttributes()
{
  static const PangoTextAttributes desc = { NORMAL_VARIANT, "serif", PANGO_STYLE_NORMAL, PANGO_WEIGHT_NORMAL };
  return desc;
}

const Gtk_DefaultPangoShaper::PangoTextAttributes&
Gtk_DefaultPangoShaper::getTextAttributes(MathVariant variant) const
{
  assert(variant >= NORMAL_VARIANT && variant <= MONOSPACE_VARIANT);
//   static const PangoTextAttributes variantDesc[] =
//     {
//       { NORMAL_VARIANT, "Serif", PANGO_STYLE_NORMAL, PANGO_WEIGHT_NORMAL },
//       { BOLD_VARIANT, "Serif", PANGO_STYLE_NORMAL, PANGO_WEIGHT_BOLD },
//       { ITALIC_VARIANT, "Serif", PANGO_STYLE_ITALIC, PANGO_WEIGHT_NORMAL },
//       { BOLD_ITALIC_VARIANT, "Serif", PANGO_STYLE_ITALIC, PANGO_WEIGHT_BOLD }, 
//       { DOUBLE_STRUCK_VARIANT, "Sans", PANGO_STYLE_NORMAL, PANGO_WEIGHT_BOLD },
//       { BOLD_FRAKTUR_VARIANT, "Serif", PANGO_STYLE_NORMAL, PANGO_WEIGHT_BOLD },
//       { SCRIPT_VARIANT, "Sans", PANGO_STYLE_NORMAL, PANGO_WEIGHT_NORMAL },
//       { BOLD_SCRIPT_VARIANT, "Sans", PANGO_STYLE_NORMAL, PANGO_WEIGHT_BOLD },
//       { FRAKTUR_VARIANT, "Serif", PANGO_STYLE_NORMAL, PANGO_WEIGHT_BOLD },
//       { SANS_SERIF_VARIANT, "Sans", PANGO_STYLE_NORMAL, PANGO_WEIGHT_NORMAL },
//       { BOLD_SANS_SERIF_VARIANT, "Sans", PANGO_STYLE_NORMAL, PANGO_WEIGHT_BOLD },
//       { SANS_SERIF_ITALIC_VARIANT, "Sans", PANGO_STYLE_ITALIC, PANGO_WEIGHT_NORMAL },
//       { SANS_SERIF_BOLD_ITALIC_VARIANT, "Sans", PANGO_STYLE_ITALIC, PANGO_WEIGHT_NORMAL },
//       { MONOSPACE_VARIANT, "Monospace", PANGO_STYLE_NORMAL, PANGO_WEIGHT_NORMAL }
//     };
  return variantDesc[variant - NORMAL_VARIANT];
}

PangoLayout*
Gtk_DefaultPangoShaper::createPangoLayout(const gchar* buffer, glong length, const scaled& sp_size,
					  const PangoTextAttributes& attributes) const
{
  // Apparently when setting font sizes the interpretation of PANGO_SCALE is different
  // (see Pango documentation) hence we do NOT use toPangoPixels
  const gint size = Gtk_RenderingContext::toPangoPoints(sp_size);

  // FIXME: I bet there are some leaks here, but using GObjectPtr just
  // gives a segfault!!!
  //GObjectPtr<PangoLayout> layout = pango_layout_new(context);
  PangoLayout* layout = pango_layout_new(context);
  pango_layout_set_text(layout, buffer, length);

  //GObjectPtr<PangoAttrList> attrList = pango_attr_list_new();
  PangoAttrList* attrList = pango_attr_list_new();

#if 1
  PangoFontDescription* fontDesc = pango_font_description_new();
  if (!attributes.family.empty()) pango_font_description_set_family_static(fontDesc, attributes.family.c_str());
  if (attributes.weight != PANGO_WEIGHT_NORMAL) pango_font_description_set_weight(fontDesc, attributes.weight);
  if (attributes.style != PANGO_STYLE_NORMAL) pango_font_description_set_style(fontDesc, attributes.style);
  pango_font_description_set_size(fontDesc, size);
  PangoAttribute* fontDescAttr = pango_attr_font_desc_new(fontDesc);
  fontDescAttr->start_index = 0;
  fontDescAttr->end_index = length;
  pango_attr_list_insert(attrList, fontDescAttr);
  pango_font_description_free(fontDesc);  //???
#else
  // PangoAttribute is not a GObject?
  PangoAttribute* sizeAttr = pango_attr_size_new(size);
  sizeAttr->start_index = 0;
  sizeAttr->end_index = length;
  pango_attr_list_insert(attrList, sizeAttr);
#if 1
  if (attributes.family)
    {
      PangoAttribute* familyAttr = pango_attr_family_new(attributes.family);
      familyAttr->start_index = 0;
      familyAttr->start_index = length;
      pango_attr_list_insert(attrList, familyAttr);
    }

  if (attributes.weight != PANGO_WEIGHT_NORMAL)
    {
      PangoAttribute* weightAttr = pango_attr_weight_new(attributes.weight);
      weightAttr->start_index = 0;
      weightAttr->end_index = length;
      pango_attr_list_insert(attrList, weightAttr);
    }

  if (attributes.style != PANGO_STYLE_NORMAL)
    {
      PangoAttribute* styleAttr = pango_attr_style_new(attributes.style);
      styleAttr->start_index = 0;
      styleAttr->end_index = length;
      pango_attr_list_insert(attrList, styleAttr);
    }
#endif
#endif
  pango_layout_set_attributes(layout, attrList);

  return layout;
}

bool
Gtk_DefaultPangoShaper::isDefaultShaper() const
{ return true; }