////////////////////////////////////////////////////////
// File Name: ArchiveWriterImp.cpp
//
//  Abstract: Implements the CArchiveWriter abstract class
//
////////////////////////////////////////////////////////

#include "stdafx.h"
#include "ArchiveWriterImp.h"
#include "ArchiveUpdateCallback.h"
#include "FileHelperFunctions.h"
#include "LoadCodecs.h"
#include "../7Zip/CPP/Windows/FileFind.h"
#include "../7Zip/CPP/7zip/Common/FileStreams.h"

using namespace NWindows::NDLL;

// globals
extern HMODULE gModule;

extern CCodecs* gpCodecs;
extern CMyComPtr<ICompressCodecsInfo> gspCompressCodecsInfo;
extern bool gbCodecsLoaded;

CArchiveWriterImp::CArchiveWriterImp() : m_RefCount(1)
{
	// private constructor
}

/////////////////////////////////////////////////////////////////////////////
// Function       : CArchiveWriterImp::AddFile
// Description    : Adds a file to the ArchiveWriter.  Use WriteArchive to 
//                  actually write the archive file to disk after you've added
//                  all your files.
// Return type    : int
// Argument       : BSTR path - the path to the file to add
// Argument       : BSTR archivePath - the path that the file should 
//                  have in the archive
/////////////////////////////////////////////////////////////////////////////
int CArchiveWriterImp::AddFile(BSTR localPath, BSTR archivePath)
{
	m_Files.push_back(make_pair(localPath, archivePath));
	return WRITECOMPRESSEDFILE_SUCCESS;
}

/////////////////////////////////////////////////////////////////////////////
// Function       : CArchiveWriterImp::CreateArchiveWriter
// Description    : Creates a new ArchiveWriter
// Return type    : int - error code
// Argument [Out] : CArchiveWriter*& pArchiveHandle - the handle of the 
//                  new ArchiveWriter
/////////////////////////////////////////////////////////////////////////////
int CArchiveWriterImp::CreateArchiveWriter(CArchiveWriter*& pArchiveHandle)
{
	if (!CArchiveWriterImp::LoadDll())
	{
		// failed to load the DLL
		return WRITECOMPRESSEDFILE_ERROR;
	}

	// this only generates the archive writer object, it doesn't create the archive or anything
	pArchiveHandle = new CArchiveWriterImp();
	return WRITECOMPRESSEDFILE_SUCCESS;
}

/////////////////////////////////////////////////////////////////////////////
// Function       : CArchiveWriterImp::LoadDll
// Description    : Loads the Codec information if it is not already loaded
// Return type    : bool - success
/////////////////////////////////////////////////////////////////////////////
bool CArchiveWriterImp::LoadDll()
{
	UString PathToThisDLL;
	CATLString PathTo7zDLL;

	if(!gbCodecsLoaded)
	{
		if(MyGetModuleFileName(gModule, PathToThisDLL))
		{
			PathTo7zDLL = GetDirectoryName((LPCTSTR)PathToThisDLL);
			PathTo7zDLL = CombinePaths(PathTo7zDLL, DLLPath);

			gpCodecs = new CCodecs((LPCTSTR)PathTo7zDLL);
			gspCompressCodecsInfo = gpCodecs;	//Reference to be released when this DLL is released.
			if(SUCCEEDED(gpCodecs->Load()))
			{
				gbCodecsLoaded = true;
			}
		}
	}
	
	return gbCodecsLoaded;
}

// private helper method
int CArchiveWriterImp::GetFormatFromEnum(CompressionFormat format)
{
	// get the format index from the loaded codecs
	int formatIndex = -1;
	switch(format)
	{
	case Zip:
		formatIndex = gpCodecs->FindFormatForExtension(_T("zip"));
		break;
	case SevenZip:
		formatIndex = gpCodecs->FindFormatForExtension(_T("7z"));
		break;
	default:
		// not supported
		formatIndex = -1;
		break;
	}
	return formatIndex;
}


/////////////////////////////////////////////////////////////////////////////
// Function       : CArchiveWriterImp::SaveArchive
// Description    : Creates an archive of the previously specified files and 
//                  format
// Return type    : int - error code
// Argument       : BSTR archivePath - archive will be created at this 
//                  path
// Argument       : CompressionFormat format - the format you want to use
/////////////////////////////////////////////////////////////////////////////
int CArchiveWriterImp::SaveArchive(BSTR archivePath, CompressionFormat format)
{
	// get the format index
	int formatIndex = CArchiveWriterImp::GetFormatFromEnum(format);

	// create an IOutArchive handle for the specified format
	CMyComPtr<IOutArchive> outArchive;
	if (formatIndex == -1 || !SUCCEEDED(gpCodecs->CreateOutArchive(formatIndex, outArchive)))
	{
		// failed to create the archive handle (probably format is not supported)
		return WRITECOMPRESSEDFILE_ERROR_FORMAT;
	}
	
	// load the file info from this class into dirItems
    CObjectVector<CDirItem> dirItems;
    for (vector<pair<CATLString, CATLString>>::iterator iter = m_Files.begin(); iter != m_Files.end(); ++iter)
    {
		pair<CATLString, CATLString> file = *iter;
		CDirItem di;
		CATLString localPath = file.first; // source path

		NWindows::NFile::NFind::CFileInfoW fi;
		if (!fi.Find(localPath))
		{
			// can't find one of the files!
			return WRITECOMPRESSEDFILE_ERROR_READING_FILE;
		}

		di.Attrib = fi.Attrib;
		di.Size = fi.Size;
		di.CTime = fi.CTime;
		di.ATime = fi.ATime;
		di.MTime = fi.MTime;
		di.Name = file.second;  // destination path
		di.FullPath = localPath;
		dirItems.Add(di);
    }

	// create an empty archive file
    COutFileStream* outFileStreamSpec = new COutFileStream;
    CMyComPtr<IOutStream> outFileStream = outFileStreamSpec;
    if (!outFileStreamSpec->Create(archivePath, false))
    {
		// can't create archive file
		return WRITECOMPRESSEDFILE_ERROR_CREATE;
    }

	// Create an ArchiveUpdateCallback and pass it our file paths
	CArchiveUpdateCallback* updateCallbackSpec = new CArchiveUpdateCallback();
	updateCallbackSpec->Init(&dirItems);
	CMyComPtr<IArchiveUpdateCallback> updateCallback(updateCallbackSpec);

	// Add the files to the empty archive
	HRESULT result = outArchive->UpdateItems(outFileStream, dirItems.Size(), updateCallback);
	
	if (result != S_OK)
	{
		// error adding the files
		return WRITECOMPRESSEDFILE_ERROR_UPDATE;
	}

	return WRITECOMPRESSEDFILE_SUCCESS;
}

ULONG CArchiveWriterImp::Release()
{
	ULONG NewRef = --m_RefCount;

	if(NewRef == 0)
	{
		delete this;
	}

	return NewRef;
}