/* Copyright (c) 2002-2012 Croteam Ltd. 
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as published by
the Free Software Foundation


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 Street, Fifth Floor, Boston, MA 02110-1301 USA. */

// If you happen to have the Exploration 3D library (in Engine/exploration3d/), you can enable its features here.
#define USE_E3D 0

#include "Engine/StdH.h"

#include <Engine/Math/Object3D.h>

#include <Engine/Base/Registry.h>
#include <Engine/Base/Stream.h>
#include <Engine/Base/Memory.h>
#include <Engine/Base/ErrorReporting.h>
#include <Engine/Graphics/Color.h>


#include <Engine/Templates/DynamicContainer.cpp>
#include <Engine/Templates/DynamicArray.cpp>
#include <Engine/Templates/StaticStackArray.cpp>

#if USE_E3D
#include <Engine/exploration3d/e3ext.h>
#include <Engine/exploration3d/explor3d.h>
#endif

#undef W
#undef NONE

void FillConversionArrays_t(const FLOATmatrix3D &mTransform);
void ClearConversionArrays( void);
void RemapVertices(BOOL bAsOpened);


/*
 *  Intermediate structures used for converting from Exploration 3D data format into O3D
 */
struct ConversionTriangle {
  INDEX ct_iVtx[3];     // indices of vertices
  INDEX ct_iTVtx[3];    // indices of texture vertices
  INDEX ct_iMaterial;   // index of material
};

struct ConversionMaterial {
  ULONG cm_ulTag;                           // for recognition of material
  CTString cm_strName;                      // material's name
  COLOR cm_colColor;                        // material's color
  CDynamicContainer<INDEX> ms_Polygons;     // indices of polygons in this material
};
// conversion arrays
CDynamicContainer<ConversionMaterial> acmMaterials;
CStaticArray<ConversionTriangle> actTriangles;
CStaticArray<FLOAT3D> avVertices;
CStaticStackArray<FLOAT3D> avDst;
CStaticArray<FLOAT2D> avTextureVertices;
CStaticArray<INDEX> aiRemap;

/////////////////////////////////////////////////////////////////////////////
// Helper functions

//--------------------------------------------------------------------------------------------
class CObjectSectorLock {
private:
	CObjectSector *oscl_posc;						// ptr to object sector that will do lock/unlock
public:
	CObjectSectorLock( CObjectSector *posc);		// lock all object sector arrays
	~CObjectSectorLock();										// unlock all object sector arrays
};

//--------------------------------------------------------------------------------------------
/*
 * To lock all object 3D dyna arrays one must create an instance of CObject3DLock.
 * Locking job is done inside class constructor
 */
CObjectSectorLock::CObjectSectorLock( CObjectSector *posc) {
	ASSERT( posc != NULL);
  oscl_posc = posc;
  posc->LockAll();
}

//--------------------------------------------------------------------------------------------
/*
 * Unlocking of all object 3D dynamic arrays will occur automatically when exiting
 * current scope (routine). This is done in class destructor
 */
CObjectSectorLock::~CObjectSectorLock() {
  oscl_posc->UnlockAll();
}

//--------------------------------------------------------------------------------------------
// function makes Little-Big indian conversion of 4 bytes and returns valid SLONG
inline SLONG ConvertLong( SBYTE *pfm)
{
  UBYTE i;
  UBYTE ret_long[ 4];

  for( i=0; i<4; i++)
    ret_long[ i] = *((UBYTE *) pfm + 3 - i);
  return( *((SLONG *) ret_long) );
};

//--------------------------------------------------------------------------------------------
// function makes Little-Big indian conversion of 2 bytes and returns valid WORD
inline INDEX ConvertWord( SBYTE *pfm)
{
  char aret_word[ 2];

  aret_word[ 0] = *(pfm+1);
  aret_word[ 1] = *(pfm+0);
  INDEX ret_word = (INDEX) *((SWORD *) aret_word);
	return( ret_word);
};

