/*****************************************************************************
 * $Id: eng_import.cc,v 1.21 2006/08/21 23:59:07 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Imports 3D models (from Blender) as Palomino Objects.
 * @remarks	See implementation.html for the data format that is imported.
 * @verbatim
 *
 * Overview:
 * ---------
 * (See implementation.html which documents the .pal file format)
 * ImportFactory::Import() reads a 3D model (exported from Blender) and
 * constructs an array of local vertexs/normals, a PolygonList, RGBAs, textures.
 * Results of importing are collected into an Importation object.
 * Since many DynaModel objects will import the same 3D model,
 * imports are cached in a STL map keyed by the 3D model's filename.
 *
 * Notes:
 * ------
 * Beware of the C++ pitfall of checking stream.eof().  If a read causes
 * failbit or badbit to be set, eofbit will never be set.
 * Call stream.good() to check for any error including EOF.
 *
 * Blender model notes:
 * --------------------
 * The normals of some faces may need to be flipped if Palomino appears to cull
 * some polygons backwards.  Blender can draw normals as small cyan rectangles.
 * Those that aren't sticking out (pointing inward) need to be flipped outward.
 *
 * @endverbatim
 *//*
 * LEGAL:	COPYRIGHT (C) JIM E. BROOKS 2005
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define ENG_IMPORT_CC 1
#include "eng.hh"
#include "eng_polygon.hh"
#include "eng_import.hh"

#if 0
#	define IMPORT_DEBUG_CODE( CODE ) { CODE }
#else
#	define IMPORT_DEBUG_CODE( CODE )
#endif

// This global var clears itself anew when ImportFactory::Import() is called.
// It's called by DynaModel constructors.
ImportFactory gImportFactory;

#define ERROR_TEXT_PREFIX	"ERROR[ImportFactory]: "

// The magic RGB (0.42,0.42,0.42) in a Blender model means translucency.
// 0.42 * 256.0 = 107
const uint MODEL_COLOR_TRANSLUCENT = uint(0.42 * 256);  // 107
const uint COLOR_TRANSLUCENT = 48;
const uint ALPHA_TRANSLUCENT = 48;

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////  ImportFactory  /////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Constructor.
 *****************************************************************************/
ImportFactory::ImportFactory( void )
:   mImportation(NULL),     // nullify pointers prior to Clear()
    mPolygonAttribs(NULL),
    mPolygonTexture(NULL)
{
	Clear();
}
ImportFactory::~ImportFactory()
{
	// (don't bother, there's only one single static instance)
}

/*****************************************************************************
 * Clear.
 *****************************************************************************/
// Clear all members.
void
ImportFactory::Clear( void )
{
  //mIstrm.close();
	mIstrm.clear();
	mErrorText.erase();
    delete mImportation; mImportation = new Importation;  // recreate new one
	ClearPolygon();
}
// Clear only polygon members.
void
ImportFactory::ClearPolygon( void )
{
	mPolygonAttribs		= NULL;
	mPolygonRgba.Set(0,0,0,0);
	mPolygonTexture		= NULL;
	mPolygonTexCoordCnt	= 0;
	mPolygonVixCnt		= 0;
}

/*****************************************************************************
 * Jump to the next line.
 *****************************************************************************/
bool
ImportFactory::ToNextLine( void )
{
	while ( mIstrm.good() && mIstrm.get() != '\n' ) { }
	return mIstrm.good();
}

/*****************************************************************************
 * Import vertex array.
 * @return mVertexCnt,mLocalVertexsShared.
 *****************************************************************************/
bool
ImportFactory::ImportVertexs( void )
{
	// Read the following line which has the count of vertexs.
	uint vertexCnt = 0;
	mIstrm >> vertexCnt;
	if ( ! mIstrm.good() )
	{
		mErrorText = ERROR_TEXT_PREFIX "reading vertexs \n";
		return false;
	}
    mImportation->SetVertexCnt( vertexCnt );  // commit after stream is known to be good

	ASSERT( mImportation->GetVertexCnt() > 0 );
	ASSERT( mImportation->GetVertexCnt() < MAX_VERTEXS );

	// Allocate the local vertex array using the count (local coord space).
	LocalVertex* localVertexs = new LocalVertex[ mImportation->GetVertexCnt() + 2 ];
	mImportation->SetLocalVertexs( localVertexs );

	// Fill the local vertex array.
	for ( uint i = 0; i < mImportation->GetVertexCnt(); ++i )
	{
		mIstrm >> localVertexs[i].x;
        mIstrm >> localVertexs[i].y;
        mIstrm >> localVertexs[i].z;
		if ( ! mIstrm.good() )
		{
			mErrorText = ERROR_TEXT_PREFIX "reading vertexs \n";
			return false;
		}
		IMPORT_DEBUG_CODE( *gStreamDebug << localVertexs[i] << endl; )
	}

	return true;
}

