/* 
 * Copyright (c) 2004, Laminar Research.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a 
 * copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the 
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
 * THE SOFTWARE.
 *
 */
#include "XUtils.h"
#include <stdio.h>
#include <ctype.h>
#include "XObjDefs.h"
#include <stdlib.h>
//#include "PlatformUtils.h"
//#include <time.h>
#include <map>
#include <math.h>
#include <string.h>

#if !defined(XUTILS_EXCLUDE_MAC_CRAP) && defined(__MACH__)
#define XUTILS_EXCLUDE_MAC_CRAP 1
#endif

#if APL
//using namespace Metrowerks;
#endif

static char * my_fgets(char * s, int n, FILE * file)
{
	char *	p = s;
	int			c;
	
	if (--n < 0)
		return(NULL);
	
	if (n)
		do
		{
			c = fgetc(file);
			
			if (c == EOF)
            {
				if (/*feof(file) &&*/ p != s)
					break;
				else
				{
					return(NULL);
				}
            }
			
			*p++ = c;
		}
		while (c != '\n' && c != '\r' && --n);
	
	*p = 0;
	
	return(s);
}



StTextFileScanner::StTextFileScanner(const char * file, bool skip) :
	mFile(fopen(file, "r")),
	mDone(false),
	mSkipBlanks(skip)
{
	read_next();
}	

StTextFileScanner::~StTextFileScanner()
{
	if (mFile)
		fclose(mFile);
}

bool	StTextFileScanner::done()
{
	return mDone;
}

void	StTextFileScanner::next()
{
	read_next();
}

string	StTextFileScanner::get()
{
	return mBuf;
}

void	StTextFileScanner::read_next(void)
{
	mBuf.clear();
	mDone = true;

	char	buf[4096];

	while (mFile && /*!feof(mFile) &&*/ my_fgets(buf, sizeof(buf), mFile))
	{
		int len = static_cast<int>(strlen(buf));
		while ((len > 0) && (buf[len-1] == '\r' || buf[len-1] == '\n'))
		{
			buf[len-1] = 0;
			--len;
		}
		
		if (buf[0] == '\r' || buf[0] == '\n')
			mBuf = buf+1;
		else
			mBuf = buf;
			
		if (!mBuf.empty() || !mSkipBlanks)
		{
			mDone = false;
			return;
		}
	}
}

void	BreakString(const string& line, vector<string>& words)
{
	words.clear();
	string::const_iterator i = line.begin();
	while (i < line.end())
	{
		string::const_iterator s = i;
		while (s < line.end() && isspace(*s))
			++s;
		
		string::const_iterator e = s;
		while (e < line.end() && !isspace(*e))
			++e;
			
		if (s < e)
			words.push_back(string(s,e));
		
		i = e;
	}
}

void	StringToUpper(string& s)
{	
	for (string::iterator i = s.begin(); i != s.end(); ++i)
		*i = toupper(*i);
}

bool	HasExtNoCase(const string& inStr, const char * inExt)
{
	string s(inStr);
	string e(inExt);
	StringToUpper(s);
	StringToUpper(e);
	
	
	if (s.rfind(e) == (s.length() - e.length()))
		return true;
	return false;
}

void	ChangePolyCmdCW(XObjCmd& ioCmd)
{
	vector<vec_tex>	v;
	for (vector<vec_tex>::reverse_iterator riter = ioCmd.st.rbegin();
		riter != ioCmd.st.rend(); ++riter)
	{
		v.push_back(*riter);
	}
	ioCmd.st = v;
}

bool	GetNextNoComments(StTextFileScanner& f, string& s)
{
	while(!f.done())
	{
		s = f.get();
		f.next();
		if (s.empty() || s[0] != '#')
			return true;		
	}
	return false;
}

double	GetObjRadius(const XObj& inObj)
{
	double	dist = 0, d;
	for (vector<XObjCmd>::const_iterator c = inObj.cmds.begin();
		c != inObj.cmds.end(); ++c)	
	{
		for (vector<vec_tex>::const_iterator v = c->st.begin();
			v != c->st.end(); ++v)
		{
			d = sqrt(v->v[0] * v->v[0] +
					 v->v[1] * v->v[1] +
					 v->v[2] * v->v[2]);
			if (d > dist) dist = d;
		}
		
		for (vector<vec_rgb>::const_iterator p = c->rgb.begin();
			p != c->rgb.end(); ++p)
		{
			d = sqrt(p->v[0] * p->v[0] +
					 p->v[1] * p->v[1] +
					 p->v[2] * p->v[2]);
			if (d > dist) dist = d;
		}
	}
	return dist;
}

