/* -*- c++ -*-
 *
 * Copyright (C) 2004 Mekensleep
 *
 *	Mekensleep
 *	24 rue vieille du temple
 *	75004 Paris
 *       licensing@mekensleep.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * Authors:
 *  Loic Dachary <loic@gnu.org>
 *
 */

#ifndef OSGCHIPS_STACKS
#define OSGCHIPS_STACKS

#include <osg/Geode>
#include <osg/Geometry>

#if defined(_MSC_VER) || defined(__CYGWIN__) || defined(__MINGW32__) || defined( __BCPLUSPLUS__) || defined( __MWERKS__)
	#  ifdef OSGCHIPS_LIBRARY
	#    define OSGCHIPS_EXPORT   __declspec(dllexport)
	#  else
	#    define OSGCHIPS_EXPORT   __declspec(dllimport)
	#  endif /* OSGCHIPS_LIBRARY */
#else
	#  define OSGCHIPS_EXPORT
#endif

extern "C" {
  struct _xmlDoc;
}

namespace osgDB {
  class Registry;
};

namespace osg {
  class Material;
  class Texture2D;
};

namespace osgchips {

  /// Container of one chip mesh and multiple osgchips::ChipBank::Chip instances defining chip types
  /** 
   * Repository of osgchips::ChipBank::Chip objects that define the type of
   * chip displayed by a given osgchips::Stack instance. osgchips::ChipBank
   * is designed to have a single instance which is created and filled
   * by reading a <b>osgchips</b> XML element (either from a file or
   * from an in-memory XML document).
   *
   * A chip is defined by 
   * - a mesh that can be stretched upward to simulate a chip stack
   * using a constant amount of graphical resources, 
   * - a texture designed to fit the streched mesh
   * - a symbolic name 
   * - the numerical value of the chip
   */

  class OSGCHIPS_EXPORT ChipBank {
  public:
    /// Texture, material, name and value defining a chip
    /**
     * Opaque class representing a chip as defined by an <b>osgchips</b> XML element.
     */
    class Chip {
    public:

      Chip(const std::string& name, unsigned int value);
      ~Chip();

      void setTexture(osg::Image* image);
      void setColor(const osg::Vec4& color) { _color = color; _hasColor = true; }

      std::string _name;
      unsigned int _value;
      osg::ref_ptr<osg::Texture2D> _texture;
      bool _hasColor;
      osg::Vec4 _color;
    };

    typedef std::map<unsigned int, Chip*> Value2Chip;
    typedef std::map<std::string, Chip*> Name2Chip;

    ChipBank();
    ~ChipBank();

    /**
     * Get the osgchips::ChipBank singleton. It is created if necessary.
     *
     * @return a pointer to the osgchips::ChipBank singleton.
     */
    static ChipBank* instance();

    void addChip(Chip* chip) {
      if(chip) {
	_value2chip[chip->_value] = chip;
	_name2chip[chip->_name] = chip;
      }
    }

    /**
     * Return the chip whose numerical value equals <b>value</b>.
     *
     * @param value [0-N] numerical value, as defined by the /osgchips/chip\@value attribute.
     * @return an osgchip::ChipBank::Chip pointer or NULL if no existing chip matches the value.
     */
    Chip* getChip(unsigned int value) {
      if(_value2chip.find(value) != _value2chip.end())
	return _value2chip[value];
      else
	return 0;
    }

    /**
     * Return the chip whose symbolic name equals <b>value</b>.
     *
     * @param name symbolic name, as defined by the /osgchips/chip\@name attribute.
     * @return an osgchip::ChipBank::Chip pointer or NULL if no existing chip matches the name.
     */
    Chip* getChip(const std::string& name) {
      if(_name2chip.find(name) != _name2chip.end())
	return _name2chip[name];
      else
	return 0;
    }

    const osg::BoundingBox& getBoundingBox() const { return _bbox; }
    void setBoundingBox(const osg::BoundingBox& bbox) { _bbox = bbox; }

    osg::Vec3Array* getVertexArray() { return _vertexArray.get(); }
    void setVertexArray(osg::Vec3Array* vertexArray) { _vertexArray = vertexArray; }

    osg::Vec3Array* getNormalArray() { return _normalArray.get(); }
    void setNormalArray(osg::Vec3Array* normalArray) { _normalArray = normalArray; }

    osg::Vec2Array* getTexCoordArray() { return _texCoordArray.get(); }
    void setTexCoordArray(osg::Vec2Array* texCoordArray) { _texCoordArray = texCoordArray; }

    osg::Geometry::PrimitiveSetList& getPrimitiveSetList() { return _primitives; }
    void setPrimitiveSetList(const osg::Geometry::PrimitiveSetList& primitives) { _primitives = primitives; }

    const Value2Chip& getValue2Chip() const { return _value2chip; }
    const Name2Chip& getName2Chip() const { return _name2chip; }