/*****************************************************************************
 * Import normals array.
 * @return Importation::mNormalCnt,mNormalsLocal.
 * @pre Vertex array was imported.
 *****************************************************************************/
bool
ImportFactory::ImportNormals( void )
{
	// Importing vertexs is a precondition for checking range of a vix.
	ASSERT( mImportation->GetVertexCnt() > 0 );

	// Read the following line which has the count of normals.
	uint normalCnt = 0;
	mIstrm >> normalCnt;
	if ( ! mIstrm.good() )
	{
		mErrorText = ERROR_TEXT_PREFIX "reading normals \n";
		return false;
	}
    mImportation->SetNormalCnt( normalCnt );

	ASSERT( mImportation->GetNormalCnt() > 0 );
	ASSERT( mImportation->GetNormalCnt() < MAX_POLYGONS );

	// Allocate normals array using the count.
	Normal* localNormals = new Normal[ mImportation->GetNormalCnt() + 2 ];
	mImportation->SetLocalNormals( localNormals );

	// Fill the normals array.
	for ( uint i = 0; i < mImportation->GetNormalCnt(); ++i )
	{
		mIstrm >> localNormals[i].mNorm.x;		// coords in normal space
		mIstrm >> localNormals[i].mNorm.y;
		mIstrm >> localNormals[i].mNorm.z;
		mIstrm >> localNormals[i].mVixNormOrig;	// index of vertex that is the origin of the normal vector
		if ( ! mIstrm.good() )
		{
			mErrorText = ERROR_TEXT_PREFIX "reading normals \n";
			return false;
		}
		ASSERT( localNormals[i].mVixNormOrig < MAX_VERTEXS );                   // assert after checking stream
		ASSERT( localNormals[i].mVixNormOrig < mImportation->GetVertexCnt() );	// ensure vix not outside vertex array
	}

	return true;
}

/*****************************************************************************
 * Called for the beginning of a polygon.
 *****************************************************************************/
bool
ImportFactory::ImportPolygonBegin( void )
{
	mPolygonAttribs = new PolygonAttribs();
	return true;
}

/*****************************************************************************
 * Called for the end of a polygon.
 *****************************************************************************/
bool
ImportFactory::ImportPolygonEnd( void )
{
	// Must have been assigned prior.
	ASSERT( mPolygonAttribs != NULL );
	ASSERT( mPolygonVixCnt > 0 );

	// If shared copy was made, delete original.
	if ( mPolygonAttribs->IfShared() )
		DELETE_NULL( mPolygonAttribs );

	// Clear data consumed for this polygon.
	ClearPolygon();

	return true;
}

/*****************************************************************************
 * Import polygon RGBA.
 *****************************************************************************/
bool
ImportFactory::ImportPolygonRgba( void )
{
	// Import the RGBA from the file.
	// The RGBA in the data format is four bytes.
	uint r, g, b, a;		// not uint8 which is in fact char
	mIstrm >> r;
	mIstrm >> g;
	mIstrm >> b;
	mIstrm >> a;
	if ( ! mIstrm.good() )
	{
		mErrorText = ERROR_TEXT_PREFIX "reading vertex index \n";
		return false;
	}

    // Translucent?
    if ( r == MODEL_COLOR_TRANSLUCENT
      && g == MODEL_COLOR_TRANSLUCENT
      && b == MODEL_COLOR_TRANSLUCENT )
    {
        r = g = b = COLOR_TRANSLUCENT; a = ALPHA_TRANSLUCENT;
        mPolygonAttribs->SetTranslucent( true );
    }

	ASSERT( r <= 256 && g <= 256 && b <= 256 && a <= 256 );

	// Convert RGBA ints to float, set PolygonAttribs.
	mPolygonAttribs->SetRgba( RGBA(r/255.0, g/255.0, b/255.0, a/255.0) );  // 255/255 = 1.0
	return true;
}

/*****************************************************************************
 * Import vertex index array (Blender face verts).
 *****************************************************************************/
