#include "wxinc.h"

#ifdef LINUX
#include "xvt_type.h"
#include "incstr.h"
#else
#include <fstream.h>
#endif

#include "agasys.h"

///////////////////////////////////////////////////////////
// Unzip support
///////////////////////////////////////////////////////////

#include <wx/dir.h>
#include <wx/wfstream.h>
#include <wx/zipstrm.h>

#pragma pack(2)

struct ZipLocalFileHeader
{
	unsigned short nVersionNeeded;
	unsigned short nFlags;
	unsigned short nMethod;
	unsigned short nLastModTime;
	unsigned short nLastModDate;
	unsigned long dwCRC;
	unsigned long dwCompressedSize;
	unsigned long dwUncompressedSize;
	unsigned short nNameLength;
	unsigned short nExtraLength;
};

struct ZipDirectoryFileHeader
{
	unsigned short nVersionUsed;
	ZipLocalFileHeader zlfh;
	unsigned short nCommentLength;
	unsigned short nStartDisk;
	unsigned short nInternalAttr;
	unsigned long nExternalAttr;
	unsigned long nLocalHeaderOffset;
};

struct ZipDirectoryEnd
{
	unsigned short nThisDisk;
	unsigned short nStartDisk;
	unsigned short nDiskEntries;
	unsigned short nTotalEntries;
	unsigned long nSize;
	unsigned long nStartOffset;
	unsigned short nCommentLength;
};

#pragma pack()

unsigned int aga_ziplist(const char* zipfile, wxArrayString& aFiles)
{
	const unsigned long dwDirectorySignature    = 0x02014B50;
	const unsigned long dwEndDirectorySignature = 0x06054B50;
 	
	wxFileInputStream fin(zipfile);
	
	const off_t off = sizeof(ZipDirectoryEnd)+sizeof(dwEndDirectorySignature);
	fin.SeekI(-off, wxFromEnd);

	unsigned long dwSignature = 0;
	fin.Read(&dwSignature, sizeof(dwSignature));
	if (dwSignature != dwEndDirectorySignature)
		return 0;

	ZipDirectoryEnd zde;
	fin.Read(&zde, sizeof(zde));
	if (zde.nThisDisk < zde.nStartDisk || zde.nDiskEntries == 0 || zde.nSize == 0)
		return 0;

	fin.SeekI(zde.nStartOffset, wxFromStart);

  for (unsigned int f = 0; f < zde.nDiskEntries; f++)
	{
    fin.Read(&dwSignature, sizeof(dwSignature));
	  if (dwSignature != dwDirectorySignature)
		  break;
		
		ZipDirectoryFileHeader zdfh; memset(&zdfh, 0, sizeof(zdfh));
		fin.Read(&zdfh, sizeof(zdfh));
		char name[_MAX_PATH]; memset(name, 0, sizeof(name));
		fin.Read(name, zdfh.zlfh.nNameLength);
		aFiles.Add(name);
	}

	return aFiles.GetCount();
}

int aga_find_slash(const wxString& path, int from)
{
  for (int i = from; path[i]; i++)
    if (wxIsPathSeparator(path[i]))
      return i;

    return -1;
}

void aga_create_dir(wxString strPath)
{
  if (!wxEndsWithPathSeparator(strPath))
		strPath += wxFILE_SEP_PATH;

  for (int i = aga_find_slash(strPath, 0); i > 0; i = aga_find_slash(strPath, i+1))
  {
    const wxString strDir = strPath.Mid(0,i);
	  if (!::wxDirExists(strDir))
			::wxMkdir(strDir);
  }
}

bool aga_unzip(const char* zipfile, const char* destdir)
{
	wxArrayString aFiles;
	const unsigned int files = aga_ziplist(zipfile, aFiles);
	for (unsigned int f = 0; f < files; f++)
	{
    const wxString& strFileName = aFiles[f];
    if (wxEndsWithPathSeparator(strFileName))  // Is dir name
    {
		  wxString strOutDir = destdir;
      if (!wxEndsWithPathSeparator(strOutDir))
		    strOutDir += wxFILE_SEP_PATH;
		  strOutDir += strFileName;
		  if (!::wxDirExists(strOutDir))
			  ::wxMkdir(strOutDir);
    }
    else
    {
		  wxZipInputStream fin(zipfile, strFileName);

		  wxString strOutFile = destdir;
      if (!wxEndsWithPathSeparator(strOutFile) && !wxIsPathSeparator(strFileName[0]))
		    strOutFile += wxFILE_SEP_PATH;
		  strOutFile += strFileName;

		  wxString strPath;
		  ::wxSplitPath(strOutFile, &strPath, NULL, NULL);
      aga_create_dir(strPath);
	
      wxFileOutputStream fout(strOutFile);
		  fout.Write(fin);
    }
	}
	return files > 0;
}

///////////////////////////////////////////////////////////
// Zip support
///////////////////////////////////////////////////////////

#include <wx/zstream.h>
#include <../src/zlib/zlib.h>

class AgaZlibOutputStream : public wxZlibOutputStream
{
	enum { AGA_COMPRESSION_LEVEL = 9 };

public:
  AgaZlibOutputStream(wxOutputStream& stream);
};

// Ricordarsi di taroccare il sorgente della deflateInit in zlib
AgaZlibOutputStream::AgaZlibOutputStream(wxOutputStream& stream)
                   : wxZlibOutputStream(stream, AGA_COMPRESSION_LEVEL)
{ }


static size_t AddFilesToList(const wxString& strBase, const wxString& strMask, wxArrayString& aFiles)
{
  const size_t n = wxDir::GetAllFiles(strBase, &aFiles, strMask);
  return n;
}

