#ifndef EXTRACT_CALLBACKS_H
#define EXTRACT_CALLBACKS_H

#include "7z_includes.h"
#include "extract_delegate.h"
#include <QDebug>

using namespace NWindows;

//////////////////////////////////////////////////////////////
// Archive Extracting callback class

static const wchar_t *kCantDeleteOutputFile = L"ERROR: Can not delete output file ";

static const char *kTestingString    =  "Testing     ";
static const char *kExtractingString =  "Extracting  ";
static const char *kSkippingString   =  "Skipping    ";

static const char *kUnsupportedMethod = "Unsupported Method";
static const char *kCRCFailed = "CRC Failed";
static const char *kDataError = "Data Error";
static const char *kUnknownError = "Unknown Error";

static const wchar_t *kEmptyFileAlias = L"[Content]";

static HRESULT IsArchiveItemProp(IInArchive *archive, UInt32 index, PROPID propID, bool &result)
{
  NCOM::CPropVariant prop;
  RINOK(archive->GetProperty(index, propID, &prop));
  if (prop.vt == VT_BOOL)
	result = VARIANT_BOOLToBool(prop.boolVal);
  else if (prop.vt == VT_EMPTY)
	result = false;
  else
	return E_FAIL;
  return S_OK;
}
static HRESULT IsArchiveItemFolder(IInArchive *archive, UInt32 index, bool &result)
{
  return IsArchiveItemProp(archive, index, kpidIsDir, result);
}

class CArchiveExtractCallback:
  public IArchiveExtractCallback,
  public ICryptoGetTextPassword,
  public CMyUnknownImp
{
public:
  MY_UNKNOWN_IMP1(ICryptoGetTextPassword)

  // IProgress
  STDMETHOD(SetTotal)(UInt64 size);
  STDMETHOD(SetCompleted)(const UInt64 *completeValue);

  // IArchiveExtractCallback
  STDMETHOD(GetStream)(UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode);
  STDMETHOD(PrepareOperation)(Int32 askExtractMode);
  STDMETHOD(SetOperationResult)(Int32 resultEOperationResult);

  // ICryptoGetTextPassword
  STDMETHOD(CryptoGetTextPassword)(BSTR *aPassword);

private:
  CMyComPtr<IInArchive> _archiveHandler;
  UString _directoryPath;  // Output directory
  UString _filePath;       // name inside arcvhive
  UString _diskFilePath;   // full path to file on disk
  bool _extractMode;
  bool all;
  ExtractDelegate * delegate;
  UInt32 _index;
  struct CProcessedFileInfo
  {
	FILETIME MTime;
	UInt32 Attrib;
	bool isDir;
	bool AttribDefined;
	bool MTimeDefined;
  } _processedFileInfo;

  COutFileStream *_outFileStreamSpec;
  CMyComPtr<ISequentialOutStream> _outFileStream;

public:
  void Init(IInArchive *archiveHandler, const UString &directoryPath);

  UInt64 NumErrors;
  bool PasswordIsDefined;
  QList<QByteArray> allFiles;
  UString Password;
  Byte * data;
  UInt64 newFileSize;
  QMap<qint32, qint32> indexesToPages;

  CArchiveExtractCallback(const QMap<qint32, qint32> & indexesToPages ,bool c = false,ExtractDelegate * d = 0) : PasswordIsDefined(false),all(c),delegate(d),indexesToPages(indexesToPages) {}
  ~CArchiveExtractCallback() {MidFree(data);}
};

void CArchiveExtractCallback::Init(IInArchive *archiveHandler, const UString &directoryPath)
{
  NumErrors = 0;
  _archiveHandler = archiveHandler;
  directoryPath;//unused 
}

STDMETHODIMP CArchiveExtractCallback::SetTotal(UInt64 /* size */)
{
  return S_OK;
}

STDMETHODIMP CArchiveExtractCallback::SetCompleted(const UInt64 * /* completeValue */)
{
  return S_OK;
}

STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index,
	ISequentialOutStream **outStream, Int32 askExtractMode)
{
  *outStream = 0;
  _outFileStream.Release();

  if(indexesToPages.isEmpty())
      _index = index;
  else
    _index = indexesToPages.value(index);

  {
	// Get Name
	NCOM::CPropVariant prop;
	RINOK(_archiveHandler->GetProperty(index, kpidPath, &prop));
	
	UString fullPath;
	if (prop.vt == VT_EMPTY)
	  fullPath = kEmptyFileAlias;
	else
	{
	  if (prop.vt != VT_BSTR)
		return E_FAIL;
	  fullPath = prop.bstrVal;
	}
	_filePath = fullPath;
  }

  askExtractMode;//unused 
  //if (askExtractMode != NArchive::NExtract::NAskMode::kExtract)
	//return S_OK;

  {
	// Get Attrib
	NCOM::CPropVariant prop;
	RINOK(_archiveHandler->GetProperty(index, kpidAttrib, &prop));
	if (prop.vt == VT_EMPTY)
	{
	  _processedFileInfo.Attrib = 0;
	  _processedFileInfo.AttribDefined = false;
	}
	else
	{
	  if (prop.vt != VT_UI4)
		return E_FAIL;
	  _processedFileInfo.Attrib = prop.ulVal;
	  _processedFileInfo.AttribDefined = true;
	}
  }

  RINOK(IsArchiveItemFolder(_archiveHandler, index, _processedFileInfo.isDir));

  {
	// Get Modified Time
	NCOM::CPropVariant prop;
	RINOK(_archiveHandler->GetProperty(index, kpidMTime, &prop));
	_processedFileInfo.MTimeDefined = false;
	switch(prop.vt)
	{
	  case VT_EMPTY:
		// _processedFileInfo.MTime = _utcMTimeDefault;
		break;
	  case VT_FILETIME:
		_processedFileInfo.MTime = prop.filetime;
		_processedFileInfo.MTimeDefined = true;
		break;
	  default:
		return E_FAIL;
	}

  }

  //se necesita conocer el tamaño del archivo para poder reservar suficiente memoria
  bool newFileSizeDefined;
  {
	// Get Size
	NCOM::CPropVariant prop;
	RINOK(_archiveHandler->GetProperty(index, kpidSize, &prop));
	newFileSizeDefined = (prop.vt != VT_EMPTY);
	if (newFileSizeDefined)
	  newFileSize = ConvertPropVariantToUInt64(prop);
  }

  //No hay que crear ningún fichero, ni directorios intermedios
  /*{
	// Create folders for file
	int slashPos = _filePath.ReverseFind(WCHAR_PATH_SEPARATOR);
	if (slashPos >= 0)
	  NFile::NDirectory::CreateComplexDirectory(_directoryPath + _filePath.Left(slashPos));
  }

  UString fullProcessedPath = _directoryPath + _filePath;
  _diskFilePath = fullProcessedPath;
  */
  if (_processedFileInfo.isDir)
  {
	//NFile::NDirectory::CreateComplexDirectory(fullProcessedPath);
  }
  else
  {
	  /*NFile::NFind::CFileInfoW fi;
	  if (fi.Find(fullProcessedPath))
	  {
	  if (!NFile::NDirectory::DeleteFileAlways(fullProcessedPath))
	  {
	  qDebug() <<(UString(kCantDeleteOutputFile) + fullProcessedPath);
	  return E_ABORT;
	  }
	  }*/
	  if(newFileSizeDefined)
	  {
		  CBufPtrSeqOutStream *outStreamSpec = new CBufPtrSeqOutStream;
		  CMyComPtr<CBufPtrSeqOutStream> outStreamLocal(outStreamSpec);
		  data = (Byte *)MidAlloc(newFileSize);
		  outStreamSpec->Init(data, newFileSize);
		  *outStream = outStreamLocal.Detach();
	  }
	  else
	  {

	  }

  }
  return S_OK;
}

STDMETHODIMP CArchiveExtractCallback::PrepareOperation(Int32 askExtractMode)
{
  _extractMode = false;
  switch (askExtractMode)
  {
	case NArchive::NExtract::NAskMode::kExtract:  _extractMode = true; break;
  };
 /* switch (askExtractMode)
  {
	case NArchive::NExtract::NAskMode::kExtract:  qDebug() << (kExtractingString); break;
	case NArchive::NExtract::NAskMode::kTest:  qDebug() <<(kTestingString); break;
	case NArchive::NExtract::NAskMode::kSkip:  qDebug() <<(kSkippingString); break;
  };*/
  //qDebug() << _filePath;
  return S_OK;
}

STDMETHODIMP CArchiveExtractCallback::SetOperationResult(Int32 operationResult)
{
  switch(operationResult)
  {
	case NArchive::NExtract::NOperationResult::kOK:
		if(all && !_processedFileInfo.isDir)
		{
			QByteArray rawData((char *)data,newFileSize);
			MidFree(data);
			data = 0;
			if(delegate != 0)
				delegate->fileExtracted(_index,rawData);
			else
			{
				allFiles.append(rawData);
			}
		}
	  break;
	default:
	{
	  NumErrors++;
	  qDebug() << "     ";
	  switch(operationResult)
	  {
		case NArchive::NExtract::NOperationResult::kUnSupportedMethod:
		  if(delegate != 0)
			  delegate->unknownError(_index);
		  qDebug() << kUnsupportedMethod;
		  break;
		case NArchive::NExtract::NOperationResult::kCRCError:
		  if(delegate != 0)
			  delegate->crcError(_index);
		  qDebug() << kCRCFailed;
		  break;
		case NArchive::NExtract::NOperationResult::kDataError:
		  if(delegate != 0)
			  delegate->unknownError(_index);
		  qDebug() << kDataError;
		  break;
		default:
		  if(delegate != 0)
			  delegate->unknownError(_index);
		  qDebug() << kUnknownError;
	  }
	}
  }
/*
  if (_outFileStream != NULL)
  {
	if (_processedFileInfo.MTimeDefined)
	  _outFileStreamSpec->SetMTime(&_processedFileInfo.MTime);
	RINOK(_outFileStreamSpec->Close());
  }
  _outFileStream.Release();
  if (_extractMode && _processedFileInfo.AttribDefined)
	NFile::NDirectory::MySetFileAttributes(_diskFilePath, _processedFileInfo.Attrib);*/
  //qDebug() << endl;
  return S_OK;
}


STDMETHODIMP CArchiveExtractCallback::CryptoGetTextPassword(BSTR *password)
{
  if (!PasswordIsDefined)
  {
	// You can ask real password here from user
	// Password = GetPassword(OutStream);
	// PasswordIsDefined = true;
	qDebug() << "Password is not defined" << endl;
	return E_ABORT;
  }
  return StringToBstr(Password, password);
}

#endif