//--------------------------------------------------------------------------------------------
// function makes Little-Big indian conversion of 4 bytes representing float and returns valid float
inline float ConvertFloat( SBYTE *pfm)
{
  UBYTE i;
  char float_no[ 4];

  for( i=0; i<4; i++)
    float_no[ i] = *( pfm + 3 - i);
  return( *((float *) float_no) );
};

//--------------------------------------------------------------------------------------------
// function recognizes and loads many 3D file formats, throws char* errors
#if USE_E3D
HINSTANCE _h3dExploration = NULL;
TInitExploration3D _Init3d;
e3_API*_api;
e3_SCENE*_pe3Scene;
e3_OBJECT *_pe3Object;
HWND _hwnd;
BOOL _bBatchLoading = FALSE;
#endif

// start/end batch loading of 3d objects
void CObject3D::BatchLoading_t(BOOL bOn)
{
#if USE_E3D
  // check for dummy calls
  if (!_bBatchLoading==!bOn) {
    return;
  }

  // if turning on
  if (bOn) {
    // if exploration library not yet loaded
    if( _h3dExploration == NULL) {
      // prepare registry
      REG_SetString("HKEY_LOCAL_MACHINE\\SOFTWARE\\X Dimension\\SeriousEngine\\Plugins\\LWO\\BreakObject", "0");
      REG_SetString("HKEY_LOCAL_MACHINE\\SOFTWARE\\X Dimension\\SeriousEngine\\Plugins\\LWO\\textures", "1");
      REG_SetString("HKEY_LOCAL_MACHINE\\SOFTWARE\\X Dimension\\SeriousEngine\\Plugins\\LWO\\chkwrap", "1");
      REG_SetString("HKEY_LOCAL_MACHINE\\SOFTWARE\\X Dimension\\SeriousEngine\\Plugins\\LWO\\readUView", "1");
      // load the dll
      _h3dExploration = LoadLibrary(EXPLORATION_LIBRRAY);
      // if library not opened
      if(_h3dExploration == NULL) {
        throw("3D Exploration dll not found !");
      }
      _Init3d=(TInitExploration3D)GetProcAddress(_h3dExploration,"InitExploration3D");
      CTString strPlugins = _fnmApplicationPath+"Bin\\3DExplorationPlugins";
      e3_INIT init;
      memset(&init,0,sizeof(init));
      init.e_size     = sizeof(init);
      init.e_registry = "Software\\X Dimension\\SeriousEngine";
      init.e_plugins  = (char*)(const char*)strPlugins;
      if(_Init3d) {
        _api=_Init3d(&init);
      } else  {
		    throw("Unable to initialize 3D object library");
      }
    }

    // if 3dexp window not open yet
    if (_hwnd==NULL) {
      // obtain window needed for 3D exploration library to work
      _hwnd=CreateWindow(EXPLORATION_WINDOW,"Object Loader",0,100,100,100,50,NULL,0,(HINSTANCE) GetModuleHandle( NULL),0);
      //ShowWindow(_hwnd, SW_HIDE);
    }

  // if turning off
  } else {
    // if 3dexp window is open
    if (_hwnd!=NULL) {
      // close it
      DestroyWindow(_hwnd);
      _hwnd = NULL;
    }
  }
  _bBatchLoading = bOn;
#else
  throw("3D Exploration is disabled in this build.");
#endif
}

