#include "StdAfx.h"
#include "MTSafeAllocator.h"
#include <smartptr.h>
#include "ZipFileFormat.h"
#include "ZipDirStructures.h"
#include "ZipDirTree.h"
#include "ZipDirCache.h"
#include "ZipDirFind.h"
#include "ZipDirCacheFactory.h"
#include "zlib/zlib.h"
#include "CritSection.h"

using namespace ZipFile;

namespace
{
	CCritSection g_cZipCacheLock;
}

// initializes the instance structure
void ZipDir::Cache::Construct(FILE* fNew, CMTSafeHeap* pHeap, size_t nDataSizeIn)
{
	m_nRefCount = 0;
	m_pFile = fNew;
	m_pHeap = pHeap;
	m_nDataSize = nDataSizeIn;
	m_nZipPathOffset = nDataSizeIn;
}

// self-destruct when ref count drops to 0
void ZipDir::Cache::Delete()
{
	if (m_pFile)
		fclose (m_pFile);
	m_pHeap->Free(this);
}


// initializes this object from the given Zip file: caches the central directory
// returns 0 if successfully parsed, error code if an error has occured
ZipDir::CachePtr ZipDir::NewCache (const char* szFileName, CMTSafeHeap* pHeap, InitMethodEnum nInitMethod)
{
	// try .. catch(Error)
	CacheFactory factory(pHeap, nInitMethod);
	return factory.New(szFileName);
}



// looks for the given file record in the Central Directory. If there's none, returns NULL.
// if there is some, returns the pointer to it.
// the Path must be the relative path to the file inside the Zip
// if the file handle is passed, it will be used to find the file data offset, if one hasn't been initialized yet
ZipDir::FileEntry* ZipDir::Cache::FindFile (const char* szPath, bool bRefresh)
{
	if (!this)
		return NULL;
	ZipDir::FindFile fd (this);
	if (!fd.FindExact(szPath))
	{
		assert (!fd.GetFileEntry());
		return NULL;
	}
	assert (fd.GetFileEntry());
	return fd.GetFileEntry();

}

// loads the given file into the pCompressed buffer (the actual compressed data)
// if the pUncompressed buffer is supplied, uncompresses the data there
// buffers must have enough memory allocated, according to the info in the FileEntry
// NOTE: there's no need to decompress if the method is 0 (store)
// returns 0 if successful or error code if couldn't do something
ZipDir::ErrorEnum ZipDir::Cache::ReadFile (FileEntry* pFileEntry, void* pCompressed, void* pUncompressed)
{
	if (!pFileEntry)
		return ZD_ERROR_INVALID_CALL;

	if (pFileEntry->desc.lSizeUncompressed == 0)
	{
		assert (pFileEntry->desc.lSizeCompressed == 0);
		return ZD_ERROR_SUCCESS;
	}

	assert (pFileEntry->desc.lSizeCompressed > 0);

	ErrorEnum nError = Refresh(pFileEntry);
	if (nError != ZD_ERROR_SUCCESS)
		return nError;

	AUTO_LOCK(g_cZipCacheLock); // Not thread safe without this

#ifdef WIN32
	if (_fseeki64 (m_pFile, (__int64)pFileEntry->nFileDataOffset, SEEK_SET))
#else
  if (fseek (m_pFile, pFileEntry->nFileDataOffset, SEEK_SET))
#endif
		return ZD_ERROR_IO_FAILED;

	SmartPtr pBufferDestroyer(m_pHeap);

	void* pBuffer = pCompressed; // the buffer where the compressed data will go

	if (pFileEntry->nMethod == 0 && pUncompressed)
	{
		// we can directly read into the uncompress buffer
		pBuffer = pUncompressed;
	}

	if (!pBuffer)
	{
		if (!pUncompressed)
		// what's the sense of it - no buffers at all?
			return ZD_ERROR_INVALID_CALL;

		pBuffer = m_pHeap->Alloc(pFileEntry->desc.lSizeCompressed, "ZipDir::Cache::ReadFile");
		pBufferDestroyer.Attach(pBuffer); // we want it auto-freed once we return
	}

	if (fread (pBuffer, pFileEntry->desc.lSizeCompressed, 1, m_pFile) != 1)
		return ZD_ERROR_IO_FAILED;

	// if there's a buffer for uncompressed data, uncompress it to that buffer
	if (pUncompressed)
	{
		if (pFileEntry->nMethod == 0)
		{
			assert (pBuffer == pUncompressed);
			//assert (pFileEntry->desc.lSizeCompressed == pFileEntry->nSizeUncompressed);
			//memcpy (pUncompressed, pBuffer, pFileEntry->desc.lSizeCompressed);
		}
		else
		{
			unsigned long nSizeUncompressed = pFileEntry->desc.lSizeUncompressed;
			if (Z_OK != ZipRawUncompress(m_pHeap, pUncompressed, &nSizeUncompressed, pBuffer, pFileEntry->desc.lSizeCompressed))
				return ZD_ERROR_CORRUPTED_DATA;
		}
	}

	return ZD_ERROR_SUCCESS;
}