bool
ImportFactory::ImportPolygonVixArray( void )
{
	// Importing vertexs is a precondition for checking range of a vix.
	ASSERT( mImportation->GetVertexCnt() > 0 );

	// Read the following line which has the size of the vix array.
	mIstrm >> mPolygonVixCnt;
	if ( ! mIstrm.good() )
	{
		mErrorText = ERROR_TEXT_PREFIX "reading vertex index \n";
		return false;
	}

	ASSERT( mPolygonVixCnt > 0 );
	ASSERT( mPolygonVixCnt < MAX_POLYGON_VERTEXS );
	ASSERT( mPolygonAttribs != NULL );	// should've been created by ImportPolygonBegin()

	// Create the Polygon object.
	Polygon* const polygon = mImportation->GetPolygonList()->AddPolygon( mPolygonAttribs, mPolygonVixCnt );

	// For each vertex index (vix) of this polygon.
	for ( uint i = 0; i < mPolygonVixCnt; ++i )
	{
		// Read vertex index (vix).
		uint vix;
		mIstrm >> vix;
		if ( ! mIstrm.good() )
		{
			mErrorText = ERROR_TEXT_PREFIX "reading vertex index \n";
			return false;
		}

		ASSERT( vix < mImportation->GetVertexCnt() );	// ensure vix isn't outside vertex array
		ASSERT( vix < MAX_VERTEXS );

		// Add vix to Polygon object.
		polygon->AddVix( vix );

		IMPORT_DEBUG_CODE( *gStreamDebug << "vix[" << i << "] = " << vix << endl; )
	}

	return true;
}

/*****************************************************************************
 * Import Blender Vertex Group.
 * Blender's Vertex Group mechanism is used to mark/annotate a set of vertexs
 * for special actions, eg, the wings of an F-14 Tomcat.
 *****************************************************************************/
bool
ImportFactory::ImportVertexGroup( void )
{
	// Importing vertexs is a precondition.
	ASSERT( mImportation->GetVertexCnt() > 0 );

	// Read line with name of the Blender Vertex Group.
	string vertexGroupName;
	mIstrm >> vertexGroupName;
	if ( ! mIstrm.good() )
	{
		mErrorText = ERROR_TEXT_PREFIX "reading Vertex Group name \n";
		return false;
	}

	// A Vertex Group consists of a set of vixs.
	// Read line with size of the vix array.
	uint vixCnt = 0;
	mIstrm >> vixCnt;
	if ( ! mIstrm.good() )
	{
		mErrorText = ERROR_TEXT_PREFIX "reading vix cnt (Vertex Group) \n";
		return false;
	}
	ASSERT( vixCnt > 0 );
	ASSERT( vixCnt < MAX_VERTEXS );
	ASSERT( vixCnt < mImportation->GetVertexCnt() );

	// Store the vixs in a STL vector.
    // AddVertexGroupToMap() returns a reference to a new STL vector
    // in a STL map associated by a name.
    vector<Vix>& vixVec = mImportation->NewVertexGroup( vertexGroupName, vixCnt );

	// For each vix.
	for ( uint i = 0; i < vixCnt; ++i )
	{
		// Read line with vix.
		uint vix = 0;
		mIstrm >> vix;
		if ( ! mIstrm.good() )
		{
			mErrorText = ERROR_TEXT_PREFIX "reading vertex index (Vertex Group) \n";
			return false;
		}
		ASSERT( vix < mImportation->GetVertexCnt() );	// ensure vix isn't outside vertex array
		ASSERT( vix < MAX_VERTEXS );
		IMPORT_DEBUG_CODE( *gStreamDebug << "VertexGroup vix[" << i << "] = " << vix << endl; )

		// Append this vix to STL container.
	  //vixVec.push_back( vix );  // wrong: NewVertexGroup() called vector.resize()
		vixVec[i] = vix;
	}

	return true;
}

/*****************************************************************************
 * Interface to client.
 * @return True if successful and model data as an Importation object.
 * @remarks
 * Top-level function that parses beginning of .pal tags and defers to subroutines.
 *****************************************************************************/
