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

#ifndef tools_sg_text_hershey
#define tools_sg_text_hershey

#include "base_text"
#include "enums"
#include "strings"
#include "render_action"
#include "pick_action"
#include "bbox_action"
#include "gstos"
#include "sf_string"

#include "../hershey"
#include "../lina/box_3f"
#include "../mnmx"

namespace tools {
namespace sg {

class hchar {
public:
  hchar()
  :m_char(0)
  ,m_font(sg::latin)
  ,m_y_move(none)
  ,m_back(false)
  ,m_bar(false)
  ,m_cr(false)
  {
  }
  virtual ~hchar(){
  }
public:
  hchar(const hchar& aFrom)
  :m_char(aFrom.m_char)
  ,m_font(aFrom.m_font)
  ,m_y_move(aFrom.m_y_move)
  ,m_back(aFrom.m_back)
  ,m_bar(aFrom.m_bar)
  ,m_cr(aFrom.m_cr)
  {
  }
  hchar& operator=(const hchar& aFrom) {
    m_char = aFrom.m_char;
    m_font = aFrom.m_font;
    m_y_move = aFrom.m_y_move;
    m_back = aFrom.m_back;
    m_bar = aFrom.m_bar;
    m_cr = aFrom.m_cr;
    return *this;
  }
public:
  enum e_move {
    none,
    up,
    down
  };
public:
  char m_char;
  font_type m_font;
  e_move m_y_move;
  bool m_back;
  bool m_bar;
  bool m_cr;
};

class text_hershey : public base_text, public gstos {
public:
  TOOLS_NODE(text_hershey,tools::sg::text_hershey,base_text)
public:
  sf_string encoding;
  sf_enum<font_type> font;
public:
  virtual const desc_fields& node_desc_fields() const {
    TOOLS_FIELD_DESC_NODE_CLASS(tools::sg::text_hershey)
    static const desc_fields s_v(parent::node_desc_fields(),2, //WARNING : take care of count.
      TOOLS_ARG_FIELD_DESC(encoding),
      TOOLS_ARG_FIELD_DESC(font)
    );
    return s_v;
  }
private:
  void add_fields(){
    add_field(&encoding);
    add_field(&font);
  }
protected: //gstos
  virtual unsigned int create_gsto(std::ostream&,sg::render_manager& a_mgr) {
    std::vector<float> gsto_data;

   {size_t npts = m_segs.size()/2;  //2 coords
    size_t ngsto = npts*3;     //3 coords.
    size_t sz = gsto_data.size();
    gsto_data.resize(sz+ngsto);
    float* pxyz = gsto_data.data()+sz;
    const float* data = m_segs.data();
    gl::cvt_2to3(npts,data,pxyz);}

    m_gsto_sz = gsto_data.size();

    if(gsto_data.empty()) return 0;

    return a_mgr.create_gsto_from_data(gsto_data);
  }
public:
  virtual void render(render_action& a_action) {
    if(touched()) {
      update_sg();
      reset_touched();
    }
    const state& state = a_action.state();
    if(state.m_use_gsto) {
      unsigned int _id = get_gsto_id(a_action.out(),a_action.render_manager());
      if(_id) {
        a_action.begin_gsto(_id);
        a_action.draw_gsto_v(gl::lines(),m_gsto_sz/3,0);
        a_action.end_gsto();
        return;

      } else { //!_id
        // use immediate rendering.
      }

    } else {
      clean_gstos(&a_action.render_manager());
    }

    // immediate rendering :
    a_action.draw_vertex_array_xy(gl::lines(),m_segs);
  }

  virtual void pick(pick_action& a_action) {
    if(touched()) {
      update_sg();
      reset_touched();
    }
    a_action.add__lines_xy(*this,m_segs,true);
  }