int		PickRandom(vector<double>& chances)
{
	double	v = (double) (rand() % RAND_MAX) / (double) RAND_MAX;
	
	for (size_t n = 0; n < chances.size(); ++n)
	{
		if (v < chances[n])
			return static_cast<int>(n);
		v -= chances[n];
	}
	return static_cast<int>(chances.size());
}

bool	RollDice(double inProb)
{
	if (inProb <= 0.0) return false;
	if (inProb >= 1.0) return true;
	double	v = (double) (rand() % RAND_MAX) / (double) RAND_MAX;
	return v < inProb;
}

double	RandRange(double mmin, double mmax)
{
	if (mmin >= mmax)
		return mmin;
	double	v = (double) rand() / (double) RAND_MAX;
	return mmin + ((mmax - mmin) * v);
}		

double	RandRangeBias(double mmin, double mmax, double biasRatio, double randomAmount)
{
	double	span = mmax - mmin;
	double lower_rat = biasRatio * (1.0 - randomAmount);
	double upper_rat = lower_rat + randomAmount;
	return RandRange(mmin + span * lower_rat,mmin + span * upper_rat);
}

#if 0
void		StripPath(string& ioPath)
{
	string::size_type sep = ioPath.rfind(DIR_CHAR);
	if (sep != ioPath.npos)
		ioPath = ioPath.substr(sep+1,ioPath.npos);
}

void		StripPathCP(string& ioPath)
{
	string::size_type sep;
	sep = ioPath.rfind(':');
	if (sep != ioPath.npos)
		ioPath = ioPath.substr(sep+1,ioPath.npos);
	sep = ioPath.rfind('\\');
	if (sep != ioPath.npos)
		ioPath = ioPath.substr(sep+1,ioPath.npos);
}

void		ExtractPath(string& ioPath)
{
	string::size_type sep = ioPath.rfind(DIR_CHAR);
	if (sep != ioPath.npos)
		ioPath = ioPath.substr(0, sep);
}
#endif
#if APL

#if !defined(XUTILS_EXCLUDE_MAC_CRAP)

#include <Processes.h>

OSErr	FindSuperFolder(const FSSpec& inItem, FSSpec& outFolder)
{
		CInfoPBRec	paramBlock;
		OSErr		err;
		
	paramBlock.dirInfo.ioCompletion = 	NULL;
	paramBlock.dirInfo.ioNamePtr =		(StringPtr) (&(outFolder.name));
	paramBlock.dirInfo.ioVRefNum = 		inItem.vRefNum;
	paramBlock.dirInfo.ioFDirIndex = 	-1;
	paramBlock.dirInfo.ioDrDirID = 		inItem.parID;
	
	err = ::PBGetCatInfoSync(&paramBlock);
	if (err != noErr)
		return err;
		
	outFolder.vRefNum = paramBlock.dirInfo.ioVRefNum;
	outFolder.parID= paramBlock.dirInfo.ioDrParID;
	return noErr;
}

void	AppPath(string& outString)
{
	ProcessSerialNumber	psn = { 0, kCurrentProcess };
	ProcessInfoRec info = { 0 };
	FSSpec	spec;
	Str255	name;
	info.processInfoLength = sizeof(info);
	info.processAppSpec = &spec;
	info.processName = name;
	GetProcessInformation(&psn, &info);
	FSSpec_2_String(spec, outString);
}

void	FSSpec_2_String(const FSSpec& inSpec, string& outString)
{
	outString.clear();
	
	FSSpec	foo(inSpec);
	FSSpec	foo2;
	
	while (1)
	{
		if (outString.empty())
			outString = std::string(foo.name+1, foo.name+foo.name[0]+1);
		else
			outString = std::string(foo.name+1, foo.name+foo.name[0]+1) + std::string(":") + outString;
		if (FindSuperFolder(foo, foo2) != noErr)
			break;
		foo = foo2;
	}
}