    /**
     * Look for an <b>osgchips</b> XML element and fill the osgchips::ChipBank singleton.
     * The registry can be obtained by the osgDB::Registry::instance() static
     * method.
     *
     * It is not mandatory for the XML document to contain an <b>osgchips</b> element.
     * The XML document may contain more than one <b>osgchips</b> element. Each element
     * will add content to the osgchips::ChipBank singleton.
     *
     * @param doc a valid libxml2 pointer to the in-memory representation of an XML document.
     * @param registry a pointer to the OpenSceneGraph registry
     * @return <b>true</b> on success, <b>false</b> on error.
     */
    bool unserialize(struct _xmlDoc* doc, osgDB::Registry* registry);
    /**
     * Parse the <b>fileName</b> and interpret any <b>osgchips</b> XML element 
     * to fill the osgchips::ChipBank singleton.
     * The registry can be obtained by the osgDB::Registry::instance() static
     * method.
     *
     * See the other prototype for more information.
     *
     * @param fileName path name to a valid XML document.
     * @param registry a pointer to the OpenSceneGraph registry
     * @return <b>true</b> on success, <b>false</b> on error.
     */
    bool unserialize(const std::string& fileName, osgDB::Registry* registry);

  protected:
    osg::BoundingBox _bbox;
    Value2Chip _value2chip;
    Name2Chip _name2chip;
    osg::ref_ptr<osg::Vec3Array> _vertexArray;
    osg::ref_ptr<osg::Vec3Array> _normalArray;
    osg::ref_ptr<osg::Vec2Array> _texCoordArray;
    osg::Geometry::PrimitiveSetList _primitives;
  };

  /// An homogenous stack of chips defined by a osgchips::ChipBank::Chip instance
  /**
   * A stack of chips of the same type. The size of the stack may be
   * changed dynamically.  The stack can be moved by modifying the
   * center of the stack which is located at the bottom of the center
   * of the cylinder. A stack that is not associated to an
   * osgchips::ChipBank::Chip or that has a size of 0 is valid but is
   * not drawn.
   * 
   * The stack does not support picking and is unable to calculate its
   * own bounding box: it relies on its container (osgchips::Stacks).
   * The type of the chip can be changed dynamically. It is the type
   * of the chip that defines the color, transparency, texture and
   * value of the chip.
   *
   * The stack uses the texture unit 0 (if the
   * osgchips::ChipBank::Chip has a texture) and sets the material
   * attribute ((if the osgchips::ChipBank::Chip has a material). If a
   * stack is made of chips that do not have textures, the caller may
   * add its own texture without interfering. The same applies to the
   * material. The caller must not alter the texture or the material
   * if they are in use or unpredictable results will occur.
   *
   * The behaviour of an osgchips::Stacks instance that is not a child
   * of an osgchips::Stacks instance is undefined.
   *
   */
  class OSGCHIPS_EXPORT Stack : public osg::Geometry {
  public:
    /**
     * Create a one chip high stack for a yet unknown chip type.
     * It will not be drawn unless the setChips method is called
     * at some point. Get mesh information from the osgchips::ChipBank
     * singleton.
     */
    Stack();
    /**
     * Create a one chip high stack for a yet unknown chip type.
     * It will not be drawn unless the setChips method is called
     * at some point. Get mesh information from the <b>chipBank</b>.
     *
     * @param chipBank provider of vertexes, normals, texture coordinates and primitives 
     *        to draw the stack.
     */
    Stack(ChipBank* chipBank);

    META_Object(osgchips, Stack)

    Stack(const Stack&, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

    /**
     * Set the stack to be <b>count</b> chips high. If set to 0, the
     * chip stack will not be drawn.
     *
     * @param count the number of chips in the stack
     */
    void setCount(unsigned int count);
    /**
     * Get the height of the stack, in chips.
     *
     * @result the height of the stack, in chips.
     */
    unsigned int getCount(void) const { return _count; }
    
    /**
     * Set the center of the bottom of the stack to <b>position</b>.
     * By default the stack is located on 0, 0, 0.
     *
     * @param position translation of the stack from 0, 0, 0.
     */
    void setPosition(const osg::Vec3& position);
    /**
     * Retrieve the position of the center of the bottom of the stack.
     * By default the stack is located on 0, 0, 0.
     *
     * @result the position of the stack. 
     */
    const osg::Vec3& getPosition() const { return _position; }

    /**
     * Set the type of the stack to <b>chip</b>. It defines the
     * material and textures to use for drawing and provides the
     * name and value of each chip in the stack.
     *
     * @param chip a chip type from an osgchips::ChipBank instance
     */
    void setChip(ChipBank::Chip* chip);
    /**
     * Retrieve the chip type of the stack, read only.
     *
     * @result the chip type of the stack.
     */
    const ChipBank::Chip* getChip() const { return _chip; }

    /**
     * Retreive the value of a single chip in the stack.
     *
     * @return the value of a chip as defined by the chip type.
     */
    unsigned int getValue() const { return _chip ? _chip->_value : 0; }

    virtual void drawImplementation(osg::State& state) const;

    virtual bool supports(osg::Drawable::AttributeFunctor&) const { return false; }
    virtual bool supports(osg::Drawable::ConstAttributeFunctor&) const { return true; }
    virtual void accept(osg::Drawable::ConstAttributeFunctor& af) const {}

    virtual bool supports(PrimitiveIndexFunctor&) const { return false; }
    virtual void accept(PrimitiveIndexFunctor&) const {}

    virtual bool supports(osg::Drawable::PrimitiveFunctor&) const { return false; }
    virtual void accept(osg::Drawable::PrimitiveFunctor& pf) const {}

  private:
    void setMesh(ChipBank* chipBank); 
    void updateVertexArray();
    void updateTexCoordArray();
    void dirtyParentBound();

    unsigned int _count;
    osg::Vec3f _position;
    ChipBank::Chip* _chip;
    ChipBank* _chipBank;
  };


