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

#ifndef tools_sg_vertices
#define tools_sg_vertices

#include "node"
#include "gstos"

#include "sf"
#include "mf"
#include "render_action"
#include "pick_action"
#include "bbox_action"
#include "visible_action"

#include "../vmanip"

namespace tools {
namespace sg {

class vertices : public node, public gstos {
  TOOLS_NODE(vertices,tools::sg::vertices,node)
  typedef gstos parent_gstos;
public:
  sf<gl::mode_t> mode;
  mf<float> xyzs;
public:
  virtual const desc_fields& node_desc_fields() const {
    TOOLS_FIELD_DESC_NODE_CLASS(tools::sg::vertices)
    static const desc_fields s_v(parent::node_desc_fields(),2, //WARNING : take care of count.
      TOOLS_ARG_FIELD_DESC(mode),
      TOOLS_ARG_FIELD_DESC(xyzs)
    );
    return s_v;
  }
private:
  void add_fields(){
    add_field(&mode);
    add_field(&xyzs);
  }
protected: //gstos
  virtual unsigned int create_gsto(std::ostream&,sg::render_manager& a_mgr) {
    //unsigned int npt = xyzs.values().size()/3;
    //::printf("debug : vertices : %lu : create_gsto : %u\n",this,npt);
    return a_mgr.create_gsto_from_data(xyzs.values());
  }

public:
  virtual void render(render_action& a_action) {
    if(touched()) {clean_gstos();reset_touched();}
    if(xyzs.empty()) return;

    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) {
#ifdef __APPLE__
        bool restore_blend = check_set_blend(a_action);
#endif
        a_action.begin_gsto(_id);
        size_t npt = xyzs.values().size()/3;
        bufpos pos = 0;
        if(gl::is_line(mode.value())) {
          //Same logic as Inventor SoLightModel.model = BASE_COLOR.
          a_action.set_lighting(false);
          a_action.draw_gsto_v(mode.value(),npt,pos);
          a_action.set_lighting(state.m_GL_LIGHTING);
        } else {
          a_action.draw_gsto_v(mode.value(),npt,pos);
        }
        a_action.end_gsto();
#ifdef __APPLE__
        if(restore_blend) a_action.set_blend(true);
#endif
        return;
      } else { //!_id
        // use immediate rendering.
      }

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

#ifdef __APPLE__
    bool restore_blend = check_set_blend(a_action);
#endif

    // immediate rendering :
    if(gl::is_line(mode.value())) {
      //Same logic as Inventor SoLightModel.model = BASE_COLOR.
      a_action.set_lighting(false);
      a_action.draw_vertex_array(mode.value(),xyzs.values());
      a_action.set_lighting(state.m_GL_LIGHTING);
    } else {
      a_action.draw_vertex_array(mode.value(),xyzs.values());
    }

#ifdef __APPLE__
    if(restore_blend) a_action.set_blend(true);
#endif
  }
  virtual void pick(pick_action& a_action) {
    if(touched()) {clean_gstos();reset_touched();}
    a_action.add__primitive(*this,mode.value(),xyzs.values(),true);
  }