  virtual void bbox(bbox_action& a_action) {
    if(touched()) {
      update_sg();
      reset_touched();
    }

    float x,y;
    std::vector<float>::const_iterator it;
    for(it=m_segs.begin();it!=m_segs.end();) {
      x = *it;it++;
      y = *it;it++;
      a_action.add_one_point(x,y,0);
    }
  }

public:
  text_hershey()
  :parent()
  ,encoding(encoding_none())
  ,font(sg::latin)
  ,m_gsto_sz(0)
  {
    add_fields();
  }
  virtual ~text_hershey(){}
public:
  text_hershey(const text_hershey& a_from)
  :parent(a_from)
  ,gstos(a_from)
  ,encoding(a_from.encoding)
  ,font(a_from.font)
  ,m_gsto_sz(0)
  {
    add_fields();
  }
  text_hershey& operator=(const text_hershey& a_from){
    parent::operator=(a_from);
    gstos::operator=(a_from);
    encoding = a_from.encoding;
    font = a_from.font;
    return *this;
  }
public: //sg::base_text :
  virtual void get_bounds(float a_height,
                          float& a_mn_x,float& a_mn_y,float& a_mn_z,
                          float& a_mx_x,float& a_mx_y,float& a_mx_z) const {
    get_bounds(a_height,encoding.value(),font.value(),
               strings.values(),
               a_mn_x,a_mn_y,a_mn_z,
               a_mx_x,a_mx_y,a_mx_z);
  }
  virtual float ascent(float a_height) const {
    // '/' seems to be the char with the max ascent.
    // NOTE : If 'A', the ascent = height.value().
    float mn_x,mn_y,mn_z;
    float mx_x,mx_y,mx_z;
    get_char_bound('/',sg::latin,a_height,false,
                   mn_x,mn_y,mn_z,
                   mx_x,mx_y,mx_z);
    return mx_y;
  }
  virtual float y_advance(float a_height) const {
    float HEIGHT = a_height;
    return 2 * HEIGHT; //Y_ADVANCE
  }

public:
  virtual float descent(float a_height) const {return _descent(a_height);}

  virtual bool truncate(const std::string& a_string,float a_height,float a_cut_width,std::string& a_out) const {
    return _truncate(a_string,a_height,font.value(),a_cut_width,a_out);
  }

public:
  static void get_bounds(float a_height,
                         const std::string& a_encoding,
                         font_type a_font,
                         const std::vector<std::string>& a_ss,
                         float& a_mn_x,float& a_mn_y,float& a_mn_z,
                         float& a_mx_x,float& a_mx_y,float& a_mx_z){
    if(a_ss.size()) {
      float HEIGHT = a_height;
      float Y_ADVANCE = 2 * HEIGHT;
      float width = 0;
      float Y = 0;
      std::vector<float> dummy;
      tools_vforcit(std::string,a_ss,it) {
        float XL = 0;
        string_segs(false,*it,a_height,a_encoding,a_font,XL,Y,dummy,false);
        Y -= Y_ADVANCE;
        width = max_of<float>(width,XL);
      }

      a_mn_x = 0;
      a_mn_y = -Y_ADVANCE*(a_ss.size()-1)-_descent(a_height);
      a_mn_z = 0;
      a_mx_x = width;
      a_mx_y = HEIGHT;
      a_mx_z = 0;
    } else {
      box_3f_make_empty(a_mn_x,a_mn_y,a_mn_z,a_mx_x,a_mx_y,a_mx_z);
    }
  }

  static void get_bounds(float a_height,
                         const std::string& a_encoding,
                         font_type a_font,
                         const std::string& a_s,
                         float& a_mn_x,float& a_mn_y,float& a_mn_z,
                         float& a_mx_x,float& a_mx_y,float& a_mx_z){
    float HEIGHT = a_height;
    float Y_ADVANCE = 2 * HEIGHT;
    float width = 0;
    float Y = 0;
    std::vector<float> dummy;
    float XL = 0;
    string_segs(false,a_s,a_height,a_encoding,a_font,XL,Y,dummy,false);
    Y -= Y_ADVANCE;
    width = max_of<float>(width,XL);

    a_mn_x = 0;
    a_mn_y = -_descent(a_height);
    a_mn_z = 0;
    a_mx_x = width;
    a_mx_y = HEIGHT;
    a_mx_z = 0;
  }

protected:
  void update_sg() {
    clean_gstos();
    m_segs.clear();
    get_segments(m_segs);
  }

