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

#ifndef toolx_sg_GL_manager
#define toolx_sg_GL_manager

#ifndef GL_VERSION_1_1
  #error You have to include OpenGL header(s) before including this file.
#endif

#include <tools/sg/render_manager>
#include <tools/mapmanip>
#include <tools/carray>
#include <tools/num2s>
#ifdef TOOLS_USE_GL_VERSION_3_2
#include <tools/lina/mat4f>
#endif

#include <sstream>

#include <cmath> //::sqrt


namespace toolx {
namespace sg {

template <class GL_FUNCTIONS>
class gsto_elem: protected GL_FUNCTIONS {
  TOOLS_T_SCLASS(GL_FUNCTIONS,toolx::sg::gsto_elem)
  using parent_gl_functions = GL_FUNCTIONS;
public:
  enum kind {
    kind_texture,
    kind_buffer,
    kind_list,
    kind_memory
  };
public:
#ifdef TOOLS_USE_GL_VERSION_3_2
  gsto_elem(kind a_kind,unsigned int a_gl_id,size_t a_size,const float* a_data,unsigned int a_vao_id = 0)
  :m_gl_id(a_gl_id)
  ,m_vao_id(a_vao_id)
#else
  gsto_elem(kind a_kind,unsigned int a_gl_id,size_t a_size,const float* a_data)
  :m_gl_id(a_gl_id)
#endif
  ,m_kind(a_kind)
  ,m_size(a_size)
  ,m_data(0)
  {
    if(a_data) {
      size_t num = m_size/sizeof(float);
      m_data = new float[num];
      ::memcpy(m_data,a_data,m_size);
    }
  }
  virtual ~gsto_elem() {
    if(m_kind==kind_texture) {
      parent_gl_functions::gl__DeleteTextures(1,&m_gl_id);
    } else if(m_kind==kind_buffer) {
#ifdef TOOLS_USE_GL_VERSION_3_2
      parent_gl_functions::gl__DeleteBuffers(1,&m_gl_id);
      parent_gl_functions::gl__DeleteVertexArrays(1,&m_vao_id);
#endif
    } else if(m_kind==kind_list) {
      if(m_gl_id) {
#ifndef TOOLS_USE_GL_VERSION_3_2
        parent_gl_functions::gl__DeleteLists(m_gl_id,1);
#endif
      }
    }

    if(m_data) {
      delete [] m_data;
    }
  }
private:
  gsto_elem(const gsto_elem& a_from)
  :parent_gl_functions(a_from)
  ,m_gl_id(a_from.m_gl_id)
#ifdef TOOLS_USE_GL_VERSION_3_2
  ,m_vao_id(a_from.m_vao_id)
#endif
  ,m_kind(a_from.m_kind)
  ,m_size(a_from.m_size)
  ,m_data(0)
  {
    if(a_from.m_data) {
      size_t num = m_size/sizeof(float);
      m_data = new float[num];
      ::memcpy(m_data,a_from.m_data,m_size);
    }
  }
  gsto_elem& operator=(const gsto_elem& a_from){
    if(&a_from==this) return *this;
    m_gl_id = a_from.m_gl_id;
#ifdef TOOLS_USE_GL_VERSION_3_2
    m_vao_id = a_from.m_vao_id;
#endif
    m_kind = a_from.m_kind;
    m_size = a_from.m_size;
    if(m_data) {
      delete [] m_data;
      m_data = 0;
    }
    if(a_from.m_data) {
      size_t num = m_size/sizeof(float);
      m_data = new float[num];
      ::memcpy(m_data,a_from.m_data,m_size);
    }
    return *this;
  }
public:
  bool is_valid() /*const*/ {
    if(!parent_gl_functions::initialize()) return false;
    if(m_kind==kind_texture) {
      return (parent_gl_functions::gl__IsTexture(m_gl_id)==GL_TRUE?true:false);
    } else if(m_kind==kind_buffer) {
#ifdef TOOLS_USE_GL_VERSION_3_2
      return (parent_gl_functions::gl__IsBuffer(m_gl_id)==GL_TRUE?true:false); //If ok, then VAO is ok too.
#endif
    } else if(m_kind==kind_list) {
#ifndef TOOLS_USE_GL_VERSION_3_2
      return (parent_gl_functions::gl__IsList(m_gl_id)==GL_TRUE?true:false);
#endif
    } else if(m_kind==kind_memory) {
      return true;
    }
    return false;
  }
  void bind() /*const*/ {
    parent_gl_functions::initialize();
    if(m_kind==kind_texture) {
      parent_gl_functions::gl__BindTexture(GL_TEXTURE_2D,m_gl_id);
    } else if(m_kind==kind_buffer) {
#ifdef TOOLS_USE_GL_VERSION_3_2
      parent_gl_functions::gl__BindVertexArray(m_vao_id);
      parent_gl_functions::gl__BindBuffer(GL_ARRAY_BUFFER,m_gl_id);
#endif
    }
  }
public:
  unsigned int m_gl_id;
#ifdef TOOLS_USE_GL_VERSION_3_2
  unsigned int m_vao_id;
#endif
  kind m_kind;
  size_t m_size;
  float* m_data;
};

template <class GL_FUNCTIONS>
class GL_manager_T : public virtual tools::sg::render_manager, protected GL_FUNCTIONS {
  typedef tools::sg::render_manager parent;
  using parent_gl_functions = GL_FUNCTIONS;
  typedef gsto_elem<GL_FUNCTIONS> gsto_t;
  typedef typename  std::map<unsigned int, gsto_t*>::const_iterator gstos_it_t;
public:
  TOOLS_T_SCLASS(GL_FUNCTIONS,toolx::sg::GL_manager_T)
  virtual void* cast(const std::string& a_class) const {
    if(void* p = tools::cmp_cast<GL_manager_T>(this,a_class)) {return p;}
    else return 0;
  }
public:
  virtual bool begin_render(int a_x,int a_y,unsigned int a_ww,unsigned int a_wh,
                            float a_r,float a_g,float a_b,float a_a,bool a_clear = true) {

    if (!parent_gl_functions::initialize()) {
      m_out << "toolx::sg::GL_manager_T::begin_render: parent_gl_functions::initialize() failed." << std::endl;
      return false;
    }

    if(m_gl_version.empty()) {
     {m_gl_version.clear();
      const char* _sv = (const char*)parent_gl_functions::gl__GetString(GL_VERSION);
      if(_sv) m_gl_version = _sv;}
#ifdef TOOLS_USE_GL_VERSION_3_2
     {m_gl_shading_language_version.clear();
      const char* _sv = (const char*)parent_gl_functions::gl__GetString(GL_SHADING_LANGUAGE_VERSION);
      if(_sv) m_gl_shading_language_version = _sv;}
#endif
     {m_gl_vendor.clear();
      const char* _sv = (const char*)parent_gl_functions::gl__GetString(GL_VENDOR);
      if(_sv) m_gl_vendor = _sv;}
     {m_gl_renderer.clear();
      const char* _sv = (const char*)parent_gl_functions::gl__GetString(GL_RENDERER);
      if(_sv) m_gl_renderer = _sv;}
     {m_gl_extensions.clear();
      const char* _sv = (const char*)parent_gl_functions::gl__GetString(GL_EXTENSIONS);
      if(_sv) m_gl_extensions = _sv;}
      m_gl_max_texture_size = 0;
      parent_gl_functions::gl__GetIntegerv(GL_MAX_TEXTURE_SIZE, &m_gl_max_texture_size);
    }

    clear_gl_errors();

#ifdef TOOLS_USE_GL_VERSION_3_2

    // to avoid a 0x506 error message :
    if(parent_gl_functions::gl__CheckFramebufferStatus(GL_FRAMEBUFFER)!=GL_FRAMEBUFFER_COMPLETE) {
      return false;
    }

    if(!create_shader_program()) {
      dump_if_gl_errors(m_out,"toolx::sg::GL_manager_T::create_shader_program :");
      m_out << "toolx::sg::GL_manager_T::begin_render : create_shader_program() failed." << std::endl;
      return false;
    }
    use_shader_program();

    // WARNING : the values set here must match the default values in sg::state.

    //WARNING : the below glEnable/glDisable corresponds
    //          to defaults in tools::sg::state.
    parent_gl_functions::gl__Enable(GL_DEPTH_TEST);
    parent_gl_functions::gl__FrontFace(GL_CCW);
    parent_gl_functions::gl__Enable(GL_CULL_FACE);
    parent_gl_functions::gl__Disable(GL_POLYGON_OFFSET_FILL);

    parent_gl_functions::gl__DepthFunc(GL_LESS);
    parent_gl_functions::gl__DepthMask(GL_TRUE);

    parent_gl_functions::gl__Disable(GL_BLEND); //must be in sync with tools::sg::state.m_GL_BLEND and sg::blend node default.
    parent_gl_functions::gl__BlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

    parent_gl_functions::gl__LineWidth(1.0f);
    parent_gl_functions::gl__Uniform1f(g_point_size_location(),1.0f);

    parent_gl_functions::gl__Viewport(a_x,a_y,a_ww,a_wh);

    if(a_clear) {
      parent_gl_functions::gl__ClearColor(a_r,a_g,a_b,a_a);
      parent_gl_functions::gl__Clear(GL_COLOR_BUFFER_BIT);
      parent_gl_functions::gl__Clear(GL_DEPTH_BUFFER_BIT);
    }

    tools::mat4f ID4;ID4.set_identity();
    parent_gl_functions::gl__UniformMatrix4fv(g_model_proj_matrix_location(),1,GL_FALSE,ID4.data());
    parent_gl_functions::gl__UniformMatrix4fv(g_normal_matrix_location(),1,GL_FALSE,ID4.data());

#else //not TOOLS_USE_GL_VERSION_3_2

#ifdef __APPLE__
#ifdef __COREFOUNDATION__  //Cocoa.h included: we build a Cocoa application.
    // to avoid a 0x506 error message :
    if(parent_gl_functions::gl__CheckFramebufferStatus(GL_FRAMEBUFFER)!=GL_FRAMEBUFFER_COMPLETE) { //It is a GL_VERSION_3_0 function.
      return false;
    }
#endif
#endif

    // WARNING : the values set here must match the default values in sg::state.


    parent_gl_functions::gl__Disable(GL_POLYGON_STIPPLE); //CoinGL : reading a .wrl having Material::transparency may enable GL_POLYGON_STIPPLE.

    parent_gl_functions::gl__Enable(GL_NORMALIZE);
    parent_gl_functions::gl__ShadeModel(GL_FLAT);
    parent_gl_functions::gl__Enable(GL_COLOR_MATERIAL);


    parent_gl_functions::gl__Disable(GL_BLEND); //must be in sync with tools::sg::state.m_GL_BLEND and sg::blend node default.
    parent_gl_functions::gl__BlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

    //WARNING : the below glEnable/glDisable corresponds
    //          to defaults in tools::sg::state.
    parent_gl_functions::gl__Enable(GL_DEPTH_TEST);
    parent_gl_functions::gl__Disable(GL_LIGHTING);
    parent_gl_functions::gl__FrontFace(GL_CCW);
    parent_gl_functions::gl__Enable(GL_CULL_FACE);
    parent_gl_functions::gl__Disable(GL_POLYGON_OFFSET_FILL);
    parent_gl_functions::gl__Disable(GL_TEXTURE_2D);

    parent_gl_functions::gl__DepthFunc(GL_LESS);
    parent_gl_functions::gl__DepthMask(GL_TRUE);

    parent_gl_functions::gl__Disable(GL_POINT_SMOOTH);
    parent_gl_functions::gl__PointSize(1);

    parent_gl_functions::gl__Disable(GL_LINE_SMOOTH); //NOTE : it does not work on all platforms !
    parent_gl_functions::gl__LineWidth(1);

    parent_gl_functions::gl__Viewport(a_x,a_y,a_ww,a_wh);

    if(a_clear) {
      parent_gl_functions::gl__ClearColor(a_r,a_g,a_b,0);
      parent_gl_functions::gl__Clear(GL_COLOR_BUFFER_BIT);
      parent_gl_functions::gl__Clear(GL_DEPTH_BUFFER_BIT);
    }

    parent_gl_functions::gl__MatrixMode(GL_PROJECTION);
    parent_gl_functions::gl__LoadIdentity();

    parent_gl_functions::gl__MatrixMode(GL_MODELVIEW);
    parent_gl_functions::gl__LoadIdentity();


   {parent_gl_functions::gl__Color4f(a_r,a_g,a_b,::sqrt(a_a));
    float xyzs[12];
    xyzs[0] = -1;xyzs[ 1] = -1;xyzs[ 2] = 0;
    xyzs[3] =  1;xyzs[ 4] = -1;xyzs[ 5] = 0;
    xyzs[6] =  1;xyzs[ 7] =  1;xyzs[ 8] = 0;
    xyzs[9] = -1;xyzs[10] =  1;xyzs[11] = 0;
    parent_gl_functions::gl__Disable(GL_DEPTH_TEST);
    parent_gl_functions::gl__EnableClientState(GL_VERTEX_ARRAY);
    parent_gl_functions::gl__VertexPointer(3,GL_FLOAT,0,xyzs);
    parent_gl_functions::gl__DrawArrays(GL_TRIANGLE_FAN,0,4);
    parent_gl_functions::gl__DisableClientState(GL_VERTEX_ARRAY);
    parent_gl_functions::gl__Enable(GL_DEPTH_TEST);}

#endif //not TOOLS_USE_GL_VERSION_3_2
    return true;
  }

