/*
 * TWAIN Plug-in
 * Copyright (C) 1999 Craig Setera
 * Craig Setera <setera@home.com>
 * 03/31/1999
 *
 * Updated for Mac OS X support
 * Brion Vibber <brion@pobox.com>
 * 07/22/2004
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

/*
 * Windows platform-specific code
 */

#include "config.h"

#include <libgimp/gimp.h>

#include "tw_platform.h"
#include "tw_func.h"
#include "tw_util.h"
#include "tw_local.h"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int twainMessageLoop(pTW_SESSION);
int TwainProcessMessage(LPMSG lpMsg, pTW_SESSION twSession);

extern pTW_SESSION initializeTwain ();
#ifdef _DEBUG
extern void setRunMode(char *argv[]);
#endif


#define APP_NAME "TWAIN"
#define SHOW_WINDOW 0
#define WM_TRANSFER_IMAGE (WM_USER + 100)

/* main bits */
static HWND        hwnd = NULL;
static HINSTANCE   hInst = NULL;

/* Storage for the DLL handle */
static HINSTANCE hDLL = NULL;

/* Storage for the entry point into the DSM */
static DSMENTRYPROC dsmEntryPoint = NULL;


/*
 * callDSM
 *
 * Call the specified function on the data source manager.
 */
TW_UINT16
callDSM(pTW_IDENTITY pOrigin,
	pTW_IDENTITY pDest,
	TW_UINT32    DG,
	TW_UINT16    DAT,
	TW_UINT16    MSG,
	TW_MEMREF    pData)
{
  /* Call the function */
  return (*dsmEntryPoint) (pOrigin, pDest, DG, DAT, MSG, pData);
}

/*
 * twainIsAvailable
 *
 * Return boolean indicating whether TWAIN is available
 */
int
twainIsAvailable(void)
{
  /* Already loaded? */
  if (dsmEntryPoint) {
    return TRUE;
  }

  /* Attempt to load the library */
  hDLL = LoadLibraryW (TWAIN_DLL_NAME_W);
  if (hDLL == NULL)
    return FALSE;

  /* Look up the entry point for use */
  dsmEntryPoint = (DSMENTRYPROC) GetProcAddress(hDLL, "DSM_Entry");
  if (dsmEntryPoint == NULL)
    return FALSE;

  return TRUE;
}

TW_HANDLE
twainAllocHandle (size_t size)
{
  return GlobalAlloc(GHND, size);
}

TW_MEMREF
twainLockHandle (TW_HANDLE handle)
{
  return GlobalLock (handle);
}

void
twainUnlockHandle (TW_HANDLE handle)
{
  GlobalUnlock (handle);
}

void
twainFreeHandle (TW_HANDLE handle)
{
  GlobalFree (handle);
}

gboolean
twainSetupCallback (pTW_SESSION twSession)
{
  /* Callbacks go through the window messaging system */
  return TRUE;
}

/*
 * unloadTwainLibrary
 *
 * Unload the TWAIN dynamic link library
 */
int
unloadTwainLibrary(pTW_SESSION twSession)
{
  /* Explicitly free the SM library */
  if (hDLL) {
    FreeLibrary(hDLL);
    hDLL=NULL;
  }

  /* the data source id will no longer be valid after
   * twain is killed.  If the id is left around the
   * data source can not be found or opened
	 */
  DS_IDENTITY(twSession)->Id = 0;

	/* We are now back at state 1 */
  twSession->twainState = 1;
  LogMessage("Source Manager successfully closed\n");

  return TRUE;
}

/*
 * TwainProcessMessage
 *
 * Returns TRUE if the application should process message as usual.
 * Returns FALSE if the application should skip processing of this message
 */
int
TwainProcessMessage(LPMSG lpMsg, pTW_SESSION twSession)
{
  TW_EVENT   twEvent;

  twSession->twRC = TWRC_NOTDSEVENT;

  /* Only ask Source Manager to process event if there is a Source connected. */
  if (DSM_IS_OPEN(twSession) && DS_IS_OPEN(twSession)) {
		/*
		 * A Source provides a modeless dialog box as its user interface.
		 * The following call relays Windows messages down to the Source's
		 * UI that were intended for its dialog box.  It also retrieves TWAIN
		 * messages sent from the Source to our Application.
		 */
		twEvent.pEvent = (TW_MEMREF) lpMsg;
		twSession->twRC = callDSM(APP_IDENTITY(twSession), DS_IDENTITY(twSession),
			DG_CONTROL, DAT_EVENT, MSG_PROCESSEVENT,
			(TW_MEMREF) &twEvent);

		/* Check the return code */
		if (twSession->twRC == TWRC_NOTDSEVENT) {
			return FALSE;
		}

		/* Process the message as necessary */
		processTwainMessage(twEvent.TWMessage, twSession);
  }

  /* tell the caller what happened */
  return (twSession->twRC == TWRC_DSEVENT);
}

