// Copyright (C) 2010, Guy Barrand. All rights reserved.
// See the file tools.license for terms.

#ifndef tools_sg_infos_box
#define tools_sg_infos_box

//
// In z the scene is within [0,0.5]
//

#include "back_area"
#include "matrix"
#include "text_hershey"
#include "base_freetype"
#include "enums"

#include "../colorf"

namespace tools {
namespace sg {

class infos_box : public back_area {
  TOOLS_NODE(infos_box,tools::sg::infos_box,back_area)
public:
  mf_string lstrings;
  mf_string rstrings;
  sf<unsigned int> num_spaces; //in "number of one line height" unit.

  sf_vec<colorf,float> color;
  sf_string font;
  sf_enum<sg::font_modeling> font_modeling;
  sf_string encoding;
  sf<float> line_width; // for text_hershey.
  sf_enum<winding_type> front_face; //no more used.

  sf<bool> back_visible;
  sf<float> wmargin_factor;
  sf<float> hmargin_factor;
  sf_enum<hjust> lhjust;
  sf_enum<hjust> rhjust;
  sf<bool> confine;
public:
  virtual const desc_fields& node_desc_fields() const {
    TOOLS_FIELD_DESC_NODE_CLASS(tools::sg::infos_box)
    static const desc_fields s_v(parent::node_desc_fields(),15, //WARNING : take care of count.
      TOOLS_ARG_FIELD_DESC(lstrings),
      TOOLS_ARG_FIELD_DESC(rstrings),
      TOOLS_ARG_FIELD_DESC(num_spaces),
      TOOLS_ARG_FIELD_DESC(color),

      TOOLS_ARG_FIELD_DESC_OPTS_BEG(font,10)
        font_hershey().c_str(),
        font_lato_regular_ttf().c_str(),
        font_roboto_bold_ttf().c_str(),
        font_arial_ttf().c_str(),
        font_arialbd_ttf().c_str(),
        font_timesbd_ttf().c_str(),
        font_symbol_ttf().c_str(),
        font_stixgeneral_otf().c_str(),
        font_helvetica_ttf().c_str(),
        font_times_roman_ttf().c_str()
      TOOLS_ARG_FIELD_DESC_OPTS_END,

      TOOLS_ARG_FIELD_DESC_ENUMS_BEG(font_modeling,3)
        TOOLS_ARG_ENUM(font_outline),
        TOOLS_ARG_ENUM(font_filled),
        TOOLS_ARG_ENUM(font_pixmap)
      TOOLS_ARG_FIELD_DESC_ENUMS_END,

      TOOLS_ARG_FIELD_DESC(encoding),
      TOOLS_ARG_FIELD_DESC(line_width),
      TOOLS_ARG_FIELD_DESC(front_face),

      TOOLS_ARG_FIELD_DESC(back_visible), //10
      TOOLS_ARG_FIELD_DESC(wmargin_factor),
      TOOLS_ARG_FIELD_DESC(hmargin_factor),
      TOOLS_ARG_FIELD_DESC(lhjust),
      TOOLS_ARG_FIELD_DESC(rhjust),
      TOOLS_ARG_FIELD_DESC(confine)
    );
    return s_v;
  }
private:
  void add_fields(){
    add_field(&lstrings);
    add_field(&rstrings);
    add_field(&num_spaces);

    add_field(&color);
    add_field(&font);
    add_field(&font_modeling);
    add_field(&encoding);
    add_field(&line_width);
    add_field(&front_face);

    add_field(&back_visible);
    add_field(&wmargin_factor);
    add_field(&hmargin_factor);
    add_field(&lhjust);
    add_field(&rhjust);
    add_field(&confine);
  }
public:
  virtual void render(render_action& a_action) {
    if(touched()) {
      update_sg();
      reset_touched();
    }
    if(back_visible.value()) m_back_sep.render(a_action);
    m_sep.render(a_action);
  }
  virtual void pick(pick_action& a_action) {
    if(touched()) {
      update_sg();
      reset_touched();
    }
    if(back_visible.value()) {
      m_back_sep.pick(a_action);
      if(a_action.done()) return;
    }
  }
  virtual void search(search_action& a_action) {
    if(touched()) {
      update_sg();
      reset_touched();
    }
    node::search(a_action);
    if(a_action.done()) return;
    if(back_visible.value()) {
      m_back_sep.search(a_action);
      if(a_action.done()) return;
    }
    m_sep.search(a_action);
    if(a_action.done()) return;
  }
  virtual bool write(write_action& a_action) {
    if(touched()) {
      update_sg();
      reset_touched();
    }
    if(back_visible.value()) if(!m_back_sep.write(a_action)) return false;
    return m_sep.write(a_action);
  }
  virtual void bbox(bbox_action& a_action) {
    if(touched()) {
      update_sg();
      reset_touched();
    }
    if(back_visible.value()) m_back_sep.bbox(a_action);
    m_sep.bbox(a_action);
  }
public:
  infos_box(const base_freetype& a_ttf)
  :parent()
  ,lstrings()
  ,rstrings()
  ,num_spaces(4)