  /// Container of osgchips::Stack instances
  /**
   * All osgchips::Stack instances must be children of an
   * osgchips::Stacks instance to be displayed. It provides a square bounding box
   * (osgchips::Stacks do not provide bounding boxes) and picking based on the
   * bounding box. As a result, osgchips::Stacks will behave as expected if the
   * stacks are close to each other. If there are large gaps between them, the
   * gaps will be included in the bounding box.
   *
   * osgchips::Stacks is publicly derived from osg::Geode and imposes a single
   * constraints in the use of public osg::Geode methods by the application: 
   * instead of using getNumChildren, addChild, replaceChild, getChild and setChild
   * the application must use getNumStacks, addStack, replaceStack, getStack and
   * setStack respectively.
   */
  class OSGCHIPS_EXPORT Stacks : public osg::Geode {
  public:
    /**
     * Create an osgchips::Stack container that relies on the osgchips::ChipBank
     * singleton for chip mesh information.
     */
    Stacks();
    /**
     * Create an osgchips::Stack container that relies on <b>chipBank</b>
     * for chip mesh information.
     *
     * @param chipBank pointer to chip types and mesh repository
     */
    Stacks(ChipBank* chipBank);

    META_Node(osgchips, Stacks)

    Stacks(const Stacks&, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

  protected:
    virtual ~Stacks();

  public:
    /**
     * Append <b>stack</b> to the list of children.
     *
     * @param stack the child to add
     * 
     * @return <b>true</b> on success, <b>false</b> on error.
     */
    virtual bool addStack(Stack* stack);
    /**
     * Remove <b>origStack</b> from the list of children and insert
     * <b>newStack</b> at the same position.
     *
     * @param origStack the stack to remove
     * @param newStack the stack to insert
     *
     * @return <b>true</b> on success, <b>false</b> on error.
     */
    virtual bool replaceStack(Stack* origStack, Stack* newStack);
    /**
     * Insert <b>stack</b> at position <b>index</b> in the list of
     * children, shifting the children already at position index or
     * above.
     *
     * @param index the position in the list of children, counting from zero
     * @param stack the stack to insert
     *
     * @return <b>true</b> on success, <b>false</b> on error.
     */
    virtual bool setStack(unsigned int index, Stack* stack);

    /**
     * Return the current number of stacks.
     * 
     * @return the number of stacks
     */
    inline unsigned int getNumStacks() const { return getNumDrawables() - 1; }
    /**
     * Return a pointer to the stack at position <b>index</b> in the list of children.
     * 
     * @return a pointer to a osgchips::Stack or NULL if the index is invalid.
     */
    inline Stack* getStack(unsigned int index) { return dynamic_cast<Stack*>(getDrawable(index + 1)); }
    /**
     * Return a read only pointer to the stack at position <b>index</b> in the list of children.
     * 
     * @return a pointer to a osgchips::Stack or NULL if the index is invalid.
     */
    inline const Stack* getStack(unsigned int index) const { return dynamic_cast<const Stack*>(getDrawable(index + 1)); }

    /**
     * Look for an <b>osgchips</b> XML element and fill the osgchips::ChipBank singleton.
     * The registry can be obtained by the osgDB::Registry::instance() static
     * method.
     *
     * It is not mandatory for the XML document to contain an <b>osgchips</b> element.
     * The XML document may contain more than one <b>osgchips</b> element. Each element
     * will add content to the osgchips::ChipBank singleton and add children to the 
     * container.
     *
     * @param doc a valid libxml2 pointer to the in-memory representation of an XML document.
     * @param registry a pointer to the OpenSceneGraph registry
     * @return <b>true</b> on success, <b>false</b> on error.
     */
    bool unserialize(struct _xmlDoc* doc, osgDB::Registry* registry);
    /**
     * Parse the <b>fileName</b> and interpret any <b>osgchips</b> XML element 
     * to fill the osgchips::ChipBank singleton.
     * The registry can be obtained by the osgDB::Registry::instance() static
     * method.
     *
     * See the other prototype for more information.
     *
     * @param fileName path name to a valid XML document.
     * @param registry a pointer to the OpenSceneGraph registry
     * @return <b>true</b> on success, <b>false</b> on error.
     */
    bool unserialize(const std::string& fileName, osgDB::Registry* registry);

  protected:
    virtual bool computeBound() const;

    osg::ref_ptr<osg::Drawable> _box;
    ChipBank* _chipBank;
  };

} // namespace osgchips

#endif // OSGCHIPS_STACKS