  virtual void end_render() {
#ifdef TOOLS_USE_GL_VERSION_3_2
    reset_shader_locations();
#endif
    dump_if_gl_errors(m_out,"toolx::sg::GL_manager_T::end_render :");
  }

  ////////////////////////////////////////////////
  /// texture : //////////////////////////////////
  ////////////////////////////////////////////////
  virtual unsigned int create_texture(const tools::img_byte& a_img,bool a_NEAREST) {

    tools::img_byte* _img = 0;
    bool _img_delete = false;

    if(a_img.bytes_per_pixel()==1) { //exa eiffel-tower.png
      _img = new tools::img_byte;
      _img_delete = true;
      if(!a_img.bw2x(3,*_img)) {
        m_out << "toolx::sg::GL_manager_T::create_texture :"
              << " a_imgr.bw2x() failed."
              << std::endl;
        delete _img;
        return 0;
      }
    } else if(a_img.bytes_per_pixel()==3) {
      _img = const_cast<tools::img_byte*>(&a_img);
    } else if(a_img.bytes_per_pixel()==4) { //exa windusrf.png, text_freetype/font_pixmap.
      _img = const_cast<tools::img_byte*>(&a_img);
    } else {
      //should not happen.
      m_out << "toolx::sg::GL_manager_T::create_texture :"
            << " image with bytes_per_pixel " << a_img.bytes_per_pixel() << " not (yet) supported."
            << std::endl;
      return 0;
    }
    if(_img->is_empty()) {
      m_out << "toolx::sg::GL_manager_T::create_texture : img_byte is empty." << std::endl;
      if(_img_delete) delete _img;
      return 0;
    }

   {int sz;
    parent_gl_functions::gl__GetIntegerv(GL_MAX_TEXTURE_SIZE,&sz);
    if(!sz) {
      m_out << "toolx::sg::GL_manager_T::create_texture : warning : GL_MAX_TEXTURE_SIZE is zero." << std::endl;
    } else {
      tools::img_byte* res = new tools::img_byte;
      if(_img->check_gl_limit(sz,*res)) {
        if(res->is_empty()) { //_img does not exceed.
          delete res;
        } else {
          m_out << "toolx::sg::GL_manager_T::create_texture:"
	        << " warning : img size (" << _img->size() << ") > GL_MAX_TEXTURE_SIZE (" << sz << "), we use a reduced one." << std::endl;
          if(_img_delete) delete _img;
	  _img = res;
          _img_delete = true;
        }
      } else {
        m_out << "toolx::sg::GL_manager_T::create_texture :"
              << " warning : img size (" << _img->size() << ") > GL_MAX_TEXTURE_SIZE (" << sz << ") but can't reduce it."
              << std::endl;
        delete res;
        if(_img_delete) delete _img;
	return false;
      }
    }}
    if(_img->is_empty()) {
      m_out << "toolx::sg::GL_manager_T::create_texture : img_byte is empty." << std::endl;
      if(_img_delete) delete _img;
      return 0;
    }

    unsigned int gl_id;
    parent_gl_functions::gl__GenTextures(1,&gl_id);
    if(!gl_id) {
      m_out << "toolx::sg::GL_manager_T::create_texture : glGenTextures failed ()." << std::endl;
      if(_img_delete) delete _img;
      return 0;
    }

    unsigned int gsto_size = _img->size();
    if(_img->bytes_per_pixel()==3) {
      parent_gl_functions::gl__BindTexture(GL_TEXTURE_2D,gl_id);
      parent_gl_functions::gl__TexImage2D(GL_TEXTURE_2D,0,GL_RGB,_img->width(),_img->height(),0,GL_RGB,GL_UNSIGNED_BYTE,_img->buffer());
      if(_img_delete) delete _img;
    } else if(_img->bytes_per_pixel()==4) {
      parent_gl_functions::gl__BindTexture(GL_TEXTURE_2D,gl_id);
      parent_gl_functions::gl__TexImage2D(GL_TEXTURE_2D,0,GL_RGBA,_img->width(),_img->height(),0,GL_RGBA,GL_UNSIGNED_BYTE,_img->buffer());
      if(_img_delete) delete _img;
    } else {
      m_out << "toolx::sg::GL_manager_T::create_texture : img.bytes_per_pixel() " << _img->bytes_per_pixel() << " not handled." << std::endl;
      if(_img_delete) delete _img;
      parent_gl_functions::gl__DeleteTextures(1,&gl_id);
      gl_id = 0;
      return false;
    }
    if(a_NEAREST) {
      parent_gl_functions::gl__TexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); //good to see astro images pixels.
      parent_gl_functions::gl__TexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); //idem.
    } else {
      parent_gl_functions::gl__TexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
      parent_gl_functions::gl__TexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    }
    parent_gl_functions::gl__BindTexture(GL_TEXTURE_2D,0);