// loads and unpacks the file into a newly created buffer (that must be subsequently freed with
// Free()) Returns NULL if failed
void* ZipDir::Cache::AllocAndReadFile (FileEntry* pFileEntry)
{
	if (!pFileEntry)
		return NULL;

	void* pData = m_pHeap->Alloc(pFileEntry->desc.lSizeUncompressed, "ZipDir::Cache::AllocAndReadFile");
	if (pData)
	{
		if (ZD_ERROR_SUCCESS != ReadFile (pFileEntry, NULL, pData))
		{
			m_pHeap->Free (pData);
			pData = NULL;
		}
	}
	return pData;
}

// frees the memory block that was previously allocated by AllocAndReadFile
void ZipDir::Cache::Free (void* pData)
{
	m_pHeap->Free(pData);
}

// refreshes information about the given file entry into this file entry
ZipDir::ErrorEnum ZipDir::Cache::Refresh (FileEntry* pFileEntry)
{
	AUTO_LOCK(g_cZipCacheLock); // Not thread safe without this

	if (!pFileEntry)
		return ZD_ERROR_INVALID_CALL;

	if (pFileEntry->nFileDataOffset != pFileEntry->INVALID_DATA_OFFSET)
		return ZD_ERROR_SUCCESS; // the data offset has been successfully read..

	if (!this)
		return ZD_ERROR_INVALID_CALL; // from which cache is this file entry???

	return ZipDir::Refresh(m_pFile, pFileEntry);
}

//////////////////////////////////////////////////////////////////////////
uint32 ZipDir::Cache::GetFileDataOffset(FileEntry* pFileEntry)
{
	AUTO_LOCK(g_cZipCacheLock); // Not thread safe without this

	if (pFileEntry->nFileDataOffset == pFileEntry->INVALID_DATA_OFFSET)
		ZipDir::Refresh (m_pFile,pFileEntry);
	return pFileEntry->nFileDataOffset;
}

// returns the size of memory occupied by the instance referred to by this cache
// must be exact, because it's used by CacheRW to reallocate this cache
size_t ZipDir::Cache::GetSize()const
{
	if (this)
		return m_nDataSize + sizeof(Cache) + strlen(GetFilePath());
	else
		return 0;
}


// QUICK check to determine whether the file entry belongs to this object
bool ZipDir::Cache::IsOwnerOf (const FileEntry*pFileEntry)const
{
	if (this)
	{
		// just check whether the pointer is within the memory block of this cache instance
		return ((ULONG_PTR)pFileEntry >= (ULONG_PTR)(GetRoot()+1)
			&&(ULONG_PTR)pFileEntry <= ((ULONG_PTR)GetRoot()) + m_nDataSize - sizeof(FileEntry));
	}
	else
		return false;
}
