//===========================================================================//
// File: ExportUntil.cpp 
// Project: MechWarrior 4
// Contents: helper function for exporters
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// 05/10/00 fwang Initial coding, 
//---------------------------------------------------------------------------//
// Copyright (C) 1998, Fasa Interactive, Inc.
// All Rights reserved worldwide
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL
//===========================================================================//


#include "stdafx.h"

#ifdef STRICT // Already defined by stdafx,
#undef STRICT // so we avoid warning linking msg
#endif
#ifdef _MBCS // The same as above
#undef _MBCS
#endif

#include "AnimationSuite.h"
#include "ArmatureExport.h"
#include "ExportUtil.h"

//undef Verify

#ifndef _DEBUG //This is a hack To make it link in Release
#undef Spew
void Spew() {}
#endif


// Helper functions

/*******************************************************************************
/* function name: ClearDirectory
/* description: Recursively clear all files in a directory
/*******************************************************************************/
void ClearDirectory(CString dir)
{
    
    HANDLE hfl;
    WIN32_FIND_DATA fd;
    
    if(dir[dir.GetLength()-1]=='\\')
    {
        dir=dir.Left(dir.GetLength()-1);
    }
    
    fd.dwFileAttributes=NULL;
    hfl=FindFirstFile(dir+"\\*.*",&fd);
    
    if(INVALID_HANDLE_VALUE!=hfl)
    {
        do
        {
            if(fd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)
            {
                if(strcmp(fd.cFileName,".") && strcmp(fd.cFileName,"..") )
                {
                    ClearDirectory(dir+"\\"+fd.cFileName); 
                    RemoveDirectory(dir+"\\"+fd.cFileName);
                }
            }
            else
            {
                DeleteFile(dir+"\\"+fd.cFileName);
            }
            
        } while(FindNextFile(hfl,&fd));
    }
    
}

/*******************************************************************************
/* function name: MakeElementRoot
/* description: 
/*******************************************************************************/
ElementRenderer::GroupElement *MakeElementRoot()
{
    ElementRenderer::GroupElement *root = new ElementRenderer::GroupElement;
    Check_Object(root);
    return root;
}

/*******************************************************************************
/* function name: MakeElementScene
/* description: 
/*******************************************************************************/
ElementProxies::ElementSceneProxy *MakeElementScene(ElementRenderer::GroupElement *pRoot,CString TexturePath)
{
    ElementProxies::ElementSceneProxy *element_scene =
        ElementProxies::ElementSceneProxy::MakeProxy(
        pRoot,
        TexturePath
        );
    Check_Object(element_scene);
    return element_scene;
}

/*******************************************************************************
/* function name: InitElementScene
/* description: 
/*******************************************************************************/
ElementProxies::ElementSceneProxy *InitElementScene(CString texturepath)
{
    ElementRenderer::GroupElement *pRoot = MakeElementRoot();
    ElementProxies::ElementSceneProxy *element_scene = MakeElementScene(pRoot,texturepath);
    return element_scene;
}

/*******************************************************************************
/* function name: DestroyElementScene
/* description: 
/*******************************************************************************/
int DestroyElementScene(ElementProxies::ElementSceneProxy *element_scene)
{
    Verify(element_scene->GetReferenceCount() == 1);
    element_scene->Destroy();
    return 0;
}

/*******************************************************************************
/* function name: MakeMaxScene
/* description: 
/*******************************************************************************/
MAXProxies::MAXScene *MakeMaxScene(Interface *i,CString strHintFile)
{
    MAXProxies::MAXScene *max_scene = MAXProxies::MAXScene::MakeProxy(i,strHintFile);
    Check_Object(max_scene);
    return max_scene;
}

