

#include "stdafx.h"
#include "IsisScanner.h"
#include <pixdflt.h>
#include <windows.h>
#include "scan.h"
#include <stdio.h>
#include "utils.h"
#include "logger.h"
#include <filesystem>
#include <strsafe.h>
#include "dirent.h"
#include "SystemInfo.h"

#define DEMAND_SUCCESS(code) {auto __pawsOff = code;if (__pawsOff != PIX_SUCCESS) return AtlReportPixError(GetObjectCLSID(), hScan, __pawsOff);}

HRESULT CreateHresultAndReportIfNecessary(INT32 code, CLSID clsid, PixDrvHandle driver)
{
	if (code != PIX_SUCCESS)
	{
		return AtlReportPixError(clsid, driver, code);
	}
	return code;
}


HWND    hDlgStatus = nullptr;
DLGPROC lpfnStatusDlgProc = nullptr;

long    ScanAhead = 1;
long    ScanAheadClear = 0;
long    dwPagesAhead = 0L;
long 	dwThroughPut = 0L;

char lpcDriverName[255];

HINSTANCE hInst;

HANDLE PixDefaultInstance;


int bFileLoaded;
PixDrvHandle hFileDriver;



CIsisScanner::CIsisScanner()
	: m_driverName(nullptr)
	, m_scanningDataTempLocation(nullptr)
	, m_configFilename(nullptr)
	, m_scannerLoaded(FALSE)
	, m_fileBuffer(nullptr)
	, m_fileBufferSize(0)
	, m_pSystemInfo(nullptr)
	, m_hwnd(nullptr)

	
{
	memset(m_outputName1, 0, MAX_PATH * sizeof(char));
	memset(m_outputName2, 0, MAX_PATH * sizeof(char));
}

CIsisScanner::~CIsisScanner()
{
	if (::IsWindow(m_hwnd))
	{
		::DestroyWindow(m_hwnd);
	}
}

STDMETHODIMP CIsisScanner::FinalConstruct()
{
	char path[MAX_PATH];

	GetTempPathA(MAX_PATH, path);
	GetTempFileNameA(path, "~vdir-", 0, m_outputName1);
	GetTempFileNameA(path, "~vdir-", 0, m_outputName2);

	m_fileBufferSize = 1 << 21;
	m_fileBuffer = PixMemAllocPtr<char*>(m_fileBufferSize, 0);

	IObjectFactory* pFactory;
	const auto hr = ::CoCreateInstance(CLSID_ObjectFactory, nullptr, CLSCTX_INPROC_SERVER, IID_IObjectFactory, reinterpret_cast<void**>(&pFactory));
	if (FAILED(hr)) return hr;

	StartLookingForConfigDialog();

	return hr;
}

STDMETHODIMP CIsisScanner::FinalRelease()
{
	ReleaseScanner();
	::SysFreeString(m_driverName);
	::SysFreeString(m_configFilename);
	::SysFreeString(m_scanningDataTempLocation);
	remove(m_outputName1);
	remove(m_outputName2);
	PixMemFreePtr(m_fileBuffer);
	m_fileBufferSize = 0;
	if (m_pSystemInfo) m_pSystemInfo->Release();
	if (m_page1) ::SafeArrayDestroy(m_page1);
	if (m_page2) ::SafeArrayDestroy(m_page2);
	StopLookingForConfigDialog();

	try
	{
		logg->flush();

	}
	catch (...)
	{

	}
	spdlog::shutdown();

	return S_OK;
}

STDMETHODIMP CIsisScanner::InterfaceSupportsErrorInfo(REFIID riid)
{
	static const IID* const arr[] =
	{
		&IID_IIsisScanner
	};

	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		if (InlineIsEqualGUID(*arr[i], riid))
			return S_OK;
	}
	return S_FALSE;
}


STDMETHODIMP CIsisScanner::ScanToByteArrays(LONG parentHwnd)
{
	
	
	
	
	
	
	
	
	m_hwnd = IsWindow(reinterpret_cast<HWND>(parentHwnd)) ? reinterpret_cast<HWND>(parentHwnd) : nullptr;
	
	return ScanBatchToByteArrays(0);
}