    if(dump_if_gl_errors(m_out,"toolx::sg::GL_manager_T::create_texture :")) {
      parent_gl_functions::gl__DeleteTextures(1,&gl_id);
      gl_id = 0;
      clear_gl_errors();
      return 0;
    }

    unsigned int _id = m_gen_id;m_gen_id++;
    m_gstos[_id] = new gsto_t(gsto_t::kind_texture,gl_id,gsto_size,0);
    return _id;
  }

  ////////////////////////////////////////////////
  /// gsto ///////////////////////////////////////
  ////////////////////////////////////////////////
  virtual unsigned int create_gsto_from_data(size_t a_floatn,const float* a_data) {
    if(!a_floatn) return 0;
    switch(m_gsto_mode) {
    case tools::sg::gsto_gl_vbo:{
#ifdef TOOLS_USE_GL_VERSION_3_2
      GLuint vao_id = 0;
      parent_gl_functions::gl__GenVertexArrays(1,&vao_id);
      if(!vao_id) {
        m_out << "toolx::sg::GL_manager_T::create_gsto_from_data : glGenVertexArray failed ()." << std::endl;
        return 0;
      }

      parent_gl_functions::gl__BindVertexArray(vao_id);
      ////////////////////////////////////////////////////////////////////
      ////////////////////////////////////////////////////////////////////
      GLuint gl_id = 0;
      parent_gl_functions::gl__GenBuffers(1,&gl_id);
      if(!gl_id) {
        parent_gl_functions::gl__DeleteVertexArrays(1,&vao_id);
        m_out << "toolx::sg::GL_manager_T::create_gsto_from_data : glGenBuffers failed ()." << std::endl;
        return 0;
      }
      parent_gl_functions::gl__BindBuffer(GL_ARRAY_BUFFER,gl_id);
      parent_gl_functions::gl__BufferData(GL_ARRAY_BUFFER,a_floatn*sizeof(float),a_data,GL_STATIC_DRAW);
      parent_gl_functions::gl__BindBuffer(GL_ARRAY_BUFFER,0);
      ////////////////////////////////////////////////////////////////////
      ////////////////////////////////////////////////////////////////////
      parent_gl_functions::gl__BindVertexArray(0);

      if(dump_if_gl_errors(m_out,"toolx::sg::GL_manager_T::create_gsto_from_data :")) {
        m_out << "toolx::sg::GL_manager_T::create_gsto_from_data :"
	      << " a_floatn = " << a_floatn << ", a_data = " << a_data
	      << ", vao_id " << vao_id << ", gl_id " << gl_id
	      << std::endl;
        parent_gl_functions::gl__DeleteBuffers(1,&gl_id);
        parent_gl_functions::gl__DeleteVertexArrays(1,&vao_id);
        clear_gl_errors();
        return 0;
      }

      unsigned int _id = m_gen_id;m_gen_id++;
      m_gstos[_id] = new gsto_t(gsto_t::kind_buffer,gl_id,a_floatn*sizeof(float),0,vao_id);
      return _id;

#else
      m_out << "toolx::sg::GL_manager_T::create_gsto_from_data :"
            << " gsto mode is gl_vbo but class not compiled with TOOLS_USE_GL_VERSION_3_2."
            << std::endl;
      return 0;
#endif
      }break;
    case tools::sg::gsto_gl_list:{
      unsigned int gl_id = 0;
      unsigned int _id = m_gen_id;m_gen_id++;
      m_gstos[_id] = new gsto_t(gsto_t::kind_list,gl_id,a_floatn*sizeof(float),a_data);
      return _id;
      }break;
    case tools::sg::gsto_memory:{
      unsigned int gl_id = 0;
      unsigned int _id = m_gen_id;m_gen_id++;
      m_gstos[_id] = new gsto_t(gsto_t::kind_memory,gl_id,a_floatn*sizeof(float),a_data);
      return _id;
      }break;
    }
    return 0;
  }