/*******************************************************************************
/* function name: CopySceneMaxToElement
/* description: Copy a MAXScene proxy into a ElementSceneProxy
/*******************************************************************************/
int CopySceneMaxToElement(ElementProxies::ElementSceneProxy *element_scene,
						  MAXProxies::MAXScene *max_scene,
						  bool bFlatShading,Interface *ip,int& progress,bool suppress)
{
    CString ini_file = ip->GetDir(APP_PLUGCFG_DIR);
    ini_file += "\\Plugins.ini";
    Stuff::NotationFile config_file(ini_file);
    MAXProxies::MAXCopyProcess copy_process(&config_file,ip,progress,suppress,(LPVOID)&ReportError);
    element_scene->Copy(&copy_process, max_scene);
    int ref_count = element_scene->GetReferenceCount();
    
    MAXProxies::MAXFlattenHierarchyProcess flatten_hierarchy(&config_file,ip,progress,suppress);
    element_scene->FlattenHierarchy(&flatten_hierarchy);
    Verify(element_scene->GetReferenceCount() == ref_count);
    
    MAXProxies::MAXSplitByStateProcess split_process(&config_file,ip,progress,suppress);
    element_scene->SplitByState(&split_process);
    Verify(element_scene->GetReferenceCount() == ref_count);
    
    Proxies::MakeSingleSidedProcess sided_process(&config_file);
    element_scene->MakeSingleSided(&sided_process);
    Verify(element_scene->GetReferenceCount() == ref_count);
    
	if (bFlatShading) {
		Proxies::OptimizeFlatShadingProcess shading_process(&config_file);
		element_scene->OptimizeFlatShading(&shading_process);
		Verify(element_scene->GetReferenceCount() == ref_count);
	}


    return 0;
}

/*******************************************************************************
/* function name: CopyPolygonMeshProxyToElement
/* description: Copy a MAXPolygonMesh into a ElementSceneProxy
/*******************************************************************************/
int CopyPolygonMeshProxyToElement(ElementProxies::ElementSceneProxy *element_scene,
								  MAXProxies::MAXPolygonMesh *max_mesh,
								  bool bFlatShading,
								  Interface *ip,int& progress,bool suppress)
{
    ElementProxies::ElementPolygonMeshProxy *element_mesh = 
        Cast_Object(ElementProxies::ElementPolygonMeshProxy *,
        element_scene->AppendNewPolygonMeshProxy());

    CString ini_file = ip->GetDir(APP_PLUGCFG_DIR);
    ini_file += "\\Plugins.ini";
    Stuff::NotationFile config_file(ini_file);
    MAXProxies::MAXCopyProcess copy_process(&config_file,ip,progress,suppress,(LPVOID)&ReportError);
    element_mesh->Copy(&copy_process,max_mesh);
	
    element_mesh->SetLocalToParent(Stuff::LinearMatrix4D::Identity);
    element_mesh->DetachReference();
    
    int ref_count = element_scene->GetReferenceCount();
    
    MAXProxies::MAXFlattenHierarchyProcess flatten_hierarchy(&config_file,ip,progress,suppress);
    element_scene->FlattenHierarchy(&flatten_hierarchy);
    Verify(element_scene->GetReferenceCount() == ref_count);
    
    MAXProxies::MAXSplitByStateProcess split_process(&config_file,ip,progress,suppress);
    element_scene->SplitByState(&split_process);
    Verify(element_scene->GetReferenceCount() == ref_count);
    
    Proxies::MakeSingleSidedProcess sided_process(&config_file);
    element_scene->MakeSingleSided(&sided_process);
    Verify(element_scene->GetReferenceCount() == ref_count);
    
	if (bFlatShading) {
		Proxies::OptimizeFlatShadingProcess shading_process(&config_file);
		element_scene->OptimizeFlatShading(&shading_process);
		Verify(element_scene->GetReferenceCount() == ref_count);
	}
	
    return 0;
}

