/*
 *  Copyright (C) 2005  Andreas Volz
 *
 *  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
 *  
 *  File: Mesh.h
 *  Andreas Volz <linux@brachttal.net>
 *   
 */

#ifndef MESH_H
#define MESH_H 1

#ifdef HAVE_CONFIG_H
  #include <config.h>
#endif

#include <iostream>
#include <fstream>
#include <stdio.h>
#include <vector>
#include <string>
#include <memory>
#include "Face.h"
#include "Vertex.h"
#include "Target.h"
#include "BodySettings.h"
#include "FaceVector.h"
#include "VertexVector.h"
#include "Hotspot.h"
#include "VertexGroup.h"
#include "Material.h"
#include "DirectoryList.h"
#include "Matrix.h"
#include "util.h"
#include "Euler.h"
#include "MathUtil.h"
#include "TextureVector.h"
#include "PoseTarget.h"

using std::vector;
using std::map;
using std::string;
using std::ostringstream;

namespace Animorph {

// ------------------------------------------------------------------------
// Wrapper for a Target Object which is capable for lazy loading.
// ------------------------------------------------------------------------
class TargetEntry
{
public:
     TargetEntry(const string &inFilename, bool inPreload = false);
    ~TargetEntry();

    Target* getTarget();

private:
    bool  loadFromFile(); // supposed for lazy loading

private: // intentionally not implemented
    TargetEntry(const TargetEntry&);
    TargetEntry& operator=(const TargetEntry&);


private:    
    const string *mFilename;
    Target       *mTarget;
    bool          mTargetLoadTry;
}; // class TargetEntry

// ------------------------------------------------------------------------
// Wrapper for a Pose Target Object which is capable for lazy loading.
// ------------------------------------------------------------------------
class PoseEntry
{
public:
     PoseEntry(const string &inFilename, bool inPreload = false, bool negative = false);
    ~PoseEntry();

    PoseTarget* getTarget();
    void setNegative(bool negative) {mHasNegative = negative;}
    void addAuxRotation(const string& filename, const bool negative);
private:
    bool  loadFromFile(); // supposed for lazy loading

private: // intentionally not implemented
    PoseEntry(const PoseEntry&);
    PoseEntry& operator=(const PoseEntry&);

private:    
    const string  *mFilename;
    PoseTarget    *mTarget;
    bool           mTargetLoadTry;
    bool           mHasNegative;
    vector<string> positiveAuxRotations;
    vector<string> negativeAuxRotations;
}; // class PoseEntry

typedef map <string, Hotspot>      HotspotMap;
typedef map <string, TargetEntry*> TargetMap;
typedef map <string, Vector3f>     Centeroid;
typedef map <string, Vector3f>     FormFactor;
typedef map <string, PoseEntry*>   PoseMap;
typedef map <string, BodySettings> CharactersMap;

class Mesh
{
private:
  FaceVector        facevector;
  HotspotMap        hotspotmap;
  VertexVector      vertexvector_morph;    // container for modified mesh
  VertexVector      vertexvector_morph_copy;  
  vector <Vector3f> vertexvector_orginal;  // container for orginal mesh
  VertexGroup       vgroup;
  BodySettings      bodyset;
  TargetMap         targetmap;
  MaterialVector    materialvector;
  Centeroid         centeroid;
  TextureVector     texture_vector;
  BodySettings      poses;
  PoseMap           posemap;
  CharactersMap     charactersmap;

public:
   Mesh();
  ~Mesh();

  void calcFaceNormals ();
  void calcVertexNormals ();
  void calcSharedVertices ();

  void createTargetStream (ostringstream &out_stream);

  PoseTarget* getPoseTargetForName(const string& inTargetname) const;

private:
    void  clearTargetmap();
    void  clearPosemap  ();
    
    const Target* getTargetForName(const string& inTargetname);

public:
  /**** get Pointer API ****/
  /*************************/