  ,color(colorf_black())
  ,font(font_hershey())
  ,font_modeling(font_filled)
  ,encoding(encoding_PAW())
  ,line_width(1)
  ,front_face(winding_ccw)

  ,back_visible(true)
  ,wmargin_factor(0.9f)
  ,hmargin_factor(0.9f)
  ,lhjust(left)
  ,rhjust(right)

  ,confine(false)

  ,m_ttf(a_ttf)
  {
    add_fields();
  }
  virtual ~infos_box(){}
public:
  infos_box(const infos_box& a_from)
  :parent(a_from)
  ,lstrings(a_from.lstrings)
  ,rstrings(a_from.rstrings)
  ,num_spaces(a_from.num_spaces)

  ,color(a_from.color)
  ,font(a_from.font)
  ,font_modeling(a_from.font_modeling)
  ,encoding(a_from.encoding)
  ,line_width(a_from.line_width)
  ,front_face(a_from.front_face)

  ,back_visible(a_from.back_visible)
  ,wmargin_factor(a_from.wmargin_factor)
  ,hmargin_factor(a_from.hmargin_factor)
  ,lhjust(a_from.lhjust)
  ,rhjust(a_from.rhjust)

  ,confine(a_from.confine)

  ,m_ttf(a_from.m_ttf)
  {
    add_fields();
  }
  infos_box& operator=(const infos_box& a_from){
    parent::operator=(a_from);
    lstrings = a_from.lstrings;
    rstrings = a_from.rstrings;
    num_spaces = a_from.num_spaces;

    color = a_from.color;
    font = a_from.font;
    font_modeling = a_from.font_modeling;
    encoding = a_from.encoding;
    line_width = a_from.line_width;
    front_face = a_from.front_face;

    back_visible = a_from.back_visible;
    wmargin_factor = a_from.wmargin_factor;
    hmargin_factor = a_from.hmargin_factor;
    lhjust = a_from.lhjust;
    rhjust = a_from.rhjust;

    confine = a_from.confine;

    return *this;
  }
public:
  void update_sg() {
    // have this method public in order to use it in plotter.
    // This is so because infos_box::height is an output field
    // needed in plotter to place the box.

    m_back_sep.clear(); //back_area::update_sg done last.

    m_sep.clear();

    if(width.value()<=0) return;
    if(confine) {
      if(height.value()<=0) return;
    }

   {bool empty = true;
    std::vector<std::string>::const_iterator it;
    for(it=lstrings.values().begin();it!=lstrings.values().end();++it) {
      if((*it).size()) {empty = false;break;}
    }
    if(empty){
      for(it=rstrings.values().begin();it!=rstrings.values().end();++it) {
        if((*it).size()) {empty = false;break;}
      }
    }
    if(empty) {
      return;
    }}

    rgba* mat = new rgba();
    mat->color = color;
    m_sep.add(mat);

    if(font==font_hershey()) {
      draw_style* ds = new draw_style;
      ds->style.value(draw_lines);
      ds->line_width = line_width;
      m_sep.add(ds);
    }

    ////////////////////////////////////////////////////////////
    /// left text //////////////////////////////////////////////
    ////////////////////////////////////////////////////////////
    base_text* ltext = 0;
    matrix* ltsf = 0;

   {separator* sep = new separator;
    m_sep.add(sep);
    ltsf = new matrix;
    sep->add(ltsf);
    if(font==font_hershey()) {
      text_hershey* text = new text_hershey;
      ltext = text;
      text->encoding = encoding;
      sep->add(text);
    } else {
      base_freetype* text = base_freetype::create(m_ttf);
      ltext = text;
      text->font = font;
      text->modeling = font_modeling;
      sep->add(text);
    }
    ltext->strings = lstrings;
    ltext->hjust = lhjust;}

    ////////////////////////////////////////////////////////////
    /// right text /////////////////////////////////////////////
    ////////////////////////////////////////////////////////////
    base_text* rtext = 0;
    matrix* rtsf = 0;

   {separator* sep = new separator;
    m_sep.add(sep);
    rtsf = new matrix;
    sep->add(rtsf);
    if(font==font_hershey()) {
      text_hershey* text = new text_hershey;
      rtext = text;
      text->encoding = encoding;
      sep->add(text);
    } else {
      base_freetype* text = base_freetype::create(m_ttf);
      rtext = text;
      text->font = font;
      text->modeling = font_modeling;
      sep->add(text);
    }
    rtext->strings = rstrings;
    rtext->hjust = rhjust;}

    ////////////////////////////////////////////////////////////
    /// middle spaces //////////////////////////////////////////
    ////////////////////////////////////////////////////////////
    base_text* mtext = 0;
   {std::string _s(num_spaces,' ');
    if(font==font_hershey()) {
      text_hershey* text = new text_hershey;
      mtext = text;
      text->strings.add(_s);
      text->hjust = left;
    } else {
      base_freetype* text = base_freetype::create(m_ttf);
      mtext = text;
      text->strings.add(_s);
      text->hjust = left;
      text->font = font;
      text->modeling = font_modeling;
    }}

    float zz = back_visible.value()?0.01f:0;

    float fw = width * wmargin_factor;

    if(confine) {
      // left right texts may overlap.

      // adjust height :
      float fh = height * hmargin_factor;
      float th = fh;
     {float mn_x,mn_y,mn_z;
      float mx_x,mx_y,mx_z;
      ltext->get_bounds(th,mn_x,mn_y,mn_z,mx_x,mx_y,mx_z);
      float bxh = mx_y-mn_y;
      // adjust box height :
      // fh -> bxh then to have fh :
      if(!bxh) {
        m_sep.clear();
        parent::update_sg();
        delete mtext;
        return;
      }
      th = fh*fh/bxh;}

      ltext->height = th;
      rtext->height = th;

     {float mn_x,mn_y,mn_z;
      float mx_x,mx_y,mx_z;
      ltext->get_bounds(th,mn_x,mn_y,mn_z,mx_x,mx_y,mx_z);
      float lw = mx_x-mn_x;
      float xtrans = (fw-lw)*0.5f; //left justified
      float xx = -(mn_x+mx_x)*0.5F-xtrans;
      float yy = -(mn_y+mx_y)*0.5F;
      ltsf->set_translate(xx,yy,zz);}

     {float mn_x,mn_y,mn_z;
      float mx_x,mx_y,mx_z;
      rtext->get_bounds(th,mn_x,mn_y,mn_z,mx_x,mx_y,mx_z);
      rtext->hjust = right;
      float xx = fw*0.5f;
      float yy = -(mn_y+mx_y)*0.5F;
      rtsf->set_translate(xx,yy,zz);}

      parent::update_sg();

    } else {
      // height of the info box is an output of the below.
      // The input to compute the geometry is the width of the infos
      // box, the left/right texts and the space between
      // left/right texts.

      // from a text height of th=1, and a left/right sep
      // of num_spaces*th, get width of the box :

      float th = 1;

      float lmn_x,lmn_y,lmn_z;
      float lmx_x,lmx_y,lmx_z;
      ltext->get_bounds(th,lmn_x,lmn_y,lmn_z,lmx_x,lmx_y,lmx_z);
      float lw = (lmx_x<lmn_x)?0:lmx_x-lmn_x;

      float rmn_x,rmn_y,rmn_z;
      float rmx_x,rmx_y,rmx_z;
      rtext->get_bounds(th,rmn_x,rmn_y,rmn_z,rmx_x,rmx_y,rmx_z);
      float rw = (rmx_x<rmn_x)?0:rmx_x-rmn_x;

      float mmn_x,mmn_y,mmn_z;
      float mmx_x,mmx_y,mmx_z;
      mtext->get_bounds(th,mmn_x,mmn_y,mmn_z,mmx_x,mmx_y,mmx_z);
      float mw = (mmx_x<mmn_x)?0:mmx_x-mmn_x;

      float winfos = lw+mw+rw;
      if(!winfos) {
        m_sep.clear();
        height = 1;
        parent::update_sg();
        delete mtext;
        return;
      }

      // assuming that text size is linear relative
      // to th, we get the th needed to match infos_box.width :
      // th -> winfos then to have fw :
      th = fw*th/winfos;

      // place the left/right texts by rescaling them to
      // the new th :
      ltext->height = th;
      ltext->get_bounds(th,lmn_x,lmn_y,lmn_z,lmx_x,lmx_y,lmx_z);
      float hinfos = (lmx_x<lmn_x)?0:lmx_y-lmn_y;

      float xx = -fw*0.5F; //left justified.
      float ty = -ltext->ascent(th)+hinfos*0.5f;
      ltsf->set_translate(xx,ty,zz);

      rtext->height = th;
      if(rtext->hjust==right) {
        xx = fw*0.5F;
      } else if(rtext->hjust==left) {
        rtext->get_bounds(th,rmn_x,rmn_y,rmn_z,rmx_x,rmx_y,rmx_z);
        rw = (rmx_x<rmn_x)?0:rmx_x-rmn_x;
        xx = fw*0.5F-rw;
      } else { //center
        rtext->get_bounds(th,rmn_x,rmn_y,rmn_z,rmx_x,rmx_y,rmx_z);
        rw = (rmx_x<rmn_x)?0:rmx_x-rmn_x;
        xx = fw*0.5F-rw*0.5F;
      }
      rtsf->set_translate(xx,ty,zz);

      // set infos_box.height :
      height = hinfos/hmargin_factor;

      //done last because of the upper height setup.
      parent::update_sg();

    }

    delete mtext;

  }
protected:
  const base_freetype& m_ttf;

  separator m_sep;
};

}}

#endif