STDMETHODIMP CIsisScanner::ScanToFiles(BSTR Directory)
{
	

	
	

	
	
	
	
	
	
	
	
	
	
	return E_NOTIMPL;
}

HRESULT CIsisScanner::ApplyConfiguration() const
{
	USES_CONVERSION;

	char dummy[255];
	DEMAND_SUCCESS(PixTagSetLong(&hScan, TAG_CONTRAST, 0, TAG_CONTRAST_AUTO)); 
	DEMAND_SUCCESS(PixTagSetLong(&hScan, TAG_SAMPLESPERPIXEL, 0, 1));
	DEMAND_SUCCESS(PixTagSetLong(&hScan, TAG_BITSPERSAMPLE, 0, 1));
	DEMAND_SUCCESS(PixTagSetLong(&hScan, TAG_PHOTOMETRICINTERPRETATION, 0, TAG_PHOTOMETRIC_WHITE1));
	DEMAND_SUCCESS(PixTagSetLong(&hScan, TAG_COMPRESSION, 0, TAG_COMPRESSION_NONE));
	if (PixTagGetAscii(&hScan, TAG_PAGESIZE, 255, dummy) >= 0)
	{
		try
		{
			DEMAND_SUCCESS(PixTagSetAscii(&hScan, TAG_PAGESIZE, "A3"));
		}
		catch (...)
		{
		}
	}
	PixTagRestoreFile(&hScan, nullptr, W2A(m_configFilename));
	DEMAND_SUCCESS(PixTagSetLong(&hScan, TAG_SCANTYPE, 0, TAG_SCANTYPE_DUPLEX));

	return S_OK;
}

STDMETHODIMP CIsisScanner::Init(BSTR ScannerModelName, BSTR ConfigFilename)
{
	USES_CONVERSION;
	logg->debug("Entering Init({}, {})", W2A(ScannerModelName), W2A(ConfigFilename));
	::SysFreeString(m_configFilename);
	m_configFilename = ::SysAllocString(ConfigFilename);
	const HRESULT hr = ModelNameToDriverName(ScannerModelName, &m_driverName);
	if (FAILED(hr)) return hr;
	return InitByDriverName(m_driverName, ConfigFilename);
}












STDMETHODIMP CIsisScanner::InitByDriverName(BSTR DriverName, BSTR ConfigFilename)
{
	USES_CONVERSION;
	logg->debug("Entering InitByDriverName()");

	m_driverName = SysAllocString(DriverName);
	m_configFilename = ::SysAllocString(ConfigFilename);
	const auto loadResult = LoadScanner();
	if (FAILED(loadResult))
	{
		logg->debug("InitByDriverName failed with HRESULT {}", loadResult);
	}
	else
	{
		logg->debug("InitByDriverName OK");
	}
	return loadResult;
}

STDMETHODIMP CIsisScanner::get_ConfigFilename(BSTR * pPath)
{
	*pPath = ::SysAllocString(m_configFilename);
	return S_OK;
}

STDMETHODIMP CIsisScanner::get_SystemInfo(ISystemInfo** ppResult)
{
	if (m_pSystemInfo)
	{
		return m_pSystemInfo->QueryInterface(ppResult);
	}
	return E_POINTER;
}

INT32 CIsisScanner::myPixRunZone(PixDrvHandlePtr lpScan, char * buf, int size)
{
	INT32 lValue;
	PIXDRVRUN hRun;

	if (!(lValue = PixDrvRunStart(lpScan, &hRun))) {
		while (true) {
			lValue = PixDrvRunStep(hRun, buf, static_cast<INT16>(size), PIX_DRV_DATANOWAIT);
			if (lValue < 0)
			{
				break;
			}

			
			
			
			
			
			
			
			
			
			
			
			
			

			
			if (CancelHit())
			{
				bCancelScanning = FALSE;
				lValue = MAKELONG(0, PIX_ERR_CANCEL);
				
				break;
			}
		}
		if (lValue < 0 && HIWORD(lValue) != static_cast<UINT16>(PIX_ERR_ENDZONE))
		{
			PixDrvRunDone(hRun);
		}
		else
		{
			lValue = PixDrvRunDone(hRun);
		}
	}
	return lValue;
}