#endif
#endif

void	ExtractFixedRecordString(	
				const string&		inLine,
				int					inBegin,
				int					inEnd,
				string&				outString)
{
	int	sp = inBegin-1;
	int ep = inEnd;
	if (ep > static_cast<int>(inLine.length())) ep = static_cast<int>(inLine.length());
	if (sp > static_cast<int>(inLine.length())) sp = static_cast<int>(inLine.length());
	
	while ((sp < ep) && (inLine[sp] == ' '))
		++sp;
	
	while ((ep > sp) && (inLine[ep-1] == ' '))
		--ep;
		
	outString = inLine.substr(sp, ep - sp);
}				
				
bool	ExtractFixedRecordLong(
				const string&		inLine,
				int					inBegin,
				int					inEnd,
				long&				outLong)
{
	string	foo;
	ExtractFixedRecordString(inLine, inBegin, inEnd, foo);
	if (foo.empty())	return false;
	outLong = strtol(foo.c_str(), NULL, 10);
	return true;
}				
				
bool	ExtractFixedRecordUnsignedLong(
				const string&		inLine,
				int					inBegin,
				int					inEnd,
				unsigned long&		outUnsignedLong)
{
	string	foo;
	ExtractFixedRecordString(inLine, inBegin, inEnd, foo);
	if (foo.empty())	return false;
	outUnsignedLong = strtoul(foo.c_str(), NULL, 10);
	return true;
}				

//#pragma mark -

struct	XPointPool::XPointPoolImp {

	struct	p_info {
		float xyz[3];
		float st[2];
	};
	vector<p_info>			pts;
	map<string, int>		index;

	void	clear() 
	{
		pts.clear();
		index.clear();
	}
	
	int		count(void)
	{
		return static_cast<int>(pts.size());
	}
	
	int		accumulate(const float xyz[3], const float st[2])
	{
		static	char	buf[256];
		sprintf(buf,"%08X%08X%08X|%08x%08x",
			*(reinterpret_cast<const int*>(xyz+0)),
			*(reinterpret_cast<const int*>(xyz+1)),
			*(reinterpret_cast<const int*>(xyz+2)),
			*(reinterpret_cast<const int*>(st +0)),
			*(reinterpret_cast<const int*>(st +1)));
		string	key(buf);
		map<string, int>::iterator i = index.find(key);
		if (i != index.end()) return i->second;			
		p_info	p;
		memcpy(p.xyz, xyz, sizeof(p.xyz));
		memcpy(p.st, st, sizeof(p.st));
		pts.push_back(p);
		index.insert(map<string,int>::value_type(key, static_cast<int>(pts.size())));
		pts.push_back(p);
		return static_cast<int>(pts.size())-1;
	}
	
	void	get(int i, float xyz[3], float st[2])
	{
		p_info& p = pts[i];
		memcpy(xyz,p.xyz,sizeof(p.xyz));
		memcpy(st,p.st,sizeof(p.st));
	}
};

XPointPool::XPointPool()
{
	mImp = new XPointPoolImp;
}

XPointPool::~XPointPool()
{
	delete mImp;
}
		
void	XPointPool::clear()
{
	mImp->clear();
}

int		XPointPool::accumulate(const float xyz[3], const float st[2])
{
	return mImp->accumulate(xyz, st);
}

void	XPointPool::get(int index, float xyz[3], float st[2])
{
	mImp->get(index,xyz,st);
}

int		XPointPool::count(void)
{
	return mImp->count();
}