  virtual bool is_gsto_id_valid(unsigned int a_id) const {
    gstos_it_t it = m_gstos.find(a_id);
    if(it==m_gstos.end()) return false;
    return (*it).second->is_valid();
  }

  virtual void delete_gsto(unsigned int a_id){
    tools::delete_key<unsigned int,gsto_t>(m_gstos,a_id);
  }

  ////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////

  float* gsto_data(unsigned int a_id) const {
    gstos_it_t it = m_gstos.find(a_id);
    if(it==m_gstos.end()) return 0;
    return (*it).second->m_data;
  }

  unsigned int gsto_gl_list_id(unsigned int a_id,bool& a_created) {
    gstos_it_t it = m_gstos.find(a_id);
    if(it==m_gstos.end()) {a_created = false;return 0;}
    if((*it).second->m_kind!=gsto_t::kind_list) {a_created = false;return 0;}
    if((*it).second->m_gl_id) {
      a_created = false;
      return (*it).second->m_gl_id;
    } else {
#ifndef TOOLS_USE_GL_VERSION_3_2
      unsigned int _id = parent_gl_functions::gl__GenLists(1);
      if(!_id) {a_created = false;return 0;}
      a_created = true;
      (*it).second->m_gl_id = _id;
      return _id;
#else
      a_created = false;
      return 0;
#endif
    }
  }

  ////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////
  virtual tools::sg::gsto_mode get_gsto_mode() const {return m_gsto_mode;}