#define BUFFSIZE 8192
static char staticBuf[BUFFSIZE];

HRESULT CIsisScanner::ScanBatchToFiles(HWND , long )
{
	return E_NOTIMPL;
}


HRESULT CIsisScanner::ScanBatchToByteArrays(long Orientation)
{
	logg->debug("Entering ScanBatchToByteArrays()");

	USES_CONVERSION;

	HRESULT ret = S_OK;
	INT32 PixErr, dwFeeder, dwFlags;
	char * OutputName;
	const auto autoDeskew = true;
	BOOL bShouldCheckFeeder, bDidScan = FALSE;

	dwThroughPut = 0L;

	PixDrvHandle hPixfPack;
	DEMAND_SUCCESS(PixDrvLoadInitPipe(nullptr, "RAS2RUN|RUN2G4|PIXFPACK", &hPixfPack, 0));
	DEMAND_SUCCESS(PixTagSetLong(&hPixfPack, TAG_FILETYPE, 0, TAG_FILETYPE_TIFF));
	DEMAND_SUCCESS(PixDrvLink(&hScan, &hPixfPack));

	const long BuffSize = BUFFSIZE;

	PixTagSetLong(&hScan, TAG_SCANAHEAD, 0, ScanAhead);

	DEMAND_SUCCESS(PixTagGetLong(&hScan, TAG_FEEDER, 0, &dwFeeder));

	
	switch (dwFeeder &
		(TAG_FEEDER_FEED | TAG_FEEDER_TELLFEED))
	{
	case TAG_FEEDER_FEED:
		
		bShouldCheckFeeder = FALSE;
		break;
	case TAG_FEEDER_FEED | TAG_FEEDER_TELLFEED:
		
		bShouldCheckFeeder = TRUE;
		break;
	default:
		bShouldCheckFeeder = FALSE;
		ret = AtlReportError(GetObjectCLSID(), "Only scanners with an automatic feeder are supported.");
		goto Cleanup;
	}

	BOOL bDoScan;

	if (bShouldCheckFeeder)
	{
		PixErr = PixDrvPageLoaded(&hScan, TAG_FEEDER_FEED, &dwFlags);
		if (PixErr < 0) 
		{
			ret = AtlReportPixError(GetObjectCLSID(), hScan, PixErr);
			IErrorInfo* e = nullptr;
			GetErrorInfo(0, &e);
			BSTR bstrErr = nullptr;
			e->GetDescription(&bstrErr);
			Fire_ScanningError(PixErr, bstrErr);
			::SysFreeString(bstrErr);
			ReleaseScanner();
			goto Cleanup;
		}

		if (dwFlags != PIX_DRV_STACKPAGE)
		{
			
			Fire_FeederEmpty();
			goto Cleanup;
		}
		else
		{
			bDoScan = TRUE;
		}
	}
	else
	{
		PixErr = 0;
		bDoScan = TRUE;
	}

	bool eventPending = false;
	bool topSide = false;
	auto cancel = VARIANT_FALSE;
	bool feederEmpty = false;

	while (bDoScan && !PixErr)
	{
		topSide = !topSide;
		if (topSide)
		{
			if (bDidScan)
			{
				eventPending = false;
				const auto hr = CreateDataAndFireScannedEvent(m_outputName1, m_outputName2, autoDeskew, &cancel);
				if (FAILED(hr)) goto Cleanup;
				if (cancel)
				{
					bCancelScanning = TRUE;
				}
			}
			OutputName = m_outputName1;
		}
		else
		{
			OutputName = m_outputName2;
		}

		if (!PixErr && ScanAhead)
		{
			PixTagGetLong(&hScan, TAG_SCANAHEAD_PAGES, 0, &dwPagesAhead);
		}

		PixErr = PixTagSetLong(&hScan, TAG_SCANORIENTATION, 0, Orientation);
		if (!PixErr)
		{
			PixErr = PixTagSetAscii(&hScan, TAG_OUTPUTNAME, OutputName);
		}
		if (!PixErr)
		{
#ifdef USE_GLOBALALLOC
			PixErr = myPixRunZone(&hScan, Buf, (int)BuffSize);
			#elseif USE_SHAREDMEMORY
				PixErr = myPixRunZone(&hScan, sharedBuf, (int)BuffSize);
#else
			PixErr = myPixRunZone(&hScan, staticBuf, static_cast<int>(BuffSize));
#endif
		}
		bDidScan = true;
		
		if (PixErr < 0)
		{
			logg->debug("PixErr={}, removing OutputName({})", PixErr, OutputName);
			remove(OutputName);
			break;
		}

		if (!topSide)
		{
			eventPending = true;
		}

		if (bShouldCheckFeeder)
		{
			PixErr = PixDrvPageLoaded(&hScan, TAG_FEEDER_FEED, &dwFlags);
			if (PixErr < 0 || (dwFlags != PIX_DRV_STACKPAGE))
			{
				feederEmpty = true;
				break;
			}
			else
			{
				continue;
			}
		}
	}

	if (eventPending)
	{
		CreateDataAndFireScannedEvent(m_outputName1, m_outputName2, autoDeskew, &cancel);
	}

	if (feederEmpty)
	{
		Fire_FeederEmpty();
	}

	if (bDidScan && ScanAheadClear)
	{
		PixDrvSend(&hScan, PIX_DRV_SCANAHEADCLEAR, 0, 0);
	}
	dwPagesAhead = 0;
	ret = ChkPixError(hScan, PixErr);

Cleanup:
	
	PixDrvLink(&hScan, nullptr);
	PixDrvUnloadPipe(&hPixfPack);
	logg->flush();

	return ret;
}