void	DecomposeObjCmd(const XObjCmd& inCmd, vector<XObjCmd>& outCmds, int maxValence)
{
	XObjCmd	c;
	c.cmdType = type_Poly;
	c.cmdID = obj_Tri;
	switch(inCmd.cmdID) {
	case obj_Tri:
		// Triangles never need breaking down.
		outCmds.push_back(inCmd);
		break;
	case obj_Quad:
	case obj_Quad_Hard:
	case obj_Smoke_Black:
	case obj_Smoke_White:
	case obj_Movie:
		// Quads - split into triangles if necessary.
		if (maxValence > 3) {
			outCmds.push_back(inCmd);
			outCmds.back().cmdID = obj_Quad;
		} else {
			outCmds.push_back(inCmd);
			outCmds.back().cmdID = obj_Tri;
			outCmds.back().st.erase(outCmds.back().st.begin()+3);
			outCmds.push_back(inCmd);			
			outCmds.back().cmdID = obj_Tri;
			outCmds.back().st.erase(outCmds.back().st.begin()+1);
		}
		break;
	case obj_Polygon:
		// Polygons might be ok.  But if we have to break them down,
		// we generate N-2 triangles in a fan configuration.
		if (maxValence < static_cast<int>(inCmd.st.size()))
		{
			c.st.push_back(inCmd.st[0]);
			c.st.push_back(inCmd.st[1]);
			c.st.push_back(inCmd.st[2]);
			for (size_t n = 2; n < inCmd.st.size(); ++n)
			{
				c.st[1] = inCmd.st[n-1];
				c.st[2] = inCmd.st[n  ];
				outCmds.push_back(c);
			}
		} else 
			outCmds.push_back(inCmd);
		break;
	case obj_Tri_Strip:
		// Triangle strips - every other triangle's vertices
		// are backward!
		c.st.push_back(inCmd.st[0]);
		c.st.push_back(inCmd.st[1]);
		c.st.push_back(inCmd.st[2]);
		for (size_t n = 2; n < inCmd.st.size(); ++n)
		{
			if (n%2)
			{
				c.st[0] = inCmd.st[n-2];
				c.st[1] = inCmd.st[n  ];
				c.st[2] = inCmd.st[n-1];
				outCmds.push_back(c);
			} else {
				c.st[0] = inCmd.st[n-2];
				c.st[1] = inCmd.st[n-1];
				c.st[2] = inCmd.st[n  ];
				outCmds.push_back(c);
			}
		}
		break;
	case obj_Tri_Fan:
		// Tri fan - run around the triangle fan emitting triangles.
		c.st.push_back(inCmd.st[0]);
		c.st.push_back(inCmd.st[1]);
		c.st.push_back(inCmd.st[2]);
		for (size_t n = 2; n < inCmd.st.size(); ++n)
		{
			c.st[1] = inCmd.st[n-1];
			c.st[2] = inCmd.st[n  ];
			outCmds.push_back(c);
		}
		break;
	case obj_Quad_Strip:
		// Quad strips can become either quads or triangles!!
		if (maxValence > 3)
		{
			c.cmdID = obj_Quad;
			c.st.push_back(inCmd.st[0]);
			c.st.push_back(inCmd.st[1]);
			c.st.push_back(inCmd.st[2]);
			c.st.push_back(inCmd.st[3]);
			for (size_t n = 2; n < inCmd.st.size(); n += 2)
			{
				c.st[0] = inCmd.st[n-2];
				c.st[1] = inCmd.st[n-1];
				c.st[2] = inCmd.st[n+1];
				c.st[3] = inCmd.st[n  ];
				outCmds.push_back(c);
			}
		} else {
			c.st.push_back(inCmd.st[0]);
			c.st.push_back(inCmd.st[1]);
			c.st.push_back(inCmd.st[2]);
			for (size_t n = 2; n < inCmd.st.size(); ++n)
			{
				if (n%2)
				{
					c.st[0] = inCmd.st[n-2];
					c.st[1] = inCmd.st[n  ];
					c.st[2] = inCmd.st[n-1];
					outCmds.push_back(c);
				} else {
					c.st[0] = inCmd.st[n-2];
					c.st[1] = inCmd.st[n-1];
					c.st[2] = inCmd.st[n  ];
					outCmds.push_back(c);
				}
			}
		}
		break;
	default:
		outCmds.push_back(inCmd);
	}
}

void	DecomposeObj(const XObj& inObj, XObj& outObj, int maxValence)
{
	outObj.cmds.clear();
	outObj.texture = inObj.texture;
	for (vector<XObjCmd>::const_iterator cmd = inObj.cmds.begin(); 
		cmd != inObj.cmds.end(); ++cmd)
	{
		vector<XObjCmd>		newCmds;
		DecomposeObjCmd(*cmd, newCmds, maxValence);
		outObj.cmds.insert(outObj.cmds.end(), newCmds.begin(), newCmds.end());
	}
}