void CObject3D::LoadAny3DFormat_t(
  const CTFileName &fnmFileName,
  const FLOATmatrix3D &mTransform,
  enum LoadType ltLoadType/*= LT_NORMAL*/)
{
#if USE_E3D
  BOOL bWasOn = _bBatchLoading;
  try {
    if (!_bBatchLoading) {
      BatchLoading_t(TRUE);
    }
    // call file load with file's full path name
    CTString strFile = _fnmApplicationPath+fnmFileName;
    char acFile[MAX_PATH];
    wsprintf(acFile,"%s",strFile);
    e3_LoadFile(_hwnd, acFile);
    _pe3Scene=e3_GetScene(_hwnd);    
    // if scene is successefuly loaded
    if(_pe3Scene != NULL)
    {
      _pe3Object = _pe3Scene->GetObject3d( 0);
      // use different methods to convert into Object3D
      switch( ltLoadType)
      {
      case LT_NORMAL:
        FillConversionArrays_t(mTransform);
        ConvertArraysToO3D();
        break;
      case LT_OPENED:
        FillConversionArrays_t(mTransform);
        RemapVertices(TRUE);
        ConvertArraysToO3D();
        break;
      case LT_UNWRAPPED:
        FLOATmatrix3D mOne;
        mOne.Diagonal(1.0f);
        FillConversionArrays_t(mOne);
        if( avTextureVertices.Count() == 0)
        {
    		  ThrowF_t("Unable to import mapping from 3D object because it doesn't contain mapping coordinates.");
        }

        RemapVertices(FALSE);
        ConvertArraysToO3D();
        break;
      }
      ClearConversionArrays();
    }
    else 
    {
		  ThrowF_t("Unable to load 3D object: %s", (const char *)fnmFileName);
    }
  
    if (!bWasOn) {
      BatchLoading_t(FALSE);
    }
  } catch (char *) {
    if (!bWasOn) {
      BatchLoading_t(FALSE);
    }
    throw;
  }
#endif
}


/*
 * Converts data from Exploration3D format into arrays used for conversion to O3D
 */
