/*
	Twilight Prophecy 3D/Multimedia SDK
	A multi-platform development system for virtual reality and multimedia.

	Copyright (C) 1997-2001 by Twilight 3D Finland Oy Ltd.

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

	Please read the file LICENSE.TXT for additional details.


	source:
		filestream implementation

	revision history:
		Jan/24/2001 - Jukka Liimatta - initial revision/renaissance build
		Jan/30/2001 - Timo Saarinen - read cache
		Feb/01/2001 - Jukka Liimatta - filefactory
*/
#include <prcore/prcore.hpp>
using namespace prcore;



//////////////////////////////////////////////////////
// server                                          //
////////////////////////////////////////////////////

static LinkedList<FileFactory> FileFactoryList;
static String password;
static String basepath;

FileFactory CreateFileFactoryFILE();
FileFactory CreateFileFactoryZIP();


//////////////////////////////////////////////////////
// FileStream                                      //
////////////////////////////////////////////////////

FileStream::FileStream(const char* filename_user, AccessMode mode)
: stream(NULL), offset(0), page(-1)
{
	// initialize factory
	static bool init = false;
	if ( !init )
	{
		FileFactoryList.PushBack( CreateFileFactoryZIP() );
		init = true;
	}

	// create filename at basepath
	String filename( basepath );
	filename += filename_user;

	// iterate looking for a filefactory
	for ( int i=0; i<FileFactoryList.GetSize(); i++ )
	{
		FileFactory node = FileFactoryList[ i ];

		for ( int j=0; j<node.NumEXT(); j++ )
		{
			// next supported extension
			const char* ext = node.GetEXT( j );

			// find extension as substring of filename
			char* p = strstr(filename,ext);
			if ( p )
			{
				// found externsion, skip it to get filename
				p += strlen(ext);

				// filename must be prefixed with slash
				if ( *p=='/' || *p=='\\' )
				{
					// construct archive filename
					char archive[600];//TODO error prone
					strcpy(archive,filename);
					archive[ p-filename ] = 0;

					// internal filename
					char* name = p + 1;

					// create stream
					stream = node.CreateFile(archive,name,mode,password);

					// error?
					if ( !stream )
					{
						String error("cannot access file: ");
						error += filename;
						PRCORE_EXCEPTION("FileStream::FileStream()",error);
					}

					// mission impossible complete
					return;
				}
			}
		}
	}

	// create default filestream
	FileFactory node = CreateFileFactoryFILE();
	stream = node.CreateFile("",filename,mode,NULL);

	// error?
	if ( !stream )
	{
		String error("cannot access file: ");
		error += filename;
		PRCORE_EXCEPTION("FileStream::FileStream()",error);
	}
}


FileStream::~FileStream()
{
	if ( stream )
		delete stream;
}


int FileStream::Seek(int delta, SeekMode mode)
{
	if ( stream )
	{
		switch ( mode )
		{
			case START:		offset = delta; break;
			case CURRENT:	offset += delta; break;
			case END:		offset = stream->GetSize() - delta; break;
		}
	}
	return offset;
}


int FileStream::Read(void* target, int bytes)
{
	int offset0 = offset;
	if ( stream )
	{
		if ( bytes > CACHE_SIZE )
		{
			stream->Seek(offset,START);
			stream->Read(target,bytes);
			offset += bytes;
			page = -1; // invalidate cache after blockread
		}
		else
		{
			char* cbuffer = (char*)target;
			int cpos = offset;
			int cpage = cpos >> CACHE_BITS;

			// read page, if not cached
			if ( cpage != page )
			{
				stream->Seek(cpage<<CACHE_BITS,START);
				stream->Read(cache,CACHE_SIZE);
				page = cpage;
			}

			// cache boundry cross
			int bytes_left = CACHE_SIZE - (cpos & (CACHE_SIZE-1));
			while ( bytes > bytes_left )
			{
				memcpy(cbuffer,cache+(cpos & (CACHE_SIZE-1)),bytes_left);
				cpos += bytes_left;
				cbuffer += bytes_left;
				bytes -= bytes_left;

				stream->Seek(cpos,START);
				stream->Read(cache,CACHE_SIZE);
				page = cpos >> CACHE_BITS;
				bytes_left = CACHE_SIZE;
			}

			// leftovers
			memcpy(cbuffer,cache+(cpos & (CACHE_SIZE-1)),bytes);
			offset = cpos + bytes;
		}
	}
	return offset - offset0;
}


int FileStream::Write(const void* source, int bytes)
{
	int delta = stream ? stream->Write(source,bytes) : 0;
	offset += delta;
	return delta;
}


int FileStream::GetOffset() const
{
	return offset;
}


int FileStream::GetSize() const
{
	return stream ? 
		stream->GetSize() : 0;
}


bool FileStream::IsOpen() const
{
	return stream ?
		stream->IsOpen() : false;
}


bool FileStream::IsEOS() const
{
	int size = stream ? stream->GetSize() : 0;
	return offset >= size;
}


void FileStream::SetBasePath(const char* path)
{
	basepath = path;
}


void FileStream::SetPassword(const char* pass)
{
	password = pass;
}