BOOL CIsisScanner::CancelHit() const
{
	return bCancelScanning;
}


HRESULT  CIsisScanner::ChkPixError(PixDrvHandle hDrv, INT32 lCode)
{
	USES_CONVERSION;
	char lpcString[255];
	auto ret = S_OK;
	const auto hi = static_cast<short>(HIWORD(lCode));
	
	if (lCode == MAKELONG(0, PIX_ERR_CANCEL))
	{
		Fire_BatchAborted();
	}
	else if (!hDrv.lpCode) 
	{
		PixDrvGetError(static_cast<char *>(nullptr), nullptr, lCode, lpcString,
			sizeof(lpcString));
		Fire_ScanningError(lCode, ::SysAllocString(A2W(lpcString)));
		ret = AtlReportError(GetObjectCLSID(), lpcString, GUID_NULL, MAKE_HRESULT(SEVERITY_ERROR, FACILITY_NULL, -hi));
	}
	else if (lCode < PIX_SUCCESS)
	{
		PixDrvGetError(static_cast<char *>(nullptr), &hDrv, lCode, lpcString,
			sizeof(lpcString));
		Fire_ScanningError(-hi, ::SysAllocString(A2W(lpcString)));
		ret = AtlReportError(GetObjectCLSID(), lpcString, GUID_NULL, MAKE_HRESULT(SEVERITY_ERROR, FACILITY_NULL, -hi));
	}
	return ret;
}

STDMETHODIMP CIsisScanner::ConfigureViaGui(LONG maybeHwnd)
{
	USES_CONVERSION;

	HRESULT ret;
	const auto hwnd = IsWindow(reinterpret_cast<HWND>(maybeHwnd)) ? reinterpret_cast<HWND>(maybeHwnd) : nullptr;
	auto PixErr = PixDrvSetDialog(&hScan, hwnd, hInst, PIX_DRV_SET_DIALOG_AREA);
	const auto hi = static_cast<short>(HIWORD(PixErr)), lo = static_cast<short>(LOWORD(PixErr));
	if (hi == PIX_ERR_CANCEL)
	{
		ret = S_FALSE;
		goto Cleanup;
	}

	PixErr = PixTagSaveFile(&hScan, nullptr, W2A(m_configFilename));
	ret = CreateHresultAndReportIfNecessary(PixErr, GetObjectCLSID(), hScan);

Cleanup:
	return ret;
}

