/* -*- c++ -*-
    Copyright (C) 2003 <ryu@gpul.org>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
/*
 *
 * Copyright (C) 2007 Loic Dachary <loic@dachary.org>
 * Copyright (C) 2004, 2006 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Authors:
 *  Cedric PINSON <cpinson@freesheep.org>
 *  Loic Dachary <loic@gnu.org>
 *  Igor Kravtchenko <igor@tsarevitch.org>
 *
 */
#ifndef __OSGCAL__MODEL_H__
#define __OSGCAL__MODEL_H__

#include <osg/Geode>
#include <osg/VertexProgram>

#include <cal3d/cal3d.h>

#include <osg/Notify>
#include <osgCal/CoreModel>
#include <osgCal/Export>

#include <osgCal/TextureLayersFlatten>

namespace osgCal {

class SubMeshSoftware;
class GlueCalHardwareModel;

#define MODEL_VBO_GEOMETRY 0
#define MODEL_VBO_INDEX 1
#define MODEL_VBO_SIZE 2


class OSGCAL_EXPORT Model: public osg::Geode {

public:

  class TargetMap {
  public:
  
    TargetMap() :
      baseLayerIndex(0),
      bFlushIfDependent(false) { };

    class Param {
    public:
      Param() : layerIndex(0) {}
      int layerIndex;
      std::string name;
    };

    std::string colorMask;
    std::string alphaPart;
    std::vector<TextureLayersFlatten::Layer> layers;
    std::vector<Param> params;
    osg::ref_ptr<TextureLayersFlatten> tlf;

    osg::ref_ptr<TextureLayersFlatten> tp_tlf;
    int baseLayerIndex; // used only if tp_tlf is not NULL

    bool bFlushIfDependent;
  };

  struct OutfitDescription {
    typedef std::map< std::pair<std::string, std::string>, int> Parameters;

    struct Slot {
      std::string _default;
      const std::string& getDefault() const { return _default;}
      void setDefault(const std::string& name) { _default=name;}
      Parameters _params;
    };

    typedef std::vector<Slot> SlotBank;
    typedef std::map<std::string,SlotBank> SlotMap;
    SlotMap& getSlots() { return _slots;}
    const SlotMap& getSlots() const { return _slots;}

    SlotMap _slots;
    std::string _comment;
  };

  typedef std::map<std::string, TargetMap> FlattenConf;

  typedef CoreModel::Textures2D Textures2D;
  typedef std::vector<osg::ref_ptr<osg::Drawable> > Drawables;
  typedef std::map<int,Drawables> CoreMeshId2Drawables;
  typedef std::vector<int> VectorInt;

  META_Object(osgCal, Model);

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

protected:
  virtual ~Model();
public:
  /**
   * @brief Create model from a templated CoreModel. Remember to set CoreModel with setCoreModel function.
   */
  bool create(void);
  void update(void);

  /**
   * @brief Retrieves CoreModel
   */ 
  CoreModel *getCoreModel() { return _coreModel.get(); }

  /**
   * @brief Sets CoreModel
   * @return \c false if case of error, otherwise \c true.
   */
  bool setCoreModel(CoreModel* coreModel);
  
  /**
   * @brief Retrieves the Cal3d CalCoreModel.
   */
  CalCoreModel *getCalCoreModel(void) { return _coreModel->getCalCoreModel(); }
  
  /**
   * @brief Retrieves the Cal3d CalModel associated with the current osgCalModel instance.
   */
  CalModel *getCalModel(void) { return _calModel; }

  /**
   * @brief Retrieves the 2D textures given a material name
   * @param materialName a valid materialName
   *
   * @remark Each material has map(s) associated, i.e. texture(s).
   */
  Textures2D* getTextures2D(const std::string& materialName);

  /**
   * @brief Retrieves the 2D textures given a material id
   * @param materialId a valid material Id
   *
   * @remark Each material has map(s) associated, i.e. texture(s).
   */
  Textures2D* getTextures2D(int coreMaterialId);
  
  /**
   * @brief Retrieves the drawables given a material name
   * @param materialName a valid materialName
   *
   * @remark Each material has map(s) associated, i.e. texture(s). 
   * The drawables are osg objects that can be rendered. 
   */
  Drawables* getDrawables(const std::string& materialName);

  /**
   * @brief Retrieves the drawables given a material id
   * @param materialId a valid materialId
   *
   * @remark Each material has map(s) associated, i.e. texture(s). 
   * The drawables are osg objects that can be rendered. 
   */  
   Drawables* getDrawables(int coreMaterialId);

  bool hasDrawables(int coreMeshId);

   /**
   * @brief Activates meshes.
   *
   * @param meshName name of the mesh to active
   *
   * @remark By default all the meshes are set to active.  
   */   
  bool setActiveMesh(const std::string &_meshName);

  bool setActiveMeshes(const std::vector<int>& activeMeshes) { _activeMeshes = activeMeshes; return true; }
  const std::vector<int>& getActiveMeshes(void) const { return _activeMeshes; }

  bool bindMaterials(const std::string& meshName, const std::vector<std::string>& materials);

  bool setInvisibleMesh(const std::string& meshName);
  bool setInvisibleMeshes(const std::vector<int>& invisibleMeshes) { _invisibleMeshes = invisibleMeshes; return true; }
  const std::vector<int>& getInvisibleMeshes(void) const { return _invisibleMeshes; }

  bool setCollisionMesh(const std::string& meshName);
  bool setCollisionMeshNames(const std::vector<std::string>& meshNames);
  const std::vector<std::string>& getCollisionMeshNames(void) const { return _collisionMeshNames; }
  bool setCollisionMeshes(const std::vector<int>& collisionMeshes) { _collisionMeshes = collisionMeshes; return true; }
  const std::vector<int>& getCollisionMeshes(void) const { return _collisionMeshes; }