  void get_segments(std::vector<float>& a_segs) const {

    float Y = 0;
    if( (vjust.value()==sg::middle) ||
        (vjust.value()==sg::top) ){
      float mn_x,mn_y,mn_z;
      float mx_x,mx_y,mx_z;
      get_bounds(height.value(),mn_x,mn_y,mn_z,mx_x,mx_y,mx_z);
      float szy = mx_y - mn_y;

      if(vjust.value()==sg::middle) {
        Y -= 0.5F * szy;
      } else if(vjust.value()==sg::top) {
        Y -= szy;
      }
    }

    float HEIGHT = height.value();
    float Y_ADVANCE = 2 * HEIGHT;

    const std::string& encod = encoding.value();

    const std::vector<std::string>& ss = strings.values();
    tools_vforcit(std::string,ss,it) {

      float X = 0;
      if( (hjust.value()==sg::center) ||
          (hjust.value()==sg::right)  ){
        float mn_x,mn_y,mn_z;
        float mx_x,mx_y,mx_z;
        get_bounds(height,encoding.value(),font,*it,
                   mn_x,mn_y,mn_z,mx_x,mx_y,mx_z);
        float szx = mx_x - mn_x;

        if(hjust.value()==sg::center) {
          X -= 0.5F * szx;
        } else if(hjust.value()==sg::right) {
          X -= szx;
        }
      }

      string_segs(true,*it,HEIGHT,encod,font.value(),X,Y,a_segs,true);
      Y -= Y_ADVANCE;
    }
  }
protected:
  static float _descent(float a_height) {
    // '/' seems to be the char with the max descent.
    float mn_x,mn_y,mn_z;
    float mx_x,mx_y,mx_z;
    get_char_bound('/',sg::latin,a_height,false,
                   mn_x,mn_y,mn_z,
                   mx_x,mx_y,mx_z);
    return -mn_y; //return then a positive number.
  }

  static bool _truncate(const std::string& a_string,
                        float a_height,
                        font_type a_font,float a_cut_width,
                        std::string& a_out) {
    //It does not take into account encoding.

    a_out.clear();

    float width = 0;

    const unsigned int mx_poly = 4;
    const unsigned int mx_point = 160;

    int max_point[mx_poly];
    float xp[mx_point];
    float yp[mx_point];

    for(auto it = a_string.cbegin(); it != a_string.cend(); ++it) {
      float cwidth;
      int number;
      if (a_font==sg::greek) {
        hershey::greek_char_points(*it,a_height,number,max_point,xp,yp,cwidth);
      } else if (a_font==sg::special) {
        hershey::special_char_points(*it,a_height,number,max_point,xp,yp,cwidth);
      } else {
        hershey::latin_char_points(*it,a_height,number,max_point,xp,yp,cwidth);
      }

      float advance = cwidth + a_height * 0.01F;

      if((width+cwidth)>=a_cut_width) return true;
      a_out += *it;
      width += advance;
    }

    return true;
  }