/*******************************************************************************
/* function name: CopyGroupProxyToElement
/* description: Copy a MAXGroup into a ElementSceneProxy
/*******************************************************************************/
int CopyGroupProxyToElement(ElementProxies::ElementSceneProxy *element_scene,
							MAXProxies::MAXGroup *proxy,bool bFlatShading, 
							Interface *ip,int& progress,bool suppress)
{
	int err = 0;
	Proxies::GroupProxy *group = Cast_Object(Proxies::GroupProxy *,proxy);
	Proxies::ChildProxy *child = group->UseFirstChildProxy();
	while (child)
	{
		Check_Object(child);
		Proxies::ChildProxy *next = child->UseNextSiblingProxy();
		Stuff::MString mstr;
		child->GetName(&mstr);
		CString name(mstr);
		name.MakeLower();

		// Do not copy lights and keep nodes
		if (name.Find("light") < 0 && name.Find("keep_") < 0 && 
			child->IsDerivedFrom(Proxies::PolygonMeshProxy::DefaultData)) {

			MAXProxies::MAXPolygonMesh *max_mesh = 
				Cast_Object(MAXProxies::MAXPolygonMesh *,child);
			err = CopyPolygonMeshProxyToElement(
				element_scene, max_mesh, bFlatShading, ip, progress, suppress);
		}
		child->DetachReference();
		if (err)
			return err;
		child = next;
	}

	return err;
}

/*******************************************************************************
/* function name: FusePolygonMeshProxy
/* description: Go through all sibling ElementPolygonMeshProxy 
/*	and fuse polygons meshes with identical states
/*******************************************************************************/
int FusePolygonMeshProxy(ElementProxies::ElementPolygonMeshProxy *mesh,
					 int child_count, Interface *ip,int& progress,bool suppress)
{
    ElementProxies::ElementPolygonMeshProxy *next_mesh =
        static_cast<ElementProxies::ElementPolygonMeshProxy*>(
        mesh->UseNextSiblingProxy()
        );
    
    //
    // First count up the number of children and make an array for the meshes
    //
    //if (next_mesh)
    {
        Verify(child_count > 0);
        
        Stuff::DynamicArrayOf<ElementProxies::ElementPolygonMeshProxy *>
            fused_children(child_count-1);
        Stuff::DynamicArrayOf<Stuff::DynamicArrayOf<Proxies::PolygonProxy *> >
            polys_array(child_count-1);
        //
        // Make the array for the polygons to add, then start spinning through
        // the children
        //
        Stuff::DynamicArrayOf<Proxies::PolygonProxy *> new_polys;
        unsigned poly_count = 0;
        child_count = 0;
        
        while (next_mesh) {
            Check_Object(next_mesh);
            
            ElementProxies::ElementPolygonMeshProxy *next =
                static_cast<ElementProxies::ElementPolygonMeshProxy*>(
                next_mesh->UseNextSiblingProxy()
                );
            fused_children[child_count] = next_mesh;
            //
            // Get its polys and copy them into the large array
            //
            unsigned start = poly_count;
            unsigned len = next_mesh->UsePolygonArray(&polys_array[child_count]);
            
            poly_count += len;
            new_polys.SetLength(poly_count);
            for (unsigned i=0; i<len; ++i)
                new_polys[start++] = polys_array[child_count][i];
            next_mesh = next;
            ++child_count;
        }
        
        CString ini_file = ip->GetDir(APP_PLUGCFG_DIR);
        ini_file += "\\Plugins.ini";
        Stuff::NotationFile config_file(ini_file);
        MAXProxies::MAXCopyProcess copy_process(&config_file,ip,progress,suppress,(LPVOID)&ReportError);
        mesh->AddPolygons(&copy_process, new_polys);
        
		//
        // Now destroy the old meshes
        //
        for (child_count=0; child_count<fused_children.GetLength(); ++child_count)
        {
            next_mesh = fused_children[child_count];
            Check_Object(next_mesh);
            next_mesh->DetachArrayReferences(&polys_array[child_count]);
            Verify(next_mesh->GetReferenceCount() == 1);
            next_mesh->Destroy();
        }
    }
	return 0;
}

/*******************************************************************************
/* function name: FuseElementScene
/* description: Go through all children ElementPolygonMeshProxy 
/*	and fuse polygons meshes with identical states
/*******************************************************************************/
int FuseElementScene(ElementProxies::ElementSceneProxy *element_scene,
					 Interface *ip,int& progress,bool suppress)
{
    
	Proxies::ChildProxy *first_child = element_scene->UseFirstChildProxy();
    ElementProxies::ElementPolygonMeshProxy *first_mesh =
        Cast_Object(ElementProxies::ElementPolygonMeshProxy*, first_child);


	int err = FusePolygonMeshProxy(first_mesh, element_scene->GetChildCount(), 
		ip, progress, suppress);
    first_child->DetachReference();
    return err;
}