bool
ImportFactory::Import( Importation& importation /*OUT*/, const string& fnameModel )
{
	// Has this model been imported before?
	// If so, its importation was stored in a map keyed by model's filename.
	ItrImportation itrImportationSaved = mImportationSaved.find( fnameModel );
	if ( itrImportationSaved != mImportationSaved.end() )
	{
		// Return stored importation to caller.  SUBTLE: Importation class must be copyable.
		importation = itrImportationSaved->second;
		return true;
	}

	bool error = false;

	// Clear this object (except stored importations).
	// Polygon lists are shared with DynaModel objects.
	Clear();

	// Open file containing a 3D model as an input stream.
	mIstrm.open( fnameModel.c_str(), ios::in|ios::binary );
	if ( ! mIstrm.good() )
	{
		mErrorText = ERROR_TEXT_PREFIX "can't open file: ";
		mErrorText += fnameModel;
		mErrorText += '\n';
		return false;
	}

	// Iteratively parse the beginning of sections marked by .pal tags.
	// Dispatch to subroutines which are expected to siphon the stream
	// so that the next line read here should be the beginning of the next .pal tag.
	for (;;)
	{
		// Read next line from istream (usually an entire line, not always).
		string line;
		mIstrm >> line;
		if ( ! mIstrm.good() )
		{
			if ( mIstrm.eof() )
			{
				// EOF should happen.
				break;
			}
			else
			{
				mErrorText = ERROR_TEXT_PREFIX "can't read next line \n";
				error = true;
				break;
			}
		}

		// Sections begin with a .pal tag whose first char should be '<'.
		ASSERT( line.size() > 0 );
		if ( line[0] != '<' )
		{
			mErrorText  = ERROR_TEXT_PREFIX "Section should begin with a .pal tag. \n";
			mErrorText += ERROR_TEXT_PREFIX "Did Blender-to-Palomino export script malfunction? \n";
			mErrorText += line;
			error = true;
			break;
		}

		// Dispatch to the appropriate subroutine.
		if ( line == "<v/>" )
		{
			if ( ! ImportVertexs() )
			{
				error = true; break;
			}
		}
		else if ( line == "<n/>" )
		{
			if ( ! ImportNormals() )
			{
				error = true; break;
			}
		}
		else if ( line == "<p>" )	// <p> means polygon (not paragraph)
		{
			if ( ! ImportPolygonBegin() )
			{
				error = true; break;
			}
		}
		else if ( line == "</p>" )
		{
			if ( ! ImportPolygonEnd() )
			{
				error = true; break;
			}
		}
		else if ( line == "<pr/>" )
		{
			if ( ! ImportPolygonRgba() )
			{
				error = true; break;
			}
		}
		else if ( line == "<pv/>" )
		{
			if ( ! ImportPolygonVixArray() )
			{
				error = true; break;
			}
		}
		else if ( line == "<vg>" )  // Blender Vertex Group
		{
			if ( ! ImportVertexGroup() )
			{
				error = true; break;
			}
		}
		else if ( line == "</vg>" )  // Blender Vertex Group
		{
			// NOP
		}
		else if (    line.find( "<info" ) != string::npos   // <info> unused
                  || line.find( "<ex" )   != string::npos ) // <ex> is a tag for the export script itself
		{
			ToNextLine();
		}
		else
		{
			mErrorText  = ERROR_TEXT_PREFIX ".pal tag unrecognized: ";
			mErrorText += line;
			mErrorText += '\n';
			error = true; break;
		}
	}

	// Close 3D model file.
	mIstrm.close();

	// Checks.
	ASSERT( mImportation->GetPolygonList()->GetPolygonCnt() > 0 );
	ASSERT( mImportation->GetVertexCnt() > 0 );
	ASSERT( mImportation->GetVertexCnt() < MAX_VERTEXS );
	ASSERT( mImportation->GetNormalCnt() > 0 );
	ASSERT( mImportation->GetNormalCnt() < MAX_POLYGONS );
	ASSERT( mImportation->GetNormalCnt() == mImportation->GetPolygonList()->GetPolygonCnt() );
	ASSERT( mImportation->GetLocalVertexs() != NULL );
	ASSERT( mImportation->GetLocalNormals() != NULL );

	// Store importation for sharing 3D models.  Return output to caller.
	if ( ! error )
	{
		// Insert importation into map keyed by model's filename.
		mImportation->GetPolygonList()->Optimize( mImportation->GetVertexCnt() );
		mImportationSaved.insert( std::make_pair( fnameModel, *mImportation ) );

		// Return importation to caller.
		importation = *mImportation;
		return true;
	}
	else
	{
		// Luke: "I failed, Master Yoda.  You want the impossible!"
		// Yoda: "No good, at coding, you are.  Darth Gates, your master, he now is."
		return false;
	}
}
