/*  DS
 *  Copyright (C) Joakim Kolsjö and Anders Asplund 2005
 *	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 
 */ 
 
#include "read_xpk.h"

uLongf endian_swap(uLongf a)
{
	return ((a&0xff)<<24) +  ((a&0xff00)<<8) + ((a&0xff0000)>>8) + ((a&0xff000000)>>24);
}

using namespace std;

namespace DS
{
	ReadXPK::ReadXPK(const char* pFile)
	{
		m_archive = pFile;
		m_loc = 0;
	}
	
	void ReadXPK::Init()
	{
		static char* fn = "[ReadXPK::Init]";
		
		// Open archive
		FILE* pXf = fopen(m_archive.c_str(), "rb");
		
		if(!pXf)
			throw Exception(fn, "Can't open archive " + m_archive + ".");	
	
		// Check if we're going to treat this as a onefiler
		if(m_archive.substr(m_archive.size()-4, 4) != ".xpk")
		{
			fseek(pXf, 0, SEEK_END);
			long size = ftell(pXf);
			fseek(pXf, 0, SEEK_SET);
			char* pTemp = new char[size];
			fread(pTemp, size, 1, pXf);
			
			long i;
			for(i = 0; i < size; i++)
			{
				if(pTemp[i] == 'x' && pTemp[i+1] == 'P' && pTemp[i+2] == 'k')
					break;
			}
			
			if(i == size)
				throw Exception(fn, (string) "Can't find xpk data in \"" + m_archive + "\".");
			
			m_loc = i + 3;
			fseek(pXf, m_loc, SEEK_SET);
			delete pTemp;
		}
		
		// Read total amount of compressed data
		fread(&m_ctotal, sizeof(uLongf), 1, pXf);
		
#ifdef macosx
	#if BYTE_ORDER == BIG_ENDIAN
		m_ctotal = endian_swap(m_ctotal);
	#endif
#endif
		
		// Skip the compressed data
		fseek(pXf, m_loc + sizeof(uLongf) + m_ctotal, SEEK_SET);
		
		// Read number of files
		uLongf nfiles;
		fread(&nfiles, sizeof(uLongf), 1, pXf);

#ifdef macosx
	#if BYTE_ORDER == BIG_ENDIAN
		nfiles = endian_swap(nfiles);
	#endif
#endif		

		// Read filenames, real and compressed sizes
		string name = "";
		for(uLongf i = 0; i < nfiles; i++)
		{
			// Read filename
			char buf[1];
			name = "";
			for(;;)
			{
				fread(buf, 1, 1, pXf);
				if(buf[0] == '$')
					break;
				else
					name = name + buf[0];
			}
			
			m_FileNames.push_back(name);
			
			// Read real and compressed size
			uLongf rsize, csize;
			fread(&rsize, sizeof(uLongf), 1, pXf);
			fread(&csize, sizeof(uLongf), 1, pXf);
			
#ifdef macosx
	#if BYTE_ORDER == BIG_ENDIAN
			rsize = endian_swap(rsize);
			csize = endian_swap(csize);
	#endif
#endif					
			m_rsizes.push_back(rsize);
			m_csizes.push_back(csize);
		}
			
		fclose(pXf);
	}
	
	void ReadXPK::Read(const char* pFile)
	{
		static char* fn = "[ReadXPK::Read]";
		if(m_MemFiles[pFile])
			throw Exception(fn, (string)"File already loaded (" + pFile + ").");
		
		// Find file
		string file = pFile;
		int number = 0;
		for(number = 0; number < (int)m_FileNames.size()-1; number++) {			
			if(file == m_FileNames[number])
				break;
		}

		if(number == (int)m_FileNames.size()-1 && file != m_FileNames[number])
			throw Exception(fn, (string)"Could not find " + pFile + ".");

		// Open archive
		FILE* pXf = fopen(m_archive.c_str(), "rb");
		
		if(!pXf)
			throw Exception(fn, "Can't open archive " + m_archive + ".");
				
		// Add sizes of all previous files
		uLongf filepos = 0;
		for(int i = 0; i < number; i++)
			filepos += m_csizes[i];
		
		// Seek to the file we want
		fseek(pXf, m_loc + sizeof(uLongf) + filepos, SEEK_SET);

		// Read file
		Bytef* pC = new Bytef[m_csizes[number]];
		fread(pC, m_csizes[number], 1, pXf);
		fclose(pXf);	
	
		// Uncompress data
		char* pData = new char[m_rsizes[number]];
		uncompress((Bytef*)pData, &m_rsizes[number], pC, (uLong)&m_csizes[number]);
		MemFile* pMemFile = new MemFile(pData, m_rsizes[number]);
			
		// Cleanup and save data
		delete pC;
		m_MemFiles[pFile] = pMemFile;
	}
}