HRESULT CIsisScanner::LoadScanner()
{
	USES_CONVERSION;
	HRESULT ret = S_OK;
	logg->debug("Entering LoadScanner()");
	auto error = PixDrvLoad(nullptr, W2A(m_driverName), &hScan, 0);
	if (error > PIX_SUCCESS)
	{
		logg->error("PixDrvLoad failed with code {}", error);
		ret = AtlReportError(GetObjectCLSID(), "Another instance of a driver that was already in memory was loaded", GUID_NULL, E_FAIL);
		goto Cleanup;
	}
	else if (error < PIX_SUCCESS)
	{
		logg->error("Driver did not load");
		ret = AtlReportPixError(GetObjectCLSID(), hScan, error);
		goto Cleanup;
	}

	error = PixDrvInitialize(&hScan, nullptr, 0);
	if (error != PIX_SUCCESS)
	{
		logg->error("PixDrvInitialize failed with code {}", error);
		char lpcString[255];
		PixDrvGetError(static_cast<char *>(nullptr), nullptr, error, lpcString, sizeof(lpcString));
		logg->error("PixDrvGetError returned {}", lpcString);

		ret = AtlReportPixError(GetObjectCLSID(), hScan, error);
		goto Cleanup;
	}

	const auto defaultResult = ApplyConfiguration();
	if (FAILED(defaultResult))
	{
		ret = defaultResult;
		goto Cleanup;
	}
	m_scannerLoaded = true;

Cleanup:
	if (FAILED(ret))
	{
		::PixDrvUnload(&hScan);
	}
	return ret;
}

HRESULT CIsisScanner::ModelNameToDriverName(std::wstring modelName, BSTR* pResult) const
{
	USES_CONVERSION;
	::SysFreeString(*pResult);
	HRESULT ret = S_OK;
	try
	{
		auto driver = FindScannerDriverName(std::string(W2A(modelName.c_str())));
		*pResult = ::SysAllocString(A2W(driver.c_str()));
	}
	catch (std::exception ex)
	{
		ret = AtlReportError(GetObjectCLSID(), ex.what(), GUID_NULL, E_FAIL);
	}
	return ret;
}

HRESULT CIsisScanner::DriverDescriptionToDriverName(BSTR driverDescription, BSTR* pResult) const
{
	return m_pSystemInfo->DriverDescriptionToDriverName(driverDescription, pResult);
}