  static void get_char_bound(char a_char,
                             font_type a_font,
                             float a_scale,bool a_bar,
                             float& a_mn_x,float& a_mn_y,float& a_mn_z,
                             float& a_mx_x,float& a_mx_y,float& a_mx_z){
    box_3f_make_empty(a_mn_x,a_mn_y,a_mn_z,a_mx_x,a_mx_y,a_mx_z);

    const unsigned int mx_poly = 4;
    const unsigned int mx_point = 160;

    int max_point[mx_poly];
    float xp[mx_point];
    float yp[mx_point];

    int number;
    float width;
    if (a_font==sg::greek) {
      hershey::greek_char_points(a_char,a_scale,number,max_point,xp,yp,width);
    } else if (a_font==sg::special) {
      hershey::special_char_points(a_char,a_scale,number,max_point,xp,yp,width);
    } else {
      hershey::latin_char_points(a_char,a_scale,number,max_point,xp,yp,width);
    }

    float ymax = 0;

    int ipoint = 0;
    for (int ipoly=0;ipoly<number;ipoly++) {
      int pointn  = max_point[ipoly];
      if(pointn>0) {
        for(int count=0;count<pointn-1;count++) {
          ymax = max_of<float>(ymax,yp[ipoint]);
          box_3f_extend_by(a_mn_x,a_mn_y,a_mn_z,a_mx_x,a_mx_y,a_mx_z,xp[ipoint],yp[ipoint],0);

          ymax = max_of<float>(ymax,yp[ipoint+1]);
          box_3f_extend_by(a_mn_x,a_mn_y,a_mn_z,a_mx_x,a_mx_y,a_mx_z,xp[ipoint+1],yp[ipoint+1],0);

          ipoint ++;
        }
        ipoint ++;
      }
    }

    if(a_bar) { //Draw a bar on top of the character.
      float xbar = 0;
      float ybar = ymax * 1.3F;
      box_3f_extend_by(a_mn_x,a_mn_y,a_mn_z,a_mx_x,a_mx_y,a_mx_z,xbar,ybar,0);
      box_3f_extend_by(a_mn_x,a_mn_y,a_mn_z,a_mx_x,a_mx_y,a_mx_z,xbar+width,ybar,0);
    }
  }

  static void string_segs(
   bool aGEN_POINTS // false = advance only.
  ,const std::string& a_string
  ,float a_height
  ,const std::string& a_encoding
  ,font_type a_font
  ,float& aX
  ,float& aY
  ,std::vector<float>& a_segs
  ,bool a_fill_segs
  ){
    float oldX = 0;
    float HEIGHT = a_height;

    bool encod_PAW = (a_encoding==encoding_PAW()?true:false);

    sencoded sed;
    if(encod_PAW) decode_PAW(a_string,sed);
    else          decode_plain(a_string,sed);

    tools_vforcit(hchar,sed,it) {
      const hchar& hc = *it;

      font_type hershey_font = hc.m_font;
      if(encod_PAW) {
        hershey_font = hc.m_font;
      } else {
        hershey_font = a_font;
      }

      float scale = HEIGHT;
      float ymove = 0;
      if(hc.m_y_move==hchar::up) {
        scale = HEIGHT*0.6F;
        ymove = HEIGHT*0.6F;
      } else if(hc.m_y_move==hchar::down) {
        scale = HEIGHT*0.6F;
        ymove = -HEIGHT*0.6F;
      }
      if(hc.m_back) aX = oldX;
      oldX = aX;
      //FIXME : bar
      aY += ymove;

      float advance = char_segs(aGEN_POINTS,hc.m_char,hershey_font,scale,hc.m_bar,aX,aY,a_segs,a_fill_segs) + HEIGHT * 0.01F;

      aX += advance;

      aY -= ymove;
    }
  }

