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

#ifndef tools_sg_node
#define tools_sg_node

#include "field"
#include "search_action"
#include "write_action"
#include "read_action"
#include "get_matrix_action"
#include "node_desc"

#include "../forit"

namespace tools {
namespace sg {
  class render_action;
  class pick_action;
  class bbox_action;
  class event_action;
  class visible_action;
}}

namespace tools {
namespace sg {

class node {
public:
  TOOLS_SCLASS(tools::sg::node)
  virtual void* cast(const std::string& a_class) const {
    if(void* p = cmp_cast<node>(this,a_class)) return p;
    return 0;
  }
  virtual const std::string& s_cls() const = 0;
public:
  virtual node* copy() const = 0;

  virtual unsigned int cls_version() const {return 1;}

  virtual const desc_fields& node_desc_fields() const {
    static const desc_fields s_v;
    return s_v;
  }

  virtual void render(render_action&) {}
  virtual void pick(pick_action&) {}
  virtual void bbox(bbox_action&) {}
  virtual void search(search_action& a_action) {
    if(a_action.what()==search_action::search_node_of_class) {
      if(void* p = cast(a_action.sclass())) {
        a_action.add_obj(p);
        if(a_action.stop_at_first()) a_action.set_done(true);
      }
    } else if(a_action.what()==search_action::search_path_to_node) {
      if(this==a_action.node()){
        a_action.path_push(this); //ending node in the path.
        a_action.set_done(true);
      }
    } else if(a_action.what()==search_action::search_path_to_node_of_class) {
      if(cast(a_action.sclass())) {
        search_action::path_t path = a_action.path();
        path.push_back(this);
        a_action.add_path(path);
        if(a_action.stop_at_first()) a_action.set_done(true);
      }
    }
  }
  virtual void get_matrix(get_matrix_action& a_action) {
    if(this==a_action.node()){
      a_action.set_found_model(a_action.model_matrix());
      a_action.set_done(true);
    }
  }
  virtual bool write(write_action& a_action) {
    if(!a_action.beg_node(*this)) return false;
    if(!write_fields(a_action)) return false;
    if(!a_action.end_node(*this)) return false;
    return true;
  }
  virtual void event(event_action&) {}
  virtual bool read(read_action& a_action) {return read_fields(a_action);}
  virtual void is_visible(visible_action&) {}

  virtual void protocol_one_fields(std::vector<field*>& a_fields) const {a_fields = m_fields;}

  virtual bool draw_in_frame_buffer() const {return false;}  // marker nodes return true.
public:
  virtual bool touched() { //virtual for plotter.
    tools_vforcit(field*,m_fields,it) {
      if((*it)->touched()) return true;
    }
    return false;
  }
  virtual void reset_touched() {
    tools_vforcit(field*,m_fields,it) (*it)->reset_touched();
  }
public:
  node()
  {
  }
  virtual ~node(){
  }
protected:
  node(const node&)
  {
  }
  node& operator=(const node&){
    return *this;
  }
protected:
  void add_field(field* a_field) {
    m_fields.push_back(a_field); //it does not take ownerhship.
  }
  bool write_fields(write_action& a_action) {

    check_fields(a_action.out()); //costly.

    unsigned int index = 0;
    tools_vforcit(field*,m_fields,it) {
      if(!(*it)->write(a_action.buffer())) {
        a_action.out() << "node::write_fields :"
                       << " for field index " << index
                       << " and field class " << (*it)->s_cls()
                       << " of node class " << s_cls()
                       << " : field.write() failed" << "."
                       << std::endl;
        return false;
      }
      index++;
    }
    return true;
  }