bool CIsisScanner::SubscriberExists()
{
	const auto nConnections = m_vec.GetSize();

	
	for (auto nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
	{
		Lock();
		const CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
		Unlock();
		const auto pDispatch = reinterpret_cast<IDispatch*>(sp.p);

		if (pDispatch != nullptr)
		{
			return true;
		}
	}
	return false;
}

HRESULT CIsisScanner::CreateByteArray(const char* filename, SAFEARRAY **ppResult)
{
	const auto fileHandle = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
	const auto size = GetFileSize(fileHandle, nullptr);
	DWORD numberOfBytesRead = 0;
	if (size > m_fileBufferSize)
	{
		m_fileBuffer = PixMemReallocPtr<char*>(m_fileBuffer, size, 0);
	}
	ReadFile(fileHandle, m_fileBuffer, size, &numberOfBytesRead, nullptr);
	const auto hr = CreateByteArray(m_fileBuffer, numberOfBytesRead, ppResult);
	CloseHandle(fileHandle);

	return hr;

}

HRESULT CIsisScanner::CreateByteArray(char* bytes, size_t size, SAFEARRAY **ppResult) const
{
	HRESULT hr;
	if (*ppResult)
	{
		hr = SafeArrayDestroy(*ppResult);
		if (FAILED(hr)) return AtlReportError(GetObjectCLSID(), "Couldn't destroy array", GUID_NULL, hr);
	}
	char* byteArray = nullptr;
	const auto psa = SafeArrayCreateVector(VT_UI1, 0L, size);
	hr = SafeArrayAccessData(psa, reinterpret_cast<void**>(&byteArray));
	if (FAILED(hr)) return AtlReportError(GetObjectCLSID(), "Couldn't access array data", GUID_NULL, hr);
	memcpy_s(byteArray, size, bytes, size);
	hr = SafeArrayUnaccessData(psa);
	if (FAILED(hr)) return AtlReportError(GetObjectCLSID(), "Couldn't unaccess array data", GUID_NULL, hr);
	*ppResult = psa;
	return hr;
}

HRESULT CIsisScanner::CreateDataAndFireScannedEvent(const char* filename1, const char* filename2, VARIANT_BOOL areDeskewed, VARIANT_BOOL* pCancel)
{
	m_criticalSection.Lock();

	auto hr = CreateByteArray(m_outputName1, &m_page1);
	if (FAILED(hr)) goto Cleanup;

	hr = CreateByteArray(m_outputName2, &m_page2);
	if (FAILED(hr)) goto Cleanup;

	logg->debug("Before Fire_DocumentScannedToByteArrays, cancel={}", *pCancel);
	Fire_DocumentScannedToByteArrays(m_page1, m_page2, areDeskewed, pCancel);
	logg->debug("After Fire_DocumentScannedToByteArrays, cancel={}", *pCancel);

Cleanup:
	m_criticalSection.Unlock();
	return hr;
}

STDMETHODIMP CIsisScanner::ReleaseScanner()
{
	if (m_scannerLoaded)
	{
		try
		{
			PixDrvUnload(&hScan);
			m_scannerLoaded = false;
		}
		catch (...)
		{
		}
	}
	return S_OK;
}

void CIsisScanner::CreateDummyTopmostWindow()
{
	if (::IsWindow(m_hwnd))
	{
		::DestroyWindow(m_hwnd);
	}
	const DWORD style = WS_VISIBLE | WS_DISABLED;
	m_hwnd = ::CreateWindowEx(WS_EX_TOPMOST, nullptr, L"dummy", style, 50, 50, 100, 10, nullptr, nullptr, nullptr, nullptr);
}

HWND hwndConfig = nullptr;
LRESULT _stdcall MakeConfigDialogTopmost(int code, WPARAM wParam, LPARAM lParam)
{
	if (code == HCBT_CREATEWND)
	{
		const auto hwnd = reinterpret_cast<HWND>(wParam);
		auto pCreateWnd = reinterpret_cast<CBT_CREATEWND*>(lParam);
		if (pCreateWnd->lpcs->lpszName && !wcscmp(pCreateWnd->lpcs->lpszName, L"Configure Device Settings"))
		{
			hwndConfig = hwnd;
			pCreateWnd->lpcs->dwExStyle |= WS_EX_TOPMOST;
		}
	}
	else if (code == HCBT_ACTIVATE)
	{
		const auto hwnd = reinterpret_cast<HWND>(wParam);
		if (hwnd == hwndConfig)
		{
			SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
		}
	}
	else if (code == HCBT_DESTROYWND)
	{
		const auto hwnd = reinterpret_cast<HWND>(wParam);
		if (hwnd == hwndConfig)
		{
			hwndConfig = nullptr;
		}
	}
	return CallNextHookEx(nullptr, code, wParam, lParam);

}

void CIsisScanner::StartLookingForConfigDialog()
{
	m_hook = SetWindowsHookEx(WH_CBT, MakeConfigDialogTopmost, nullptr, GetCurrentThreadId());
}

void CIsisScanner::StopLookingForConfigDialog()
{
	if (m_hook)
	{
		UnhookWindowsHookEx(m_hook);
		m_hook = nullptr;
	}
}