  static float char_segs(
   bool aGEN_POINTS // false = advance only.
  ,char a_char
  ,font_type a_font
  ,float a_scale
  ,bool aBar
  ,float aX
  ,float aY
  ,std::vector<float>& a_segs
  ,bool a_fill_segs
  ){
    const unsigned int mx_poly = 8;
    const unsigned int mx_point = 160;

    int max_point[mx_poly];
    float xp[mx_point];
    float yp[mx_point];

    int number;
    float width;
    if (a_font==sg::greek) {
      hershey::greek_char_points(a_char,a_scale,number,max_point,xp,yp,width);
    } else if (a_font==sg::special) {
      hershey::special_char_points(a_char,a_scale,number,max_point,xp,yp,width);
    } else {
      hershey::latin_char_points(a_char,a_scale,number,max_point,xp,yp,width);
    }
    if(!aGEN_POINTS) return width;

    float ymax = 0;

    int ipoint = 0;
    int pointn;
    for (int ipoly=0;ipoly<number;ipoly++) {
      pointn  = max_point[ipoly];
      if(pointn>0) {
        for(int count=0;count<pointn-1;count++) {
          ymax = max_of<float>(ymax,yp[ipoint]);
          if(a_fill_segs) {
            a_segs.push_back(aX+xp[ipoint]);
            a_segs.push_back(aY+yp[ipoint]);
          }
          ymax = max_of<float>(ymax,yp[ipoint+1]);
          if(a_fill_segs) {
            a_segs.push_back(aX+xp[ipoint+1]);
            a_segs.push_back(aY+yp[ipoint+1]);
          }
          ipoint ++;
        }
        ipoint ++;
      }
    }

    if(aBar) { //Draw a bar on top of the character.
      float xbar = 0;
      float ybar = ymax * 1.3F;

      if(a_fill_segs) {
        a_segs.push_back(aX+xbar);
        a_segs.push_back(aY+ybar);

        a_segs.push_back(aX+xbar+width);
        a_segs.push_back(aY+ybar);
      }
    }

    return width;
  }

  typedef std::vector<hchar> sencoded;

  static void decode_plain(const std::string& a_s,sencoded& a_sed){
    a_sed.clear();
    for(auto it = a_s.cbegin(); it != a_s.cend(); ++it) {
      hchar hc;
      hc.m_char = *it;
      a_sed.push_back(hc);
    }
    if(a_sed.size()) a_sed[a_sed.size()-1].m_cr = true;
  }
  ///////////////////////////////////////////////////////////////////////////
  /// PAW encoding //////////////////////////////////////////////////////////
  ///////////////////////////////////////////////////////////////////////////
  // PAW control characters :
  //  [ go to greek (roman = default)
  //  ] end of greek
  //  " go to special
  //  # end of special
  //  ! go to normal level of script
  //  ^ go to superscript
  //  ? go to subscript
  //  & backscpace one charachter
  //  < go to lower case
  //  > go to upper case (default)
  // Extension :
  //  | draw a bar over one character
  // Found in PAW manual Version 1.14 (July 1992), page 178, 180.
  ///////////////////////////////////////////////////////////////////////////
  static void decode_PAW(const std::string& a_s,sencoded& a_sed){
    a_sed.clear();

    font_type hershey_font = sg::latin;
    hchar::e_move move = hchar::none;
    bool back = false;
    bool bar = false;
  //bool upper = true; //to be done.

    for(auto it = a_s.cbegin(); it != a_s.cend(); ++it) {
      char c = *it;
      // Control characters :
      if(c=='[') {
        hershey_font = sg::greek;
        continue;
      } else if(c==']') {
        hershey_font = sg::latin;
        continue;
      } else if(c=='"') {
        hershey_font = sg::special;
        continue;
      } else if(c=='#') {
        hershey_font = sg::latin;
        continue;
      } else if(c=='!') {
        move = hchar::none;
        continue;
      } else if(c=='^') {
        move = hchar::up;
        continue;
      } else if(c=='?') {
        move = hchar::down;
        continue;
      } else if(c=='&') {
        back = true;
        continue;
      } else if(c=='<') {
        //upper = false;
        continue;
      } else if(c=='>') {
        //upper = true;
        continue;
      }

      hchar hc;
      hc.m_y_move = move;
      hc.m_back = back;
      hc.m_bar = bar;
      hc.m_font = hershey_font;
      hc.m_char = c;

      a_sed.push_back(hc);

      back = false;
      bar = false;
    }

    if(a_sed.size()) a_sed[a_sed.size()-1].m_cr = true;
  }

protected:
  std::vector<float> m_segs; //list of [begin,end]
  size_t m_gsto_sz;
};

}}

#endif
