/* 
 * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
 *
 * This program 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; version 2 of the
 * License.
 * 
 * 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#include "stdafx.h"

#include <cstring>
#include "stdafx.h"

#include <string.h>

#include "mdc_text.h"
#include "mdc_canvas_view.h"

#include <glib.h>

/**
 * @file mdc_text.cpp
 *
 * Implementation of the text rendering classes.
 */

using namespace mdc;
using namespace MySQL::Geometry;

/**
 * Constructor of text figure class.
 *
 * @param layer The layer on which the text figure will be located.
 */
TextFigure::TextFigure(Layer *layer)
: Figure(layer)
{
  _font= layer->get_view()->get_default_font();
  CairoCtx *cr(get_layer()->get_view()->cairoctx());
  cr->get_font_extents(_font, _font_extents);

  _text_layout= 0;

  _multi_line= false;
  _allow_shrinking= false;

  _align= AlignLeft;

  _fill_background= false;

  scoped_connect(signal_bounds_changed(),boost::bind(&TextFigure::reset_shrinked_text, this));
}

/**
 * Destructor of the text figure.
 */
TextFigure::~TextFigure()
{
  delete _text_layout;
}

/**
 * Sets a flag that indicates whether the background must be filled or not.
 */
void TextFigure::set_fill_background(bool flag)
{
  _fill_background= flag;
}

/**
 * Resets the shorted text member.
 */
void TextFigure::reset_shrinked_text()
{
  _shrinked_text.clear();
}

/**
 * Returns the width for a piece of text depending on the used font setting.
 *
 * @param cr The context to which rendering will occur.
 * @param font The actual font specification to be used for rendering.
 * @param ptr The text to compute the width for.
 * @param offset Limits the length of the text to be considered for computation.
 *
 * @return A value describing the computed text width.
 */
static double get_text_width(CairoCtx *cr, const FontSpec &font, gchar *ptr, int offset)
{
  cairo_text_extents_t extents;
  gchar save= ptr[offset];
  ptr[offset]= 0;

  cr->get_text_extents(font, ptr, extents);
  ptr[offset]= save;

  return extents.x_advance;
}


static std::string fit_text_to_width(CairoCtx *cr, const FontSpec &font, const std::string &text, 
                                double width)
{
  gchar *ptr, *p, *prev;
  // calculate length in characters of string and also a mapping from character -> offset
  prev= p= ptr= g_strdup(text.c_str());
  while (p != NULL)
  {
    double w;

    if ((w= get_text_width(cr, font, ptr, p-ptr)) > width)
    {
      g_free(ptr);
      return text.substr(0, prev-ptr);
    }
    
    prev= p;
    p= g_utf8_next_char(p);
  }
  
  g_free(ptr);

  return text;
}


void TextFigure::draw_contents(CairoCtx *cr, const Rect &bounds)
{
  if (_fill_background)
  {
    cr->set_color(_fill_color);
    cr->rectangle(bounds);
    cr->fill();
  }

  if (_text_layout)
  {
    Point pos= bounds.pos;
    Size size= bounds.size;

    pos.x+= _xpadding;
    pos.y+= _ypadding;

    size.width-= 2*_xpadding;
    size.height-= 2*_ypadding;

    cr->set_color(_pen_color);
    _text_layout->render(cr, pos, size, _align);
  }
  else
  {
    cairo_text_extents_t extents;
    double x, y;

    cr->set_font(_font);
    cr->get_text_extents(_font, _text.c_str(), extents);
    cr->set_color(_pen_color);

    x= bounds.left() + _xpadding;
    y= bounds.bottom() - (bounds.height() - _font_extents.height)/2 - _font_extents.descent;
    if ((bounds.height() - _font_extents.height) > _font_extents.descent/2)
      y += _font_extents.descent/4;
    switch (_align)
    {
    case AlignLeft:
      cr->move_to(x, y);
      break;
    case AlignCenter:
      cr->move_to(x + ceil((bounds.size.width - 2*_xpadding - extents.width) / 2), y);
      break;
    case AlignRight:
      cr->move_to(x + bounds.right() - extents.width, y);
      break;
    }
    
    if (extents.width > bounds.size.width - 2*_xpadding)
    {
      // the text doesnt fit in the space we have, so check how much of it does fit
      if (_shrinked_text.empty())
      {
        cr->get_text_extents(_font, "\xe2\x80\xa6", extents);

        _shrinked_text= fit_text_to_width(cr, _font, _text, bounds.size.width - 2*_xpadding - extents.x_advance);
        _shrinked_text.append("\xe2\x80\xa6");
      }

      cr->show_text(_shrinked_text);
    }
    else
      cr->show_text(_text);

    cr->check_state();

    cr->stroke();

    cr->check_state();
  }
}


void TextFigure::draw_contents(CairoCtx *cr)
{
  draw_contents(cr, get_bounds());
}


void TextFigure::set_multi_line(bool flag)
{
  if (flag != _multi_line)
  {
    _multi_line= flag;

    if (!flag)
    {
      delete _text_layout;
      _text_layout= 0;

      _shrinked_text= "";
    }
    else
    {
      _text_layout= new TextLayout();
      _text_layout->set_text(_text);
      _text_layout->set_font(_font);
    }
  }
}


void TextFigure::set_allow_shrinking(bool flag)
{
  _allow_shrinking= flag;
  _shrinked_text= "";
  set_needs_relayout();
}


void TextFigure::set_font(const FontSpec &font)
{
  if (_font != font)
  {
    _font= font;

    if (_text_layout)
      _text_layout->set_font(font);
    
    CairoCtx *cr(get_layer()->get_view()->cairoctx());
    cr->get_font_extents(font, _font_extents);

    _shrinked_text= "";
    set_needs_relayout();
  }
}