/*******************************************************************************
/* function name: OptimizeElementScene
/* description: Computing bounding spheres for all children ElementPolygonMeshProxy
/*******************************************************************************/
int OptimizeElementScene(ElementProxies::ElementSceneProxy *element_scene)
{
    Proxies::ChildProxy *first_child = element_scene->UseFirstChildProxy();
    ElementProxies::ElementPolygonMeshProxy *first_mesh =
        Cast_Object(ElementProxies::ElementPolygonMeshProxy*, first_child);

    ElementProxies::OptimizeSyncProcess optimize;
    if (optimize.OptimizeSync(first_mesh))
        first_mesh->DetachReference();
    
	return 0;
}

/*******************************************************************************
/* function name: SetExplicitAlphaMode
/* description: 
/*******************************************************************************/
int SetExplicitAlphaMode(ElementProxies::ElementSceneProxy *element_scene, MidLevelRenderer::MLRState::AlphaMode newAlphaMode)
{
    Proxies::ChildProxy *first_child = element_scene->UseFirstChildProxy();
    ElementProxies::ElementPolygonMeshProxy * first_mesh =
        Cast_Object(ElementProxies::ElementPolygonMeshProxy*, first_child);

	ElementProxies::SetExplicitAlphaModeProcess setAlphaMode;
	if (setAlphaMode.SetExplicitAlphaMode(first_mesh, newAlphaMode))
		first_mesh->DetachReference();
	
    return 0;
}

/*******************************************************************************
/* function name: SetExplicitLightingMode
/* description: 
/*******************************************************************************/
int SetExplicitLightingMode(ElementProxies::ElementSceneProxy *element_scene, MidLevelRenderer::MLRState::LightingMode newLightingMode)
{
    Proxies::ChildProxy *first_child = element_scene->UseFirstChildProxy();
    ElementProxies::ElementPolygonMeshProxy * first_mesh =
        Cast_Object(ElementProxies::ElementPolygonMeshProxy*, first_child);

	ElementProxies::SetExplicitLightingModeProcess setLightingMode;
	if (setLightingMode.SetExplicitLightingMode(first_mesh, newLightingMode))
		first_mesh->DetachReference();

    return 0;
}

/*******************************************************************************
/* function name: SetExplicitFogMode
/* description: 
/*******************************************************************************/
int SetExplicitFogMode(ElementProxies::ElementSceneProxy *element_scene, int newFogMode)
{
    Proxies::ChildProxy *first_child = element_scene->UseFirstChildProxy();
    ElementProxies::ElementPolygonMeshProxy * first_mesh =
        Cast_Object(ElementProxies::ElementPolygonMeshProxy*, first_child);

	ElementProxies::SetExplicitFogModeProcess setFogMode;
	if (setFogMode.SetExplicitFogMode(first_mesh, newFogMode))
		first_mesh->DetachReference();
		
    return 0;
}

/*******************************************************************************
/* function name: GetElementShape
/* description: 
/*******************************************************************************/
ElementRenderer::Element *GetElementShape(ElementProxies::ElementSceneProxy *element_scene)
{
    Proxies::ChildProxy *first_child = element_scene->UseFirstChildProxy();
    ElementProxies::ElementPolygonMeshProxy *first_mesh =
        Cast_Object(ElementProxies::ElementPolygonMeshProxy*, first_child);
    ElementRenderer::Element *element_shape = first_mesh->GetProxiedShape();
    first_mesh->DetachReference();
    return element_shape;
}

/*******************************************************************************
/* function name: SaveElementShape
/* description: 
/*******************************************************************************/
int SaveElementShape(ElementRenderer::Element *element_shape,CString filename)
{
    Check_Object(Stuff::FileStreamManager::Instance);
    Stuff::FileStream element_stream;
    element_stream.Open(filename, Stuff::FileStream::WriteOnly);
    ElementRenderer::WriteERFVersion(&element_stream);
    element_shape->SetRootMode();
    element_shape->Save(&element_stream);
    return 0;
}