/*
 * twainMessageLoop
 *
 * Process Win32 window messages and provide special handling
 * of TWAIN specific messages.  This loop will not exit until
 * the application exits.
 */
int
twainMessageLoop(pTW_SESSION twSession)
{
  MSG msg;

  while (GetMessage(&msg, NULL, 0, 0)) {
    if (DS_IS_CLOSED(twSession) || !TwainProcessMessage(&msg, twSession)) {
      TranslateMessage((LPMSG)&msg);
      DispatchMessage(&msg);
    }
  }

  return msg.wParam;
}

/*
 * LogLastWinError
 *
 * Log the last Windows error as returned by
 * GetLastError.
 */
void
LogLastWinError(void)
{
	LPVOID lpMsgBuf;

	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_FROM_SYSTEM |
		FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */
		(LPTSTR) &lpMsgBuf,
		0,
		NULL
		);

	LogMessage("%s\n", lpMsgBuf);

	/* Free the buffer. */
	LocalFree( lpMsgBuf );
}

void twainQuitApplication (void)
{
  PostQuitMessage (0);
}


/******************************************************************
 * Win32 setup...
 ******************************************************************/

/*
 * InitApplication
 *
 * Initialize window data and register the window class
 */
BOOL
InitApplication(HINSTANCE hInstance)
{
  WNDCLASS wc;
  BOOL retValue;

  /*
   * Fill in window class structure with parameters to describe
   * the main window.
   */
  wc.style = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc = (WNDPROC) WndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
  wc.lpszClassName = APP_NAME;
  wc.lpszMenuName = NULL;

  /* Register the window class and stash success/failure code. */
  retValue = RegisterClass(&wc);

  /* Log error */
  if (!retValue)
    LogLastWinError();

  return retValue;
}

/*
 * InitInstance
 *
 * Create the main window for the application.  Used to
 * interface with the TWAIN datasource.
 */
BOOL
InitInstance(HINSTANCE hInstance, int nCmdShow, pTW_SESSION twSession)
{
  /* Create our window */
  hwnd = CreateWindow(APP_NAME, APP_NAME, WS_OVERLAPPEDWINDOW,
		      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
		      NULL, NULL, hInstance, NULL);

  if (!hwnd) {
    return (FALSE);
  }

  /* Register our window handle with the TWAIN
   * support.
   */
  registerWindowHandle(twSession, hwnd);

  /* Schedule the image transfer by posting a message */
  PostMessage(hwnd, WM_TRANSFER_IMAGE, 0, 0);

  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);

  return TRUE;
}

/*
 * twainWinMain
 *
 * This is the function that represents the code that
 * would normally reside in WinMain (see above).  This
 * function will get called during run() in order to set
 * up the windowing environment necessary for TWAIN to
 * operate.
 */
int
twainMain (void)
{
  /* Initialize the twain information */
  pTW_SESSION twSession = initializeTwain();

  /* Since we are not using our own WinMain anymore where we
     could get hInst we get it here using GetModuleHandleW. */
  if (!hInst)
    hInst = GetModuleHandleW(NULL);

  /* Perform instance initialization */
  if (!InitApplication(hInst))
    return (FALSE);

  /* Perform application initialization */
  if (!InitInstance(hInst, SHOW_WINDOW, twSession))
    return (FALSE);

  /*
   * Call the main message processing loop...
   * This call will not return until the application
   * exits.
   */
  return twainMessageLoop(twSession);
}

/*
 * WndProc
 *
 * Process window message for the main window.
 */
LRESULT CALLBACK
WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message) {

  case WM_TRANSFER_IMAGE:
    /* Get an image */
    scanImage ();
    break;

  case WM_DESTROY:
    LogMessage("Exiting application\n");
    PostQuitMessage(0);
    break;

  default:
    return (DefWindowProc(hWnd, message, wParam, lParam));
  }
  return 0;
}