  bool read_fields(read_action& a_action) { //used in protocol-2.
    node_desc rndesc;
    if(!a_action.get_node_desc(s_cls(),rndesc)) {
      a_action.out() << "tools::node::read_fields :"
                     << " for node class " << s_cls()
                     << " : read_action.get_node_desc() failed."
                     << std::endl;
      return false;
    }
    //Whatever the current node fields, we must read all rndesc.fields() :
    tools_vforcit(field_desc,rndesc.fields(),it) {
      const field_desc& fdesc = *it;

      field* fd = find_field(fdesc);
      if(!fd) {
        a_action.out() << "tools::node::read_fields :"
                       << " for node class " << s_cls()
                       << " : field desc name " << fdesc.name()
                       << " : field desc class " << fdesc.cls()
                       << " : field desc offset " << fdesc.offset()
                       << " : field not found."
                       << "."
                       << std::endl;
        fd = a_action.field_factory().create(fdesc.cls());
        if(!fd) {
          a_action.out() << "tools::node::read_fields :"
                         << " for node class " << s_cls()
                         << " : field desc class " << fdesc.cls()
                         << " : can't create generic field."
                         << "."
                         << std::endl;
          return false;
        }
      } 

      if(!fd->read(a_action.buffer())) {
        a_action.out() << "tools::node::read_fields :"
                       << " for node class " << s_cls()
                       << " : and field class " << fd->s_cls()
                       << " : field read() failed."
                       << std::endl;
        return false;
      }
    }

    //NOTE : if some current node fields had not been found
    //       in rndesc.fields(), they catch the default value
    //       of the node fields.

    return true;
  }
public:
  void touch() {
    if(m_fields.empty()) return;
    m_fields.front()->touch();
  }

public:
  field& field_from_desc(const field_desc& a_desc) const {
    //WARNING : touchy.
    return *((field*)((char*)this+a_desc.offset()));
  }
  void dump_field_descs(std::ostream& a_out) const {
    a_out << "field descs of node class " << s_cls() << " :" << std::endl;
    const std::vector<field_desc>& fds = node_desc_fields();
    tools_vforcit(field_desc,fds,itd) {
      a_out << "name " << (*itd).name()
            << ", class " << (*itd).cls()
            << ", offset " << (*itd).offset()
            << std::endl;
    }
  }
  field* find_field_by_name(const std::string& a_name) const {
    // a_name is of the form <class>.<field>. For example for color on sg::text, there are :
    //   tools::sg::back_area.color
    //   tools::sg::text.color
    const std::vector<field_desc>& fds = node_desc_fields();
    for(auto it = fds.crbegin(); it != fds.crend(); ++it) {
      if((*it).name()==a_name) {
        tools_vforcit(field*,m_fields,itf) {
          if(field_offset(*itf)==(*it).offset()) return (*itf);
        }
      }
    }
    return 0;
  }
protected:
  field_desc::offset_t field_offset(const field* a_field) const {
    //WARNING : touchy.
    return ((char*)(a_field)-(char*)(this));
  }

  field* find_field(const field_desc& a_rdesc) const {
    const std::vector<field_desc>& fds = node_desc_fields();
    tools_vforcit(field_desc,fds,it) {
      if((*it).name()==a_rdesc.name()) {
        tools_vforcit(field*,m_fields,itf) {
          if(field_offset(*itf)==(*it).offset()) return (*itf);
        }
      }
    }
    return 0;
  }

  void check_fields(std::ostream& a_out) const {
    const std::vector<field_desc>& fds = node_desc_fields();
    tools_vforcit(field*,m_fields,it) {
      bool found = false;
      tools_vforcit(field_desc,fds,itd) {
        if( ((*itd).offset()==field_offset(*it)) &&
            ((*itd).cls()==(*it)->s_cls())
        ){
          found = true;
          break;
        }
      }
      if(!found) {
        a_out << "tools::sg::node::check_fields :"
              << " WARNING : node of class " << s_cls()
              << " has bad fields description."
              << std::endl;
      }
    }
  }
private:
  std::vector<field*> m_fields;
};

}}

#include "../HEADER"

#define TOOLS_NODE(a__class,a__sclass,a__parent)\
  TOOLS_HEADER(a__class,a__sclass,a__parent)\
  virtual tools::sg::node* copy() const {return new a__class(*this);}

#define TOOLS_NODE_NO_CAST(a__class,a__sclass,a__parent)\
private:\
  typedef a__parent parent;\
public:\
  TOOLS_SCLASS(a__sclass)\
public:\
  virtual const std::string& s_cls() const {return s_class();}\
  virtual tools::sg::node* copy() const {return new a__class(*this);}

#define TOOLS_NODE_T(a__T,a__class,a__sclass,a__parent)\
private:\
  typedef a__parent parent;\
public:\
  static const std::string& s_class() {\
    static const std::string s_v(std::string(#a__class)+"<"+a__T::s_class()+">");\
    return s_v;\
  }\
  static void check_class_name() {a__class<a__T>::s_class();}\
public:\
  virtual const std::string& s_cls() const {return s_class();}\
  virtual tools::sg::node* copy() const {return new a__class(*this);}\
public:\
  virtual void* cast(const std::string& a_class) const {\
    if(void* p = tools::cmp_cast<a__class>(this,a_class)) return p;\
    return parent::cast(a_class);\
  }

#define TOOLS_NODE_VT2(a__T1,a__T2,a__class,a__sclass,a__parent)\
private:\
  typedef a__parent parent;\
public:\
  static const std::string& s_class() {\
    static const std::string s_v(std::string(#a__class)+"<"+a__T1::s_class()+","+a__T2::s_class()+">");\
    return s_v;\
  }\
  static void check_class_name() {a__class<a__T1,a__T2>::s_class();}\
public:\
  virtual const std::string& s_cls() const {return s_class();}\
  /*virtual tools::sg::node* copy() const {return new a__class(*this);}*/\
public:\
  virtual void* cast(const std::string& a_class) const {\
    if(void* p = tools::cmp_cast<a__class>(this,a_class)) return p;\
    return parent::cast(a_class);\
  }

#endif