void FillConversionArrays_t(const FLOATmatrix3D &mTransform)
{
#if USE_E3D
  // all polygons must be triangles
  if(_pe3Object->_facecount != 0)
  {
    throw("Error: Not all polygons are triangles!");
  }

  // check if we need flipping (if matrix is flipping, polygons need to be flipped)
  const FLOATmatrix3D &m = mTransform;
  FLOAT fDet = 
    m(1,1)*(m(2,2)*m(3,3)-m(2,3)*m(3,2))+
    m(1,2)*(m(2,3)*m(3,1)-m(2,1)*m(3,3))+
    m(1,3)*(m(2,1)*m(3,2)-m(2,2)*m(3,1));
  FLOAT bFlipped = fDet<0;

  // ------------  Convert object vertices (coordinates)
  INDEX ctVertices = _pe3Object->pointcount;
  avVertices.New(ctVertices);
  // copy vertices
  for( INDEX iVtx=0; iVtx<ctVertices; iVtx++)
  {
    avVertices[iVtx] = ((FLOAT3D &)_pe3Object->points[iVtx])*mTransform;
    avVertices[iVtx](1) = -avVertices[iVtx](1);
    avVertices[iVtx](3) = -avVertices[iVtx](3);
  }

  // ------------ Convert object's mapping vertices (texture vertices)
  INDEX ctTextureVertices = _pe3Object->txtcount;
  avTextureVertices.New(ctTextureVertices);
  // copy texture vertices
  for( INDEX iTVtx=0; iTVtx<ctTextureVertices; iTVtx++)
  {
    avTextureVertices[iTVtx] = (FLOAT2D &)_pe3Object->txtpoints[iTVtx];
  }
  
  // ------------ Organize triangles as list of surfaces
  // allocate triangles
  INDEX ctTriangles = _pe3Object->facecount;
  actTriangles.New(ctTriangles);

  acmMaterials.Lock();
  
  // sort triangles per surfaces
  for( INDEX iTriangle=0; iTriangle<ctTriangles; iTriangle++)
  {
    ConversionTriangle &ctTriangle = actTriangles[iTriangle];
    e3_TFACE *pe3Triangle = _pe3Object->GetFace( iTriangle);
    // copy vertex indices
    if (bFlipped) {
      ctTriangle.ct_iVtx[0] = pe3Triangle->v[2];
      ctTriangle.ct_iVtx[1] = pe3Triangle->v[1];
      ctTriangle.ct_iVtx[2] = pe3Triangle->v[0];
    } else {
      ctTriangle.ct_iVtx[0] = pe3Triangle->v[0];
      ctTriangle.ct_iVtx[1] = pe3Triangle->v[1];
      ctTriangle.ct_iVtx[2] = pe3Triangle->v[2];
    }
    // copy texture vertex indices
    if (bFlipped) {
      ctTriangle.ct_iTVtx[0] = pe3Triangle->t[2];
      ctTriangle.ct_iTVtx[1] = pe3Triangle->t[1];
      ctTriangle.ct_iTVtx[2] = pe3Triangle->t[0];
    } else {
      ctTriangle.ct_iTVtx[0] = pe3Triangle->t[0];
      ctTriangle.ct_iTVtx[1] = pe3Triangle->t[1];
      ctTriangle.ct_iTVtx[2] = pe3Triangle->t[2];
    }

    // obtain material
    e3_MATERIAL *pe3Mat = pe3Triangle->material;
    BOOL bNewMaterial = TRUE;
    // attach triangle into one material
    for( INDEX iMat=0; iMat<acmMaterials.Count(); iMat++)
    {
      // if this material already exist in array of materu
      if( acmMaterials[ iMat].cm_ulTag == (ULONG) pe3Mat)
      {
        // set index of surface
        ctTriangle.ct_iMaterial = iMat;
        // add triangle into surface list of triangles
        INDEX *piNewTriangle = new INDEX(1);
        *piNewTriangle = iTriangle;
        acmMaterials[ iMat].ms_Polygons.Add( piNewTriangle);
        bNewMaterial = FALSE;
        continue;
      }
    }
    // if material hasn't been added yet
    if( bNewMaterial)
    {
      // add new material
      ConversionMaterial *pcmNew = new ConversionMaterial;
      acmMaterials.Unlock();
      acmMaterials.Add( pcmNew);
      acmMaterials.Lock();
      // set polygon's material index 
      INDEX iNewMaterial = acmMaterials.Count()-1;
      ctTriangle.ct_iMaterial = iNewMaterial;
      // add triangle into new surface's list of triangles
      INDEX *piNewTriangle = new INDEX(1);
      *piNewTriangle = iTriangle;
      acmMaterials[ iNewMaterial].ms_Polygons.Add( piNewTriangle);
      
      // remember recognition tag (ptr)
      pcmNew->cm_ulTag = (ULONG) pe3Mat;

      // ---------- Set material's name
      // if not default material
      if( pe3Mat != NULL && pe3Mat->name != NULL)
      {
        acmMaterials[iNewMaterial].cm_strName = CTString(pe3Mat->name);
        // get color
        COLOR colColor = CLR_CLRF( pe3Mat->GetDiffuse().rgb());
        acmMaterials[iNewMaterial].cm_colColor = colColor;
      }
      else
      {
        acmMaterials[iNewMaterial].cm_strName = "Default";
        acmMaterials[iNewMaterial].cm_colColor = C_GRAY;
      }
    }
  }
  acmMaterials.Unlock();
#endif
}

void ClearConversionArrays( void)
{
  acmMaterials.Clear();
  actTriangles.Clear();
  avVertices.Clear();
  avTextureVertices.Clear();
  aiRemap.Clear();
}