static void AddFileToZip(const wxString& strPrefix, const wxString& strFile,
												 wxFileOutputStream& fout, wxFileOutputStream& fdir)
{
	if (!wxFileExists(strFile))
		return;

	wxString strPath, strName, strExt;
	wxSplitPath(strFile, &strPath, &strName, &strExt);	

	wxString strRelName;
	strRelName = strPath.Mid(strPrefix.Length());
	if (!strRelName.IsEmpty())
	  strRelName += '/';
	strRelName += strName;
	strRelName += '.';
	strRelName += strExt;

	const off_t nStartPos = fout.TellO();               // Memorizzo posizione

	const unsigned long dwLocalSignature = 0x04034B50;
	fout.Write(&dwLocalSignature, sizeof(dwLocalSignature));  // Scrivo PK34
  ZipLocalFileHeader zlfh; memset(&zlfh, 0, sizeof(zlfh));
	zlfh.nVersionNeeded = 20;                           // You need at least pkunzip 2.0
	zlfh.nFlags = 0x0002;																// Deep Hacking ???
	zlfh.nMethod = 8;                                   // Implode
  zlfh.nNameLength = strRelName.Length();
	fout.Write(&zlfh, sizeof(zlfh));                    // Scrivo header azzerato
	fout.Write((const char*)strRelName, zlfh.nNameLength); // Scrivo nome file

 	const off_t nDataStart = fout.TellO();              // Memorizzo posizione dati compressi
	const int nMagicOffset = -4; // Deep hacking :-)
	
	{
  	wxFileInputStream fin(strFile);
		AgaZlibOutputStream zout(fout);
		zout.Write(fin);                                  // Scrivo file compresso
    zlfh.dwUncompressedSize = fin.TellI();
	}

	fout.SeekO(nMagicOffset, wxFromEnd);

	zlfh.dwCompressedSize = fout.TellO();
	zlfh.dwCompressedSize -= nDataStart;
 	zlfh.dwCRC = ComputeFileCRC32(strFile);
	
	const time_t tMod = ::wxFileModificationTime(strFile);
  const struct tm* t = localtime(&tMod);
  if (t != NULL)
  {
	  zlfh.nLastModDate = t->tm_mday + ((t->tm_mon+1) << 5) + ((t->tm_year-80) << 9);
	  zlfh.nLastModTime = t->tm_sec/2 + (t->tm_min << 5) + (t->tm_hour << 11);
  }

  fout.SeekO(nStartPos+sizeof(dwLocalSignature), wxFromStart);
	fout.Write(&zlfh, sizeof(zlfh));
//	fout.Write((const char*)strRelName, strRelName.Length());
	
//  Deep Hacking Here!
//  const wxByte pkHeader = 0x85;
//  fout.Write(&pkHeader, 1);

	fout.SeekO(nMagicOffset, wxFromEnd);

	ZipDirectoryFileHeader zdfh; memset(&zdfh, 0, sizeof(zdfh));
	zdfh.nVersionUsed = zlfh.nVersionNeeded;
	memcpy(&zdfh.zlfh, &zlfh, sizeof(zlfh));
	zdfh.nLocalHeaderOffset = nStartPos;

	const unsigned long dwDirectorySignature = 0x02014B50; // Scrivo PK12
	fdir.Write(&dwDirectorySignature, sizeof(dwDirectorySignature));
	fdir.Write(&zdfh, sizeof(zdfh));                    // Scrivo entry directory
  fdir.Write((const char*)strRelName, strRelName.Length());
}

static bool AddFilesToZip(const wxString& strBase, wxArrayString& aFiles, const char* zipfile)
{
  const char* zipdir = "zipdir.tmp";

	wxFileOutputStream fout(zipfile);

	off_t nDirSize = 0, nDirStart = 0;

	if (aFiles.GetCount() > 0)  // Dummy test
	{
		wxFileOutputStream fdir(zipdir);
    for (size_t i = 0; i < aFiles.GetCount(); i++) 
			AddFileToZip(strBase, aFiles[i], fout, fdir);
		nDirSize = fdir.TellO();
		nDirStart = fout.TellO();
  }
	
	if (nDirSize > 0)
	{
		wxFileInputStream fdir(zipdir);
		fout.Write(fdir); // Append central directory
		
		ZipDirectoryEnd zde; memset(&zde, 0, sizeof(zde));
		zde.nDiskEntries = zde.nTotalEntries = aFiles.GetCount();
		zde.nSize = nDirSize;
		zde.nStartOffset = nDirStart;

		const unsigned long dwEndDirectorySignature = 0x06054B50;
		fout.Write(&dwEndDirectorySignature, sizeof(dwEndDirectorySignature));
		fout.Write(&zde, sizeof(zde));
	}

	if (nDirSize > 0)
		wxRemoveFile(zipdir);

	return (nDirSize > 0 || aFiles.GetCount() == 0);
}

bool aga_zip(const char* srcfiles, const char* zipfile)
{
	wxString strBase, strMask, strExt;
	wxSplitPath(srcfiles, &strBase, &strMask, &strExt);
	strMask += '.';
	strMask += strExt;

	wxArrayString aFiles;
	AddFilesToList(strBase, strMask, aFiles);

	return AddFilesToZip(strBase, aFiles, zipfile);
}

bool aga_zip_filelist(const char* filelist, const char* zipfile)
{
  wxArrayString aFiles;
	ifstream fin(filelist);
	while (!fin.eof())
	{
		char name[_MAX_PATH];
		fin.getline(name, sizeof(name));
		if (*name)
		  aFiles.Add(name);
		else
			break;
	}
	return AddFilesToZip("", aFiles, zipfile);
}