  /*!
   * \return a pointer to the morphed VertexVector of this Mesh
   */
  VertexVector *getVertexVectorPtr () {return &vertexvector_morph;}

  /*!
   * \return a pointer to the FaceVector of this Mesh
   */
  FaceVector *getFaceVectorPtr () {return &facevector;}

  /*!
   * \return a pointer to the MaterialVector of this Mesh
   */
  MaterialVector *getMaterialVectorPrt () {return &materialvector;}

  /*!
   * \return a pointer to the TargetMap of this Mesh
   */
  TargetMap *getTargetMapPtr () {return &targetmap;}

  /*!
   * \return a pointer to the HotspotMap of this Mesh
   */
  HotspotMap *getHotspotMapPtr () {return &hotspotmap;}

  /*!
   * \return a pointer to the VertexGroup of this Mesh
   */
  VertexGroup *getVertexGroupPtr () {return &vgroup;}

  /*!
   * \return TODO
   */
  TextureVector *getTextureVectorPtr () {return &texture_vector;}

  /**** get Reference API ****/
  /***************************/

  /*!
   * \return a reference to the morphed VertexVector of this Mesh
   */
  VertexVector &getVertexVectorRef () {return vertexvector_morph;}

  /*!
   * \return a reference to the FaceVector of this Mesh
   */
  FaceVector &getFaceVectorRef () {return facevector;}

  /*!
   * \return a reference to the MaterialVector of this Mesh
   */
  MaterialVector &getMaterialVectorRef () {return materialvector;}

  /*!
   * \return a reference to the TargetMap of this Mesh
   */
  TargetMap &getTargetMapRef () {return targetmap;}

  /*!
   * \return a reference to the HotspotMap of this Mesh
   */
  HotspotMap &getHotspotMapRef () {return hotspotmap;}

  /*!
   * \return a reference to the VertexGroup of this Mesh
   */
  VertexGroup &getVertexGroupRef () {return vgroup;}

  /*!
   * \return TODO
   */
  TextureVector &getTextureVectorRef () {return texture_vector;}

  /*!
   * \return a reference to the PoseMap of this Mesh
   */
  PoseMap &getPoseMapRef () {return posemap;}  
  
  /*!
   * \return a reference to the CharactersMap of this Mesh
   */
  CharactersMap &getCharactersMapRef () {return charactersmap;}

  /**** copy API ****/
  /******************/

  /*!
  * \return the Mesh's BodySetting
  */
  const BodySettings& getBodySettings () const {return bodyset;}
  
  /*!
  * \return the Mesh's Poses
  */  
  const BodySettings& getPoses () const {return poses;}  

  /**** load Factory API ****/
  /**************************/

  /// Load the Mesh geometry files.
  /*!
  * \param mesh_filename the file with Vertex data to load
  * \param faces_filename the file with Face data to load
  * \return true if files are found
  * \return false if files aren't found
  */
  bool loadMeshFactory (const string& mesh_filename, const string& faces_filename);

  /// Load the Material files.
  /*!
  * \param material_filename the file with Material data to load
  * \param face_colors_filename the file with Face Color data to load
  * \return true if files are found
  * \return false if files aren't found
  */
  bool loadMaterialFactory (const string& material_filename, const string& face_colors_filename);

  /// Load all Targets (recursive) from a directoy.
  /*!
  * \param target_root_path the root path with targets to load
  * \param recursive_level Set the level of directory recursion. See DirectoryList for more help.
  * \return true if files are found
  * \return false if files aren't found
  */
  void loadTargetsFactory (const string& target_root_path, int recursive_level = 1);
  
  /// Load all Pose Targets (recursive) from a directoy.
  /*!
  * \param target_root_path the root path with targets to load
  * \param recursive_level Set the level of directory recursion. See DirectoryList for more help.
  * \return true if files are found
  * \return false if files aren't found
  */  
  void loadPoseTargetsFactory (const string& target_root_path, int recursive_level = 1);  

  void loadAuxiliaryPoseTargetsFactory (const string& target_root_path, int recursive_level = 1);