void RemapVertices(BOOL bAsOpened)
{
  {INDEX ctSurf = 0;
  // fill remap array with indices of vertices in order how they appear per polygons
  {FOREACHINDYNAMICCONTAINER(acmMaterials, ConversionMaterial, itcm)
  {
    _RPT1(_CRT_WARN, "Indices of polygons in surface %d:", ctSurf);
    // for each polygon in surface
    {FOREACHINDYNAMICCONTAINER(itcm->ms_Polygons, INDEX, itipol)
    {
      _RPT1(_CRT_WARN, " %d,", *itipol);
    }}
    _RPT0(_CRT_WARN, "\n");
    ctSurf++;
  }}
  
  _RPT0(_CRT_WARN, "Polygons and their vertex indices:\n");
  for( INDEX ipol=0; ipol<actTriangles.Count(); ipol++)
  {
    INDEX idxVtx0 = actTriangles[ipol].ct_iVtx[0];
    INDEX idxVtx1 = actTriangles[ipol].ct_iVtx[1];
    INDEX idxVtx2 = actTriangles[ipol].ct_iVtx[2];
    _RPT4(_CRT_WARN, "Indices of vertices in polygon %d : (%d, %d, %d)\n", ipol, idxVtx0, idxVtx1, idxVtx2);
  }}

  INDEX ctVertices = avVertices.Count();
  aiRemap.New(ctVertices);

  // fill remap array with indices of vertices in order how they appear per polygons
  FOREACHINDYNAMICCONTAINER(acmMaterials, ConversionMaterial, itcm)
  {
    // fill remap array with -1
    for( INDEX iRemap=0; iRemap<ctVertices; iRemap++)
    {
      aiRemap[iRemap] = -1;
    }
    // reset 'vertex in surface' counter
    INDEX ctvx = 0;

    // for each polygon in surface
    {FOREACHINDYNAMICCONTAINER(itcm->ms_Polygons, INDEX, itipol)
    {
      INDEX idxPol = *itipol;
      // for each vertex in polygon
      for(INDEX iVtx=0; iVtx<3; iVtx++)
      {
        // get vertex's index
        INDEX idxVtx = actTriangles[idxPol].ct_iVtx[iVtx];
        if( aiRemap[idxVtx] == -1)
        {
          aiRemap[idxVtx] = ctvx;
          ctvx++;
        }
      }
    }}

    INDEX ctOld = avDst.Count();
    // allocate new block of vertices used in this surface
    FLOAT3D *pavDst = avDst.Push( ctvx);

    // for each polygon in surface
    {FOREACHINDYNAMICCONTAINER(itcm->ms_Polygons, INDEX, itipol)
    {
      INDEX iPol=*itipol;
      // for each vertex in polygon
      for(INDEX iVtx=0; iVtx<3; iVtx++)
      {
        // get vertex's index
        INDEX idxVtx = actTriangles[iPol].ct_iVtx[iVtx];
        // get remapped index
        INDEX iRemap = aiRemap[idxVtx];
        // if cutting object
        if( bAsOpened)
        {
          // copy vertex coordinate
          pavDst[ iRemap] = avVertices[idxVtx];
        }
        // if creating unwrapped mapping
        else
        {
          // copy texture coordinate
          FLOAT3D vMap;
          vMap(1) = avTextureVertices[actTriangles[iPol].ct_iTVtx[iVtx]](1);
          vMap(2) = -avTextureVertices[actTriangles[iPol].ct_iTVtx[iVtx]](2);
          vMap(3) = 0;
          pavDst[ iRemap] = vMap;
        }
        // remap index of polygon vertex
        actTriangles[iPol].ct_iVtx[iVtx] = iRemap+ctOld;
      }
    }}
  }
  aiRemap.Clear();
  
  // replace remapped array of vertices over original one
  avVertices.Clear();
  avVertices.New(avDst.Count());
  for( INDEX iVtxNew=0; iVtxNew<avDst.Count(); iVtxNew++)
  {
    avVertices[iVtxNew] = avDst[iVtxNew];
  }
  avDst.PopAll();

  {INDEX ctSurf = 0;
  // fill remap array with indices of vertices in order how they appear per polygons
  {FOREACHINDYNAMICCONTAINER(acmMaterials, ConversionMaterial, itcm)
  {
    _RPT1(_CRT_WARN, "Indices of polygons in surface %d:", ctSurf);
    // for each polygon in surface
    {FOREACHINDYNAMICCONTAINER(itcm->ms_Polygons, INDEX, itipol)
    {
      _RPT1(_CRT_WARN, " %d,", *itipol);
    }}
    _RPT0(_CRT_WARN, "\n");
    ctSurf++;
  }}
  
  _RPT0(_CRT_WARN, "Polygons and their vertex indices:\n");
  for( INDEX ipol=0; ipol<actTriangles.Count(); ipol++)
  {
    INDEX idxVtx0 = actTriangles[ipol].ct_iVtx[0];
    INDEX idxVtx1 = actTriangles[ipol].ct_iVtx[1];
    INDEX idxVtx2 = actTriangles[ipol].ct_iVtx[2];
    _RPT4(_CRT_WARN, "Indices of vertices in polygon %d : (%d, %d, %d)\n", ipol, idxVtx0, idxVtx1, idxVtx2);
  }}
}