  virtual void bbox(bbox_action& a_action) {
    if(touched()) {clean_gstos();reset_touched();}
    a_action.add_points(xyzs.values());
  }
  virtual void is_visible(visible_action& a_action) {
    if(touched()) {clean_gstos();reset_touched();}
    if(_is_visible(a_action)) a_action.increment();
  }

public:
  vertices()
  :parent()
  ,mode(gl::line_strip()){
#ifdef TOOLS_MEM
    mem::increment(s_class().c_str());
#endif
    add_fields();
  }
  virtual ~vertices(){
#ifdef TOOLS_MEM
    mem::decrement(s_class().c_str());
#endif
  }
public:
  vertices(const vertices& a_from)
  :parent(a_from)
  ,parent_gstos(a_from)
  ,mode(a_from.mode)
  ,xyzs(a_from.xyzs)
  {
#ifdef TOOLS_MEM
    mem::increment(s_class().c_str());
#endif
    add_fields();
  }
  vertices& operator=(const vertices& a_from){
    parent::operator=(a_from);
    parent_gstos::operator=(a_from);

    mode = a_from.mode;
    xyzs = a_from.xyzs;

    return *this;
  }
public:
  template <class VEC>
  void add(const VEC& a_v) {
    xyzs.add(a_v.x());
    xyzs.add(a_v.y());
    xyzs.add(a_v.z());
  }
  void add(float a_x,float a_y,float a_z) {
    xyzs.add(a_x);
    xyzs.add(a_y);
    xyzs.add(a_z);
  }
  void add_allocated(size_t& a_pos,float a_x,float a_y,float a_z) {
    std::vector<float>& v = xyzs.values();
    v[a_pos] = a_x;a_pos++;
    v[a_pos] = a_y;a_pos++;
    v[a_pos] = a_z;a_pos++;
    xyzs.touch();
  }
  bool add(const std::vector<float>& a_v) {
    std::vector<float>::size_type _number = a_v.size()/3;
    if(3*_number!=a_v.size()) return false;
    std::vector<float>::const_iterator it;
    for(it=a_v.begin();it!=a_v.end();it+=3) {
      xyzs.add(*(it+0));
      xyzs.add(*(it+1));
      xyzs.add(*(it+2));
    }
    return true;
  }

  size_t number() const {return xyzs.size()/3;}
  void clear() {xyzs.clear();}

  bool add_dashed_line(float a_bx,float a_by,float a_bz,
                       float a_ex,float a_ey,float a_ez,
                       unsigned int a_num_dash) {
    //optimized version.
    if(!a_num_dash) return false;
    // there is a dash at beg and end of line.

    float fac = 1.0f/float(2*a_num_dash-1);
    float sx = (a_ex-a_bx)*fac;
    float sy = (a_ey-a_by)*fac;
    float sz = (a_ez-a_bz)*fac;

    float two_sx = sx*2.0f;
    float two_sy = sy*2.0f;
    float two_sz = sz*2.0f;

    float px = a_bx;
    float py = a_by;
    float pz = a_bz;

    for(unsigned int idash=0;idash<a_num_dash;idash++) {
      add(px,py,pz);
      add(px+sx,py+sy,pz+sz);
      px += two_sx;
      py += two_sy;
      pz += two_sz;
    }
    return true;
  }
  bool center() {
    std::vector<float>& v = xyzs.values();
    std::vector<float>::size_type _number = v.size()/3;
    if(!_number) return true;
    if(3*_number!=v.size()) return false;
    float x_mean = 0;
    float y_mean = 0;
    float z_mean = 0;
   {for(std::vector<float>::const_iterator it=v.begin();it!=v.end();it+=3) {
      x_mean += *(it+0);
      y_mean += *(it+1);
      z_mean += *(it+2);
    }}
    x_mean /= float(_number);
    y_mean /= float(_number);
    z_mean /= float(_number);
   {for(std::vector<float>::iterator it=v.begin();it!=v.end();it+=3) {
      *(it+0) -= x_mean;
      *(it+1) -= y_mean;
      *(it+2) -= z_mean;
    }}
    return true;
  }
protected:
  bool _is_visible(const matrix_action& a_action) {
    if(xyzs.empty()) return false;
    const state& _state = a_action.state();
    pick_action action(a_action.out(),_state.m_ww,_state.m_wh,0,float(_state.m_ww),0,float(_state.m_wh));
    action.set_win_size(_state.m_ww,_state.m_wh);
    action.set_area(0,float(_state.m_ww),0,float(_state.m_wh));
    action.set_stop_at_first(true);
    action.matrix_action::operator=(a_action); //IMPORTANT.
    int old_cur = action.cur(); //not 0.
    action.add__primitive(*this,mode.value(),xyzs.values(),true);
    if(action.cur()!=old_cur) return false;
    if(!action.node()) return false;
    return true;
  }
#ifdef __APPLE__
protected:
  // macOS/Mojave : on this version, points are blended even if alpha is one !
  bool check_set_blend(render_action& a_action) {
    bool restore_blend = false;
    const state& state = a_action.state();
    if(state.m_GL_BLEND) {
      if(state.m_color.a()==1) {
        a_action.set_blend(false);
        restore_blend = true;
      }
    }
    return restore_blend;
  }
#endif //__APPLE__
};

}}

#endif