void TextFigure::set_text_alignment(TextAlignment align)
{
  if (_align != align)
  {
    _align= align;
    set_needs_render();
  }
}


void TextFigure::set_text(const std::string &text)
{
  if (_text != text)
  {
    _text= text;
    _shrinked_text= "";

    if (_text_layout)
      _text_layout->set_text(text);

    //set_needs_render();  
    set_needs_relayout();
  }
}


void TextFigure::auto_size()
{
  Size size;

  size = get_text_size();
  size.width+= _xpadding*2;
  size.height+= _ypadding*2;

  //set_fixed_size(size);
  resize_to(size);
}


Size TextFigure::get_text_size()
{
  if (_text_layout)
  {
    _text_layout->relayout(get_layer()->get_view()->cairoctx());
    return _text_layout->get_size();
  }
  Size size;
  cairo_text_extents_t extents;

  get_layer()->get_view()->cairoctx()->get_text_extents(_font, _text, extents);

  size.width= ceil(extents.x_advance);
  size.height= ceil(_font_extents.height);
  
  return size;
}


Size TextFigure::calc_min_size()
{
  Size size= get_text_size();

  if (_allow_shrinking && !_auto_sizing)
    size.width= 0;
  
  size.width+= _xpadding*2;
  size.height+= _ypadding*2;

  return size;
}


//------------------------------------------------------------------------------------------------


void TextLayout::break_paragraphs()
{
  size_t start;
  const char *str= _text;
  const char *ptr;

  start= 0;

  _paragraphs.clear();

  while (str[start])
  {
    ptr= strchr(str+start, '\n');
    if (ptr)
    {
      Paragraph para;

      para.text_offset= start;
      para.text_length= ptr-(str+start);

      _paragraphs.push_back(para);

      start+= para.text_length+1; // +1 to skip the \n
    }
    else
    {
      Paragraph para;

      para.text_offset= start;
      para.text_length= strlen(_text)-start;

      _paragraphs.push_back(para);

      start+= para.text_length;
    }
  }
}


void TextLayout::relayout(CairoCtx *cr)
{
  if (_needs_relayout)
  {
    _needs_relayout= false;

    _lines.clear();

    for (std::vector<Paragraph>::iterator iter= _paragraphs.begin();
      iter != _paragraphs.end(); ++iter)
      layout_paragraph(cr, *iter);
  }
}


void TextLayout::layout_paragraph(CairoCtx *cr, Paragraph &para)
{
  cairo_text_extents_t ext;

  cr->get_text_extents(_font, std::string(_text+para.text_offset, para.text_length), ext);

  if (_fixed_size.width < 0 || ext.width < _fixed_size.width)
  {
    Line line;

    line.text_offset= para.text_offset;
    line.text_length= para.text_length;
    line.offset= Point(ceil(ext.x_bearing), ceil(ext.height*2 + ext.y_bearing));
    line.size= Size(ceil(std::max(ext.width, ext.x_advance)), ceil(std::max(ext.height, ext.y_advance)));

    _lines.push_back(line);
  }
  else
  {

  }
}



TextLayout::TextLayout()
{
  _fixed_size= Size(-1, -1);
  _needs_relayout= true;
  _text= NULL;

  _font= FontSpec("Helvetica");
}


TextLayout::~TextLayout()
{
  if (_text)
    free(_text);
}



void TextLayout::set_text(const std::string &text)
{
  if (_text)
    free(_text);

  _text= strdup(text.c_str());

  break_paragraphs();

  _needs_relayout= true;
}


void TextLayout::set_font(const FontSpec &font)
{
  _font= font;

  _needs_relayout= true;
}


void TextLayout::set_width(double width)
{
  _fixed_size.width= width;

  _needs_relayout= true;
}


void TextLayout::set_height(double height)
{
  _fixed_size.height= height;
}


Size TextLayout::get_size()
{
  Size size= _fixed_size;
  double w= 0, h= 0;
  double line_spacing= floor(_font.size / 4) + 1;
  double line_height= 0;

  for (std::vector<Line>::const_iterator iter= _lines.begin(); iter != _lines.end(); ++iter)
  {
    w= std::max(w, iter->size.width);
    line_height= std::max(line_height, iter->size.height);
  }

  if (!_lines.empty())
    h= line_spacing*(_lines.size()-1) +  line_height * _lines.size();

  if (size.width < 0)
    size.width= ceil(w);
  if (size.height < 0)
    size.height= ceil(h);

  return size;
}


void TextLayout::render(CairoCtx *cr, const Point &pos, const Size &size, TextAlignment align)
{
  double x= pos.x;
  double y= pos.y;
  double line_spacing= floor(_font.size / 4) + 1;
  double line_height;

  line_height= 0;
  for (std::vector<Line>::const_iterator iter= _lines.begin(); iter != _lines.end(); ++iter)
    line_height= std::max(line_height, iter->size.height);

  cr->save();
  cr->set_font(_font);
  for (std::vector<Line>::const_iterator iter= _lines.begin(); iter != _lines.end(); ++iter)
  {
    y+= line_height;

    switch (align)
    {
    case AlignLeft:
      cr->move_to(x, y);
      break;
    case AlignCenter:
      cr->move_to(x + (size.width - iter->size.width)/2, y + iter->offset.y);
      break;
    case AlignRight:
      cr->move_to(x + (size.width - iter->size.width), y + iter->offset.y);
      break;
    }
    cr->show_text(std::string(_text+iter->text_offset, iter->text_length));

    y+= line_spacing;
  }
  cr->restore();
}