  virtual void set_gsto_mode(tools::sg::gsto_mode a_v) {
    if(a_v==m_gsto_mode) return;
    tools::safe_clear<unsigned int,gsto_t>(m_gstos);
    switch(a_v) {
    case tools::sg::gsto_gl_vbo:{
#ifdef TOOLS_USE_GL_VERSION_3_2
      m_gsto_mode = a_v;
#else
      m_gsto_mode = tools::sg::gsto_memory;
#endif
      }break;
    case tools::sg::gsto_gl_list:{
#ifndef TOOLS_USE_GL_VERSION_3_2
      m_gsto_mode = a_v;
#else
      m_gsto_mode = tools::sg::gsto_memory;
#endif
      }break;
    case tools::sg::gsto_memory:{
      m_gsto_mode = tools::sg::gsto_memory;
      }break;
    }
  }

  virtual void available_gsto_modes(std::vector<std::string>& a_vs) {
    a_vs.clear();
#ifdef TOOLS_USE_GL_VERSION_3_2
    a_vs.push_back(tools::sg::s_gsto_gl_vbo());
#endif
#ifndef TOOLS_USE_GL_VERSION_3_2
    a_vs.push_back(tools::sg::s_gsto_gl_list());
#endif
    a_vs.push_back(tools::sg::s_gsto_memory());
  }

  virtual void available_not_memory_gsto_mode(std::string& a_v) const {
    a_v.clear();
#ifdef TOOLS_USE_GL_VERSION_3_2
    a_v = tools::sg::s_gsto_gl_vbo();
#else
    a_v = tools::sg::s_gsto_gl_list();
#endif
  }

  virtual size_t used_texture_memory() const {
    size_t sz = 0;
    gstos_it_t it;
    for(it=m_gstos.begin();it!=m_gstos.end();++it) {
      if((*it).second->m_kind==gsto_t::kind_texture) sz += (*it).second->m_size;
    }
    return sz;
  }

  virtual size_t gstos_size() const {
    size_t sz = 0;
    gstos_it_t it;
    for(it=m_gstos.begin();it!=m_gstos.end();++it) sz += (*it).second->m_size;
    return sz;
  }

public:
  GL_manager_T(std::ostream& a_out)
  :m_out(a_out)
  ,m_gen_id(1)
  ,m_gsto_mode(tools::sg::gsto_memory)
#ifdef TOOLS_USE_GL_VERSION_3_2
  ,m_fragment_shader(0)
  ,m_vertex_shader(0)
  ,m_shader_program(0)
#endif
  ,m_gl_max_texture_size(0)
  {}
  virtual ~GL_manager_T(){
#ifdef TOOLS_USE_GL_VERSION_3_2
    delete_shader_program();
#endif
    tools::safe_clear<unsigned int,gsto_t>(m_gstos);
  }
public:
  GL_manager_T(const GL_manager_T& a_from)
  :parent(a_from)
  ,parent_gl_functions(a_from)
  ,m_out(a_from.m_out)
  ,m_gen_id(a_from.m_gen_id)
  ,m_gsto_mode(a_from.m_gsto_mode)
  ,m_gl_max_texture_size(0)
  {}
  GL_manager_T& operator=(const GL_manager_T& a_from){
    if(&a_from==this) return *this;
    m_gen_id = a_from.m_gen_id;
    m_gsto_mode = a_from.m_gsto_mode;
    tools::safe_clear<unsigned int,gsto_t>(m_gstos);
    return *this;
  }

public:
  void bind_gsto(unsigned int a_id) {
    gstos_it_t it = m_gstos.find(a_id);
    if(it==m_gstos.end()) return;
    (*it).second->bind();
  }
  void unbind_gsto() {
#ifdef TOOLS_USE_GL_VERSION_3_2
    parent_gl_functions::gl__BindBuffer(GL_ARRAY_BUFFER,0);
    parent_gl_functions::gl__BindVertexArray(0);
#endif
  }

  void delete_gstos() {
    tools::safe_clear<unsigned int,gsto_t>(m_gstos);
  }

public:

  unsigned char* get_rgbas(unsigned int a_w,unsigned int a_h) {
    unsigned char* rgbas = new unsigned char[4 * a_w * a_h];
    if(!rgbas) return 0;
    parent_gl_functions::gl__PixelStorei(GL_PACK_ALIGNMENT,1); //needed with Cocoa.
    parent_gl_functions::gl__ReadPixels(0,0,a_w,a_h,GL_RGBA,GL_UNSIGNED_BYTE,rgbas);
    return rgbas;
  }