  /// Load all Characters BodySettings (recursive) from a directoy.
  /*!
  * \param characters_root_path the root path with characters to load
  * \param recursive_level Set the level of directory recursion. See DirectoryList for more help.
  * \return true if files are found
  * \return false if files aren't found
  */  
  void loadCharactersFactory (const string& characters_root_path, int recursive_level = 1);
  
  /**** calculate API ****/
  /***********************/

  /// calculate normals for faces and vertices
  void calcNormals ();

  /// calculate mesh muscles
//  void calcMuscles ();

  /// calculates the centeriods of all VertexGroup data
  void calcCenteroids ();

  // experimental Pose Engine!
  void calcFormFactor ();

  /// Morph the muscles for a specific limb rotation
  /*!
  * \param muscle_group The muscle group which need be morphed.
  * \param e The Euler angel about to morph the muscle group.
  * \return true if sample to rotate muscle group about an angle was found.
  * \return false if sample to rotate muscle group about an angle wasn't found.
  */
  bool makeMuscles (const string& muscle_group, const Euler &e);

  /// Rotate a limb around a joint
  /*!
  * \param joint The name of a joint to rotate about.
  * \param m The rotation Matrix to rotate all points of the limb.
  */
  void rotateLimb (const string& joint, const Matrix &m);

  /*!
  * \return a new Mesh object with same data, but only triangle faces
  */
  Mesh getTriangleMesh ();

  Mesh getVGroupMesh (const string& vgroup_ident);

  /*!
  * \param target_name the previously registered name of a target to morph
  * \param morph_value the value to morph this target
  * \return true if target is found in TargetMap and could be morphed
  * \return false if target isn't found in TargetMap and couldn't be morphed
  */
  bool doMorph (const string& target_name, float morph_value);

  /*!
  * \param bs a BodySettings object to morph the Mesh
  * \param clear default is to delete to yet applied targets 
           before using a BodySettings. Use 'false' to not clear
    the targets before morphing
  */
  void doMorph (const BodySettings &bs, const bool clear = true);

  /*!
  * \param bs a BodySettings object to morph the Mesh
  * \param value of bodysettings application
  * \param clear default is to delete to yet applied targets 
           before using a BodySettings. Use 'false' to not clear
    the targets before morphing
  */
  void doMorph (const BodySettings &bs, float value, bool clear = true);
  
  /// reset the Mesh to loaded state without deformation
  void resetMorph ();

  /// reset the Mesh to loaded state without poses
  void resetPose (const PoseSemiTarget &target);

  /*!
  * \param bs a BodySettings object to pose the Mesh
  * \param clear default is to delete to yet applied targets 
           before using a BodySettings. Use 'false' to not clear
    the targets before morphing
  */
  void doPose (const BodySettings &bs, bool clear = true);

  /*!
  * \param target_name the previously registered name of a target to morph
  * \param morph_value the value to morph this target
  * \return true if target is found in PoseMap and could be morphed
  * \return false if target isn't found in PoseMap and couldn't be morphed
  */  
  bool doPose (const std::string& target_name, float morph_value);

  /*!
  * \param target_name the previously registered name of a target to morph
  * \param morph_value the value to morph this target
  * \param modVertex the vertexes modified by the morphing
  * \return true if target is found in PoseMap and could be morphed
  * \return false if target isn't found in PoseMap and couldn't be morphed
  */   
  void doPose (const string& target_name, float morph_value, const UsedVertex& modVertex);  
    
  bool setPose (const string& target_name, float morph_value);

  /// create a new Target from the current morph state
  /*!
   * \return the new Target
  */
  Target createTarget ();
  
  /// calc centeroid given a vector of vertex numbers
  Vector3f calcCenteroid(const vector<int>& vertexNumbers);
  
  /// switch to pose mode
  void poseMode();
  
  /// switch to body details mode
  void bodyDetailsMode();
};

}

#endif	// MESH_H