/*******************************************************************************
/* function name: SaveElementScene
/* description: 
/*******************************************************************************/
int SaveElementScene(ElementProxies::ElementSceneProxy *element_scene,CString filename)
{
    ElementRenderer::Element *pShape = GetElementShape(element_scene);
    return SaveElementShape(pShape,filename);
}

/*******************************************************************************
/* function name: GetMeshMatrices
/* description: 
/*******************************************************************************/
int GetMeshMatrices(Proxies::GenericProxy *proxy,CMapStringToPtr *pTransInfo)
{
    if(proxy->IsDerivedFrom(Proxies::SceneProxy::DefaultData))
    {
        Proxies::SceneProxy *scene=Cast_Object(Proxies::SceneProxy *,proxy);
        Proxies::ChildProxy *child = scene->UseFirstChildProxy();
        while (child)
        {
            Check_Object(child);
            GetMeshMatrices(child,pTransInfo);
            Proxies::ChildProxy *next = child->UseNextSiblingProxy();
            child->DetachReference();
            child = next;
        }
        
    }
    else
        if(proxy->IsDerivedFrom(Proxies::GroupProxy::DefaultData))
        {
            Proxies::GroupProxy *group=Cast_Object(Proxies::GroupProxy *,proxy);
            Proxies::ChildProxy *child = group->UseFirstChildProxy();
            while (child)
            {
                Check_Object(child);
                GetMeshMatrices(child,pTransInfo);
                Proxies::ChildProxy *next = child->UseNextSiblingProxy();
                child->DetachReference();
                child = next;
            }
            
        }
        else
            if(proxy->IsDerivedFrom(Proxies::PolygonMeshProxy::DefaultData))
            {
                Proxies::PolygonMeshProxy *mesh=Cast_Object(Proxies::PolygonMeshProxy *,proxy);
                Stuff::MString mstr;
                CString myname,myprefix;
                mesh->GetName(&mstr);
                mstr.RemoveSpaces();
                mstr.ToLower();
                myname=(char *)mstr;
                myprefix=myname.Left(myname.Find("_",0));
                if (mesh->GetParentGroupProxy())
                {
                    MString tmstr;
                    mesh->GetParentGroupProxy()->GetName(&tmstr);
                    tmstr.RemoveSpaces();
                    tmstr.ToLower();
                    CString prefix,parent_name((char *)tmstr);
                    prefix=parent_name.Left(parent_name.Find("_",0));
                    
                    if(myprefix.CompareNoCase("cv"))
                    {
                        if(prefix.CompareNoCase("joint"))
                        {
                            ReportError(CString(myname)+" :(Hierarchy error)Mesh is not attached to a joint");
                            return 1;
                        }
                        Stuff::LinearMatrix4D *pObjectToWorld = new Stuff::LinearMatrix4D;
                        mesh->GetParentGroupProxy()->GetLocalToWorld(pObjectToWorld);
                        (*pTransInfo)[mstr] = pObjectToWorld;
                    }
                    else
                    {
                        if(prefix.CompareNoCase("joint"))
                        {
                            ReportError(CString(myname)+" :(Hierarchy error)CV is not attached to a joint");
							return 1;
                        }
                    }
                }
            }
            return 0;
}

/*******************************************************************************
/* function name: FindNodeByName
/* description: helper function, find a node by node in the tree
/*******************************************************************************/
INode *FindNodeByName(INode *nde,CString &str)
{
    
    {
        CString str2(nde->GetName());
        if(str2.CompareNoCase(str)==0)
            return nde;
    }
    
    for(int i=0;i<nde->NumberOfChildren();i++)
    {
        INode *tnde;
        tnde=FindNodeByName(nde->GetChildNode(i),str);
        if(tnde!=NULL) return tnde;
    }
    
    return NULL;
}

/*******************************************************************************
/* function name: FindNodeByNames
/* description: helper function, find a node by node in the tree
/*******************************************************************************/
INode *FindNodeByNames(INode *nde, CString strs[])
{
	INode *result = NULL;
	for (int i=0; strs[i]!= ""; i++) {
		if (result = FindNodeByName(nde, strs[i])) 
			return result;
	}

	return NULL;
}