  unsigned char* get_rgbs(unsigned int a_w,unsigned int a_h) {
    unsigned char* rgbs = new unsigned char[3 * a_w * a_h];
    if(!rgbs) return 0;
    parent_gl_functions::gl__PixelStorei(GL_PACK_ALIGNMENT,1); //needed with Cocoa.
    parent_gl_functions::gl__ReadPixels(0,0,a_w,a_h,GL_RGB,GL_UNSIGNED_BYTE,rgbs);
    return rgbs;
  }
#ifdef TOOLS_USE_GL_VERSION_3_2
  bool create_shader_program() {
    if(m_shader_program) return true; //done

    unsigned int gl_glsl_major_version = 0;
    unsigned int gl_glsl_minor_version = 0;
    if(!get_version_numbers(m_out,m_gl_shading_language_version,gl_glsl_major_version,gl_glsl_minor_version)) {
      m_out << "toolx::sg::GL_manager_T::create_shader_program :"
            << " can't get version numnbers for GL_SHADING_LANGUAGE_VERSION."
            << std::endl;
      return false;
    }
    unsigned int glsl_shader_processor_version = gl_glsl_major_version*100+gl_glsl_minor_version;
    if(glsl_shader_processor_version<150) {
      m_out << "toolx::sg::GL_manager_T::create_shader_program :"
            << " glsl_shader_processor_version is " << glsl_shader_processor_version
	    << ", but only the ones greater or equal to 150 are supported."
            << std::endl;
      return false;
    }

   {unsigned int gl_major_version = 0;
    unsigned int gl_minor_version = 0;
    if(!get_version_numbers(m_out,m_gl_version,gl_major_version,gl_minor_version)) {
      m_out << "toolx::sg::GL_manager_T::create_shader_program :"
            << " can't get version numbers for GL_VERSION."
            << std::endl;
      gl_major_version = 0;
      gl_minor_version = 0;
    }
    m_out << "toolx::sg::GL_manager_T::create_shader_program: infos:" << std::endl
          << " GL_VERSION " << gl_major_version << "." << gl_minor_version << std::endl
          << " GL_SHADING_LANGUAGE_VERSION " << gl_glsl_major_version << "." << gl_glsl_minor_version << std::endl
          << " shader processor version " << glsl_shader_processor_version
	  << std::endl;}

    ////////////////////////////////////////////////////
    ////////////////////////////////////////////////////
    static const GLchar* str_fs_150 = "\
#version 150\n\
  uniform sampler2D sampler;\n\
  in vec4 frag_color;\n\
  in float frag_tex_on;\n\
\n\
  in vec2 frag_tex_coords;\n\
  out vec4 _gl_FragColor;\n\
  void main(void) {\n\
    if(frag_tex_on==1.0) {\n\
      _gl_FragColor = frag_color*texture(sampler,vec2(frag_tex_coords.s,frag_tex_coords.t));\n\
    } else {\n\
      _gl_FragColor = frag_color;\n\
    }\n\
  }\n\
";

    m_fragment_shader = parent_gl_functions::gl__CreateShader(GL_FRAGMENT_SHADER);
    if(!m_fragment_shader) {
       m_out << "toolx::sg::GL_manager_T::create_shader_program :"
             << " glCreateShader(GL_FRAGMENT_SHADER) failed."
             << std::endl;
       return false;
    }
    char* str_fs = (char*)str_fs_150;
    parent_gl_functions::gl__ShaderSource(m_fragment_shader,1,&str_fs,NULL);
    parent_gl_functions::gl__CompileShader(m_fragment_shader);
   {GLint status;
    parent_gl_functions::gl__GetShaderiv(m_fragment_shader,GL_COMPILE_STATUS,&status);
    if(status==GL_FALSE) {
       m_out << "toolx::sg::GL_manager_T::create_shader_program :"
             << " compilation of the fragment shader failed."
             << std::endl;

      {GLsizei log_size;
       char shader_log[4096];
       parent_gl_functions::gl__GetShaderInfoLog(m_fragment_shader,1024,&log_size,shader_log);
       m_out << "toolx::sg::GL_manager_T::create_shader_program: fragment shader_log size " << log_size << std::endl;
       m_out << std::string(shader_log) << std::endl;}

       parent_gl_functions::gl__DeleteShader(m_fragment_shader);
       m_fragment_shader = 0;
       return false;
    }}

    ////////////////////////////////////////////////////
    ////////////////////////////////////////////////////
    static const GLchar* str_vs_150 = "\
#version 150\n\
  in vec3 one_pos;\n\
  in vec3 one_normal;\n\
  in vec4 one_color;\n\
  in vec2 one_tex;\n\
  uniform mat4 model_proj_matrix;\n\
  uniform mat4 normal_matrix;\n\
  uniform vec4 color;\n\
  uniform bool pos_color;\n\
  uniform vec3 normal;\n\
  uniform bool pos_normal;\n\
  uniform bool tex_on;\n\
  uniform bool light_on;\n\
  uniform vec3 light_direction;\n\
  uniform vec4 light_color;\n\
  uniform vec4 light_ambient;\n\
  uniform float point_size;\n\
\n\
  out float frag_tex_on;\n\
  out vec4 frag_color;\n\
  out vec2 frag_tex_coords;\n\
\n\
  void main(void) {\n\
    frag_tex_on = 0.0;\n\
    if(tex_on) frag_tex_on = 1.0;\n\
    frag_tex_coords = one_tex;\n\
    if(tex_on) {\n\
      frag_color = color;\n\
    } else {\n\
      frag_color = color;\n\
      if(pos_color) frag_color = one_color;\n\
    }\n\
  /*frag_color = clamp(frag_color,0,1);*/\n\
\n\
    if(light_on) {\n\
      vec3 _normal = normal;\n\
      if(pos_normal) _normal = one_normal;\n\
      vec3 frag_normal = vec3(normalize(normal_matrix*vec4(_normal,0)));\n\
      /* at this point, light_direction should be normalized.*/\n\
      vec4 ambient_color = light_ambient;\n\
    /*ambient_color = clamp(ambient_color,0,1);*/\n\
      float cooking = 1.4; /*to have same intensity as GL-1.1.*/\n\
      float _dot = dot(frag_normal,light_direction);\n\
      if(_dot<0.0) {\n\
        _dot *= -1.0;\n\
        vec4 diffuse_color = light_color*_dot*cooking;\n\
      /*diffuse_color = clamp(diffuse_color,0,1);*/\n\
        vec4 _color = (ambient_color+diffuse_color)*frag_color;\n\
        frag_color = vec4(_color.rgb,frag_color.a);\n\
      } else {\n\
        frag_color = ambient_color*frag_color*cooking;\n\
      }\n\
    }\n\
    gl_PointSize = point_size;\n\
    gl_Position = model_proj_matrix * vec4(one_pos,1);\n\
  }\n\
";

    m_vertex_shader = parent_gl_functions::gl__CreateShader(GL_VERTEX_SHADER);
    if(!m_vertex_shader) {
       m_out << "toolx::sg::GL_manager_T::create_shader_program :"
             << " glCreateShader(GL_VERTEX_SHADER) failed."
             << std::endl;
       parent_gl_functions::gl__DeleteShader(m_fragment_shader);
       m_fragment_shader = 0;
       return false;
    }
    char* str_vs = (char*)str_vs_150;
    parent_gl_functions::gl__ShaderSource(m_vertex_shader,1,&str_vs,NULL);
    parent_gl_functions::gl__CompileShader(m_vertex_shader);
   {GLint status;
    parent_gl_functions::gl__GetShaderiv(m_vertex_shader,GL_COMPILE_STATUS,&status);
    if(status==GL_FALSE) {
       m_out << "toolx::sg::GL_manager_T::create_shader_program :"
             << " compilation of the vertex shader failed."
             << std::endl;

      {GLsizei log_size;
       char shader_log[4096];
       parent_gl_functions::gl__GetShaderInfoLog(m_fragment_shader,1024,&log_size,shader_log);
       m_out << "toolx::sg::GL_manager_T::create_shader_program: vertex shader_log size " << log_size << std::endl;
       m_out << std::string(shader_log) << std::endl;}

       parent_gl_functions::gl__DeleteShader(m_vertex_shader);
       parent_gl_functions::gl__DeleteShader(m_fragment_shader);
       m_vertex_shader = 0;
       m_fragment_shader = 0;
       return false;
    }}

    m_shader_program = parent_gl_functions::gl__CreateProgram();
    parent_gl_functions::gl__AttachShader(m_shader_program,m_fragment_shader);
    parent_gl_functions::gl__AttachShader(m_shader_program,m_vertex_shader);
    parent_gl_functions::gl__LinkProgram(m_shader_program);
   {GLint status;
    parent_gl_functions::gl__GetProgramiv(m_shader_program,GL_LINK_STATUS,&status);
    if(status==GL_FALSE) {
       m_out << "toolx::sg::GL_manager_T::create_shader_program :"
             << " link program failed."
             << std::endl;
       parent_gl_functions::gl__DetachShader(m_shader_program,m_fragment_shader);
       parent_gl_functions::gl__DetachShader(m_shader_program,m_vertex_shader);
       parent_gl_functions::gl__DeleteProgram(m_shader_program);
       parent_gl_functions::gl__DeleteShader(m_vertex_shader);
       parent_gl_functions::gl__DeleteShader(m_fragment_shader);
       m_vertex_shader = 0;
       m_fragment_shader = 0;
       m_shader_program = 0;
       return false;
    }}
    return true;
  }
  void delete_shader_program() {
    if(!m_shader_program) return;
    parent_gl_functions::gl__DetachShader(m_shader_program,m_fragment_shader);
    parent_gl_functions::gl__DetachShader(m_shader_program,m_vertex_shader);
    parent_gl_functions::gl__DeleteProgram(m_shader_program);
    parent_gl_functions::gl__DeleteShader(m_vertex_shader);
    parent_gl_functions::gl__DeleteShader(m_fragment_shader);
     m_vertex_shader = 0;
     m_fragment_shader = 0;
     m_shader_program = 0;
  }

#define GET_UNIFORM_LOCATION(a__var,a__name)\
  m_##a__var = parent_gl_functions::gl__GetUniformLocation(m_shader_program,a__name);\
  if(m_##a__var==(-1)) {\
    m_out << "toolx::sg::GL_manager_T::use_shader_program : glGetUniformLocation(" << a__name << ") failed." << std::endl;\
  }

#define GET_ATTRIB_LOCATION(a__var,a__name)\
  m_##a__var = parent_gl_functions::gl__GetAttribLocation(m_shader_program,a__name);\
  if(m_##a__var==(-1)) {\
    m_out << "toolx::sg::GL_manager_T::use_shader_program : glGetAttribLocation(" << a__name << ") failed." << std::endl;\
  }