  bool setUseVertexProgram(bool useVertexProgram, unsigned int contextID = 0);
  bool getUseVertexProgram(void) { return _useVertexProgram; }

  bool setUseColorOrTexture(bool useColorOrTexture) { _useColorOrTexture = useColorOrTexture; return true; }
  bool getUseColorOrTexture(void) { return _useColorOrTexture; }

  bool bindMesh(const CoreModel::MeshDescription& meshDescription);
  void unapplySlot(const std::string& slotType,int slotIndex);

  std::vector<int>& getCoreMeshIdUsedForMesh(const std::string &_itemName) { return _mesh2CoreMeshId[_itemName]; }
  const std::string& getItemUsedForMesh(int _coreMeshId) { return _coreMeshId2Item[_coreMeshId]; }
  
  std::vector<std::string> getMeshFromSlot(const std::string& slotType,int index=0);
  bool getSlotListFromSlotType(const std::string& name,CoreModel::SlotBank& result);
  
  bool applySlot(const std::string &slotType, const std::string &slotName, int slotIndex);

  void setupLayers(const std::string &slotType, const std::string &slotName, int slotIndex);
  void setupTLF(const std::string &slotType, int slotIndex);
  void freeAllLayersRessource();

  static void freeLayersRessource();

  void setFXGroup(osg::Group *_grp) { fxGroup_ = _grp; }
  osg::Group* getFXGroup() { return fxGroup_.get(); }

  void setFXState(osg::State *_state) { fxState_ = _state; }
  osg::State* getFXState() { return fxState_.get(); }

  bool setParam(const std::string &name, const std::string &type, int value);

  FlattenConf& getFlattenConfFromSlotType(const std::string &_type) { return slotName2FlattenConf_[_type]; }
  
  void flushTextureCache();

  bool removeCollisionMesh(const std::string& meshName);
  bool removeInvisibleMesh(const std::string& meshName);
  bool unBindMesh(const CoreModel::MeshDescription& meshDescription);

  OutfitDescription* getOutfit() { return &_outfit;}

  bool initOutfitFromFile(const std::string &fname, std::vector<std::string>* excludeMesh = 0);
  bool initOutfitFromXMLString(const std::string& xmlString, std::vector<std::string>* excludeMesh = 0);
  bool installOutfitFromXMLString(const std::string& xmlString);
  bool applyParameterFromOutfitDescription();

  bool loadOutfit(OutfitDescription* outfit, std::vector<std::string> *toBeIgnored = NULL);
  
protected:
  bool createSoftware(void);
  bool createSubMeshSoftware(int coreMeshId);
  bool createHardware(void);
  bool setupMaterial(osg::Drawable* drawable, CalSubmesh* calSubmesh);
  void invertUVs(int coreMeshId);

  bool isInvisibleMesh(int coreMeshId) { return find(_invisibleMeshes, coreMeshId); }
//  bool isForceSoftwareMesh(int coreMeshId) { return find(_forceSoftwareMeshes, coreMeshId); }
  bool isCollisionMesh(int coreMeshId) {
    return find(_collisionMeshes, coreMeshId) || ( _coreModel.valid() && _coreModel->getCollisionDefault() );
  }
  
  bool find(const std::vector<int>& vector, int item);

  bool isSlotAlreadyApplied(const std::string &_slotType, const std::string &_slotName);

  void fixNormalSW(float ptThreshold);
  void fixNormalHW(int nbVertices, osg::Vec3f *pos, osg::Vec3f *normal, char *imesh, float ptThreshold);
  
  GlueCalHardwareModel* mHardwareModel;
  bool _useVertexProgram;
  bool _useColorOrTexture;
  unsigned int _vbo[MODEL_VBO_SIZE];

  osg::ref_ptr<CoreModel> _coreModel;
  
  std::vector<int> _activeMeshes;
  std::vector<int> _invisibleMeshes;
  std::vector<int> _collisionMeshes;


  std::map<int, std::string> _coreMeshId2Item;
  std::map<std::string, VectorInt> _mesh2CoreMeshId;
  
  std::map<CalCoreSubmesh*, SubMeshSoftware*> _calCoreSubmesh2SubMeshSoftware;

  OutfitDescription _outfit;

  std::vector<std::string> _collisionMeshNames;
  CoreMeshId2Drawables _coreMeshId2Drawables;
  
  // Cannot be a base class of Model because rtti disabled in cal3d
  CalModel* _calModel;
  osg::ref_ptr<osg::VertexProgram> _vp;
  osg::BoundingBox _bbox;
  std::string _outfitName;

  std::map<std::string, FlattenConf> slotName2FlattenConf_;

  std::map<std::string, osg::ref_ptr<osg::Texture2D> > targetmap2texture_;

  osg::ref_ptr<osg::Group> fxGroup_;
  osg::ref_ptr<osg::State> fxState_;

  std::map<std::string, std::vector<std::string> > slotDependencies_;

  osg::NotifySeverity _notify;
  
  friend class SubMeshHardware;
};

struct RadixUserSW;

class RadixFloatItem {
public:
  float value;
  union {
    RadixUserSW* userSW;
    int userHW;
  } user;
};

class FloatRadix {
public:
  FloatRadix(int size);
  ~FloatRadix();
  RadixFloatItem** sort(RadixFloatItem *src, int nbItems);

protected:
  int       nbItems_;
  RadixFloatItem  **pTmpListPtr1_;
  RadixFloatItem  **pTmpListPtr2_;
};

}; // namespace osgCal

#endif