/*******************************************************************************
/* function name: GetGamePath
/* description: 
/*******************************************************************************/
CString GetGamePath(Interface *ip)
{
	CString game_path;
	TSTR ini_file = ip->GetDir(APP_PLUGCFG_DIR);
	ini_file += "\\Plugins.ini";
	Check_Pointer(Stuff::FileStreamManager::Instance);

    Stuff::FileStream file_stream(ini_file);
    if (!file_stream.IsFileOpened())
    {
        //AFX_MANAGE_STATE(AfxGetStaticModuleState( ));
        //AfxMessageBox(CString("ERROR: Must have the PlugIns.ini file in : ") + ini_file, MB_OK);
        ReportError(CString(ip->GetCurFileName())+": ERROR: Must have the PlugIns.ini file in "+ini_file);
    }
	
	Stuff::NotationFile data_file(ini_file);

	const char* read_dir;
	Stuff::Page *page = data_file.FindPage("BRBData");
	if (page && page->GetEntry("GameDir", &read_dir))
	{
		game_path = read_dir;
	}
	return game_path+"\\";
}

/*******************************************************************************
/* function name: IsNodeCamera
/* description: 
/*******************************************************************************/
bool IsNodeCamera(INode *node)
{
    if (node == NULL) return false;
    
    ObjectState os = node->EvalWorldState(0);
    if (os.obj)
    {
        if (os.obj->SuperClassID() == CAMERA_CLASS_ID)
        {
            return true;
        }
    }
    return false;
}

/*******************************************************************************
/* function name: IsNodeGeometry
/* description: 
/*******************************************************************************/
bool IsNodeGeometry(INode *node)
{
    if (node == NULL) return false;
    
    ObjectState os = node->EvalWorldState(0);
    if (os.obj)
    {
        if (os.obj->SuperClassID() == GEOMOBJECT_CLASS_ID)
        {
            if (os.obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0)))
            {
                return true;
            }
        }
    }
    return false;
}

/*******************************************************************************
/* function name: IsNodePartOfGroupGeometry
/* description: 
/*******************************************************************************/
bool IsNodePartOfGroupGeometry(INode *node)
{
	// Is it a geometry?
	if (!IsNodeGeometry(node))
		return false;

	// Does it have a parent?
	INode *group= node->GetParentNode();
    if (!group)
		return false;

	// Is it the first non-light geometry child node of its parent?
	Stuff::MString name = node->GetName();
	for (int x=0;x<group->NumberOfChildren();x++)
	{
		INode *child = group->GetChildNode(x);
		Stuff::MString childName = child->GetName();
		CString strChildName(childName);
		strChildName.MakeLower();
		if (IsNodeGeometry(child) && 
			strChildName.Find("light")<0 && 
			strChildName.Find("keep_")<0) {
			if (strcmp(childName,name))
				return true;
			else
				return false;
		}
	}

    return false;
}

/*******************************************************************************
/* function name: GetLightNode
/* description: 
/*******************************************************************************/
INode *GetLightNode(INode *node)
{
	// Is it a geometry?
	if (!IsNodeGeometry(node))
		return NULL;

	// Does it have a parent?
	INode *group= node->GetParentNode();
    if (!group)
		return NULL;

	for (int x=0;x<group->NumberOfChildren();x++)
	{
		INode *child = group->GetChildNode(x);
		CString name = child->GetName();
		name.MakeLower();
		if (IsNodeGeometry(child) && name.Find("light")>=0)
			return child;
	}

	return NULL;
}

// Global Error Reporting
CString g_strLogFile("C:\\export.log");
int g_bCleanupErrors = 0;

/*******************************************************************************
/* function name: ReportError
/* description: 
/*******************************************************************************/
int ReportError(const char * str,bool bSupress)
{
	if (!bSupress)
	{
		MessageBox(NULL,str,"Animation Suite Error",MB_OK);
	}

	FILE *f = fopen(g_strLogFile,"a");
	fprintf(f,"%s\n", (const char *)str);
	fclose(f);
	return 0;
}