  GLint g_sampler_location() {return m_g_sampler;}
  GLint g_tex_on_location() {return m_g_tex_on;}
  GLint g_point_size_location() {return m_g_point_size;}

  GLint g_one_pos_location() {return m_g_one_pos;}
  GLint g_one_normal_location() {return m_g_one_normal;}
  GLint g_one_color_location() {return m_g_one_color;}
  GLint g_one_tex_location() {return m_g_one_tex;}

  GLint g_model_proj_matrix_location() {return m_g_model_proj_matrix;}
  GLint g_normal_matrix_location() {return m_g_normal_matrix;}
  GLint g_color_location() {return m_g_color;}
  GLint g_pos_color_location() {return m_g_pos_color;}
  GLint g_normal_location() {return m_g_normal;}
  GLint g_pos_normal_location() {return m_g_pos_normal;}

  GLint g_light_on_location() {return m_g_light_on;}
  GLint g_light_direction_location() {return m_g_light_direction;}
  GLint g_light_color_location() {return m_g_light_color;}
  GLint g_light_ambient_location() {return m_g_light_ambient;}

  void use_shader_program() {
    if(!m_shader_program) return;

    parent_gl_functions::gl__UseProgram(m_shader_program);

    // used by renderer :
    GET_UNIFORM_LOCATION(g_sampler,"sampler")
    GET_UNIFORM_LOCATION(g_tex_on,"tex_on")
    GET_UNIFORM_LOCATION(g_point_size,"point_size")

    GET_ATTRIB_LOCATION(g_one_pos,"one_pos")
    GET_ATTRIB_LOCATION(g_one_normal,"one_normal")
    GET_ATTRIB_LOCATION(g_one_color,"one_color")
    GET_ATTRIB_LOCATION(g_one_tex,"one_tex")

    GET_UNIFORM_LOCATION(g_model_proj_matrix,"model_proj_matrix")
    GET_UNIFORM_LOCATION(g_normal_matrix,"normal_matrix")
    GET_UNIFORM_LOCATION(g_color,"color")
    GET_UNIFORM_LOCATION(g_pos_color,"pos_color")
    GET_UNIFORM_LOCATION(g_normal,"normal")
    GET_UNIFORM_LOCATION(g_pos_normal,"pos_normal")

    GET_UNIFORM_LOCATION(g_light_on,"light_on")
    GET_UNIFORM_LOCATION(g_light_direction,"light_direction")
    GET_UNIFORM_LOCATION(g_light_color,"light_color")
    GET_UNIFORM_LOCATION(g_light_ambient,"light_ambient")

    parent_gl_functions::gl__Uniform1i(m_g_tex_on,0);
    parent_gl_functions::gl__Uniform1f(m_g_point_size,1);

    parent_gl_functions::gl__Uniform4f(m_g_color,1,1,1,1);
    parent_gl_functions::gl__Uniform1i(m_g_pos_color,false);

    parent_gl_functions::gl__Uniform3f(m_g_normal,0,0,1);
    parent_gl_functions::gl__Uniform1i(m_g_pos_normal,false);

    parent_gl_functions::gl__Uniform1i(m_g_light_on,0);
    parent_gl_functions::gl__Uniform4f(m_g_light_color,1,1,1,0);
    parent_gl_functions::gl__Uniform3f(m_g_light_direction,0,0,-1);

  }
  void reset_shader_locations() {
    m_g_sampler = -1;
    m_g_tex_on = -1;
    m_g_point_size = -1;

    m_g_one_pos = -1;
    m_g_one_normal = -1;
    m_g_one_color = -1;
    m_g_one_tex = -1;

    m_g_model_proj_matrix = -1;
    m_g_normal_matrix = -1;
    m_g_color = -1;
    m_g_pos_color = -1;
    m_g_normal = -1;
    m_g_pos_normal = -1;

    m_g_light_on = -1;
    m_g_light_direction = -1;
    m_g_light_color = -1;
    m_g_light_ambient = -1;
  }
#undef GET_ATTRIB_LOCATION
#undef GET_UNIFORM_LOCATION

#endif //TOOLS_USE_GL_VERSION_3_2

protected:
  /*static*/ void clear_gl_errors() {
    GLenum glerror = parent_gl_functions::gl__GetError();
    while(glerror!=GL_NO_ERROR) {
      glerror = parent_gl_functions::gl__GetError();
    }
  }