/*
 * Convert streihgtfowrard from intermediate structures into O3D
 */
void CObject3D::ConvertArraysToO3D( void)
{
  acmMaterials.Lock();
  // create one sector
  CObjectSector &osc = *ob_aoscSectors.New(1);
  // this will lock at the instancing and unlock while destructing all sector arrays
	CObjectSectorLock OSectorLock(&osc);

  // ------------ Vertices
  INDEX ctVertices = avVertices.Count();
  CObjectVertex *pVtx = osc.osc_aovxVertices.New(ctVertices);
  for(INDEX iVtx=0; iVtx<ctVertices; iVtx++)
  {
    pVtx[ iVtx] = FLOATtoDOUBLE( avVertices[iVtx]);
  }
	
  // ------------ Materials
  INDEX ctMaterials = acmMaterials.Count();
  osc.osc_aomtMaterials.New( ctMaterials);
  for( INDEX iMat=0; iMat<ctMaterials; iMat++)
  {
    osc.osc_aomtMaterials[iMat] = CObjectMaterial( acmMaterials[iMat].cm_strName);
    osc.osc_aomtMaterials[iMat].omt_Color = acmMaterials[iMat].cm_colColor;
  }

  // ------------ Edges and polygons
  INDEX ctTriangles = actTriangles.Count();
	CObjectPolygon *popo = osc.osc_aopoPolygons.New(ctTriangles);
	CObjectPlane *popl = osc.osc_aoplPlanes.New(ctTriangles);
  // we need 3 edges for each polygon
  CObjectEdge *poedg = osc.osc_aoedEdges.New(ctTriangles*3);
  for(INDEX iTri=0; iTri<ctTriangles; iTri++)
  {
    // obtain triangle's vertices
    CObjectVertex *pVtx0 = &osc.osc_aovxVertices[ actTriangles[iTri].ct_iVtx[0]];
    CObjectVertex *pVtx1 = &osc.osc_aovxVertices[ actTriangles[iTri].ct_iVtx[1]];
    CObjectVertex *pVtx2 = &osc.osc_aovxVertices[ actTriangles[iTri].ct_iVtx[2]];

    // create edges
    poedg[iTri*3+0] = CObjectEdge( *pVtx0, *pVtx1);
    poedg[iTri*3+1] = CObjectEdge( *pVtx1, *pVtx2);
    poedg[iTri*3+2] = CObjectEdge( *pVtx2, *pVtx0);

    // create polygon edges
    popo[iTri].opo_PolygonEdges.New(3);
    popo[iTri].opo_PolygonEdges.Lock();
    popo[iTri].opo_PolygonEdges[0].ope_Edge = &poedg[iTri*3+0];
    popo[iTri].opo_PolygonEdges[1].ope_Edge = &poedg[iTri*3+1];
    popo[iTri].opo_PolygonEdges[2].ope_Edge = &poedg[iTri*3+2];
    popo[iTri].opo_PolygonEdges.Unlock();

    // set material
    popo[iTri].opo_Material = &osc.osc_aomtMaterials[ actTriangles[iTri].ct_iMaterial];
    popo[iTri].opo_colorColor = popo[iTri].opo_Material->omt_Color;
    
    // create and set plane
    popl[iTri] = DOUBLEplane3D( *pVtx0, *pVtx1, *pVtx2);
    popo[iTri].opo_Plane = &popl[iTri];
  }
  acmMaterials.Unlock();
}