  /*static*/ bool dump_if_gl_errors(std::ostream& a_out,const std::string& a_head) {
    bool retval = false;
    GLenum glerror = parent_gl_functions::gl__GetError();
    if(glerror!=GL_NO_ERROR) {
      a_out << a_head
            << " we have gl errors :"
            << std::endl;
      retval = true;
    }
    while(glerror!=GL_NO_ERROR) {
      //#define GL_NO_ERROR                       0
      //#define GL_INVALID_ENUM                   0x0500
      //#define GL_INVALID_VALUE                  0x0501
      //#define GL_INVALID_OPERATION              0x0502
      //#define GL_STACK_OVERFLOW                 0x0503
      //#define GL_STACK_UNDERFLOW                0x0504
      //#define GL_OUT_OF_MEMORY                  0x0505

      //#define GL_INVALID_FRAMEBUFFER_OPERATION  0x0506

      std::ostringstream oss;
      oss << "0x" << std::hex << glerror;
      a_out << oss.str() << std::endl;

      glerror = parent_gl_functions::gl__GetError();
    }
    return retval;
  }

  static bool s2vers(const std::string& a_string,std::vector<unsigned int>& a_versions) {
    // a_string is of the form "<number>[.<number>.<number>] <comment>, extract <numbers>.
    // Examples : "1.0.2" "2.1 ATI-4.8.101"
    a_versions.clear();
    if(a_string.empty()) return false;
    char* pos = (char*)a_string.c_str();
    while((*pos!='\0')&&(*pos!=' ')) {
      char* s;
      long v = ::strtol(pos,&s,10);
      if(s==pos) {
        a_versions.clear();
        return false;
      }
      if(v<0) {
        a_versions.clear();
        return false;
      }
      a_versions.push_back((unsigned int)v);
      pos = s; //Could be at end.
      if(*pos=='\0') break;
      if(*pos==' ') break;
      if(*pos!='.') {
        a_versions.clear();
        return false;
      }
      // Next char is '.', skip it:
      pos++;
    }
    return true;
  }

  /*static*/ bool get_version_numbers(std::ostream& a_out,const std::string& a_version,unsigned int& a_major,unsigned int& a_minor) {
    a_major = 0;
    a_minor = 0;
    std::vector<unsigned int> verss;
    if(!s2vers(a_version,verss)) {
      a_out << "toolx::sg::GL_manager_T::get_version_numbers :"
            << " toolx::sg::GL_manager_T::s2vers() failed on \"" << a_version << "\"."
            << std::endl;
      return false;
    }
    if(verss.size()==1) {
      a_major = verss[0];
      a_minor = 0;
    } else if(verss.size()>=2) {
      a_major = verss[0];
      a_minor = verss[1];
    } else {
      a_out << "toolx::sg::GL_manager_T::get_version_numbers :"
            << " can't extract major/minor version number from \"" << a_version << "\"."
            << std::endl;
      return false;
    }
    return true;
  }

protected:
  std::ostream& m_out;

  std::map<unsigned int,gsto_t*> m_gstos;

  unsigned int m_gen_id;
  tools::sg::gsto_mode m_gsto_mode;

#ifdef TOOLS_USE_GL_VERSION_3_2
  GLuint m_fragment_shader;
  GLuint m_vertex_shader;
  GLuint m_shader_program;
  //locations:
  GLint m_g_sampler;
  GLint m_g_tex_on;
  GLint m_g_point_size;

  GLint m_g_one_pos;
  GLint m_g_one_normal;
  GLint m_g_one_color;
  GLint m_g_one_tex;

  GLint m_g_model_proj_matrix;
  GLint m_g_normal_matrix;
  GLint m_g_color;
  GLint m_g_pos_color;
  GLint m_g_normal;
  GLint m_g_pos_normal;

  GLint m_g_light_on;
  GLint m_g_light_direction;
  GLint m_g_light_color;
  GLint m_g_light_ambient;
#endif //TOOLS_USE_GL_VERSION_3_2

  std::string m_gl_version;
#ifdef TOOLS_USE_GL_VERSION_3_2
  std::string m_gl_shading_language_version;
#endif
  std::string m_gl_vendor;
  std::string m_gl_renderer;
  std::string m_gl_extensions;
  GLint m_gl_max_texture_size;
};

}}

#endif
