/*
   win32pbs.m

   GNUstep pasteboard server - Win32 extension

   Copyright (C) 2003 Free Software Foundation, Inc.

   Author: Fred Kiefer <fredkiefer@gmx.de>
   Date: December 2003

   This file is part of the GNUstep Project

   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 library; see the file COPYING.
   If not, see <http://www.gnu.org/licenses/> or write to the 
   Free Software Foundation, 51 Franklin Street, Fifth Floor, 
   Boston, MA 02110-1301, USA.
*/

/* Access Windows 2000 (and later) API.  Required for HWND_MESSAGE.  */
#define WINVER 0x500

#include <Foundation/Foundation.h>
#include <Foundation/NSUserDefaults.h>
#include <AppKit/NSPasteboard.h>

#include <windows.h>

#ifdef __CYGWIN__
#include <sys/file.h>
#endif

// The Windows SDK declares BOOL as an int.  Objective C defines BOOl as a char.
// Those two types clash.  MinGW's implementation of the Windows SDK uses the WINBOOL
// type to avoid this clash.  When compiling natively on Windows, we need to manually
// define WINBOOL.
// MinGW will define _DEF_WINBOOL_ if it has defined WINBOOL so we can use the same trick
// here.
// See https://github.com/mingw-w64/mingw-w64/blob/master/mingw-w64-headers/include/ntdef.h#L355
#ifndef _DEF_WINBOOL_
#define _DEF_WINBOOL_
typedef int WINBOOL;
#endif

@interface Win32PbOwner : NSObject
{
  NSPasteboard	*_pb;
  HINSTANCE _hinstance;
  HWND _hwnd;
  BOOL _ignore;
}

- (id) initWithOSPb: (NSPasteboard*) ospb;
- (void) clipboardHasData;
- (void) setClipboardData;
- (void) grapClipboard;
- (void) setupRunLoopInputSourcesForMode: (NSString*)mode;

@end

static Win32PbOwner *wpb = nil;
static HWND hwndNextViewer = NULL;
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg,
                             WPARAM wParam, LPARAM lParam);


@implementation Win32PbOwner

+ (BOOL) initializePasteboard
{
  if (self == [Win32PbOwner class])
    {
      wpb = [[Win32PbOwner alloc] initWithOSPb: 
                                    [NSPasteboard generalPasteboard]];
      [wpb clipboardHasData];
    }
  return YES;
}

+ (id) ownerByOsPb: (NSString*)p
{
  if ([p isEqual: [[NSPasteboard generalPasteboard] name]])
    {
      return wpb;
    }
  else
    {
      return nil;
    }
}

- (id) initWithOSPb: (NSPasteboard*) ospb
{
  WNDCLASSEX wc; 

  _ignore = NO;  
  _hinstance = (HINSTANCE)GetModuleHandle(NULL);

  // Register the main window class. 
  wc.cbSize = sizeof(wc);          
  //wc.style = CS_OWNDC;
  wc.style = CS_HREDRAW | CS_VREDRAW; 
  wc.lpfnWndProc = (WNDPROC) MainWndProc; 
  wc.cbClsExtra = 0; 
  wc.cbWndExtra = 0; 
  wc.hInstance = _hinstance; 
  wc.hIcon = NULL;
  wc.hCursor = NULL;
  wc.hbrBackground = NULL; 
  wc.lpszMenuName =  NULL; 
  wc.lpszClassName = "GNUstepClipboardClass"; 
  wc.hIconSm = NULL;

  if (RegisterClassEx(&wc)) 
    {
      _hwnd = CreateWindowEx(0, "GNUstepClipboardClass", "GNUstepClipboard", 
                             0, 0, 0, 10, 10, 
                             HWND_MESSAGE, (HMENU)NULL, _hinstance, NULL); 
    }

  ASSIGN(_pb, ospb);
  [self setupRunLoopInputSourcesForMode: NSDefaultRunLoopMode]; 

  return self;  
}

- (void) dealloc
{
  RELEASE(_pb);
  DestroyWindow(_hwnd);
  UnregisterClass("GNUstepClipboardClass", _hinstance);
  [super dealloc];
}

- (void) clipboardHasString
{
  if ((_hwnd != GetClipboardOwner()) &&
      IsClipboardFormatAvailable(CF_UNICODETEXT)) 
    {
      [_pb declareTypes: [NSArray arrayWithObject: NSStringPboardType]
           owner: self];
    }
}

/* 
   The owner of the Windows clipboard did change. Check if this 
   results in some action for us.
 */
- (void) clipboardHasData
{
  if (!_ignore)
    {
      _ignore = YES;
      [self clipboardHasString];
      _ignore = NO;
    }
}

- (void) setClipboardString
{
  HGLOBAL hglb; 
  LPWSTR lpwstr; 
  NSString *s;
  unsigned int len;

  s = [_pb stringForType: NSStringPboardType];
  if (s == nil)
    {
      return;
    }

  len = [s length];
  hglb = GlobalAlloc(GMEM_MOVEABLE, (len + 1) * sizeof(WCHAR)); 
  if (hglb == NULL) 
    { 
      return; 
    } 
  
  // Lock the handle and copy the text to the buffer. 
  lpwstr = GlobalLock(hglb); 
  [s getCharacters: lpwstr];
  lpwstr[len] = (WCHAR)0;
  GlobalUnlock(hglb); 
  SetClipboardData(CF_UNICODETEXT, hglb);
}

/* 
   Data is requested from the Windows clipboard. We are already the 
   owner of the clipboard.
 */
- (void) setClipboardData
{
  if (!_ignore)
    {
      _ignore = YES;  
      [self setClipboardString];
      _ignore = NO;;  
    }
}

/* 
   Take over the ownership of the Windows clipboard, but don't provide data 
 */
- (void) grapClipboard
{
  if (!OpenClipboard(_hwnd)) 
     {
       NSLog(@"Failed to get the Win32 clipboard. %d", GetLastError());
       return; 
     }
  if (!EmptyClipboard())
    {
      NSLog(@"Failed to get the Win32 clipboard. %d", GetLastError());
      CloseClipboard();
      return;
    }
  SetClipboardData(CF_UNICODETEXT, NULL);

  CloseClipboard();
}

/*
 * If this gets called, a GNUstep object has grabbed the pasteboard
 * or has changed the types of data available from the pasteboard
 * so we must tell the Windows system, that we have the current selection.
 */
- (void) pasteboardChangedOwner: (NSPasteboard*)sender
{
  if (!_ignore)
    {
      _ignore = YES;  
      [self grapClipboard];
      _ignore = NO;;  
    }
}

- (void) provideStringTo: (NSPasteboard*)pb
{
  HGLOBAL hglb; 
 
  if (!IsClipboardFormatAvailable(CF_UNICODETEXT) || 
      !OpenClipboard(_hwnd)) 
    {
      return; 
    }
  
  hglb = GetClipboardData(CF_UNICODETEXT); 
  if (hglb != NULL) 
    { 
      LPWSTR lpwstr; 
      
      lpwstr = GlobalLock(hglb); 
      if (lpwstr != NULL) 
        {
          unsigned int len;
          NSString *s;
          
          len = lstrlenW(lpwstr);
          s = [NSString stringWithCharacters: lpwstr 
                        length: len]; 
          [pb setString: s forType: NSStringPboardType];
          GlobalUnlock(hglb); 
        } 
    } 
  CloseClipboard(); 
}

- (void) pasteboard: (NSPasteboard*)pb provideDataForType: (NSString*)type
{
  if (!_ignore)
    {
      if ([type isEqual: NSStringPboardType])
        {
          _ignore = YES;  
          [self provideStringTo: pb];
          _ignore = NO;;  
        }
    }
}

- (void) callback: (id) sender
{
  MSG msg;
  WINBOOL bRet; 

  while ((bRet = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) != 0)
    { 

      if (msg.message == WM_QUIT)
        {
          // Exit the program
          return;
        }
      if (bRet == -1)
        {
          // handle the error and possibly exit
        }
      else
        {
          // Don't translate messages, as this would give extra character messages.
          DispatchMessage(&msg); 
        } 
    } 
}

- (void) receivedEvent: (void*)data
                  type: (RunLoopEventType)type
                 extra: (void*)extra
               forMode: (NSString*)mode
{
#ifdef    __CYGWIN__
  if (type == ET_RDESC)
#else 
  if (type == ET_WINMSG)
#endif
    {
      MSG *m = (MSG*)extra;

      if (m->message == WM_QUIT)
        {
          //[NSApp terminate: nil];
          // Exit the program
          return;
        }
      else
        {
          DispatchMessage(m); 
        } 
    } 
  if (mode != nil)
    [self callback: mode];
}

- (void) setupRunLoopInputSourcesForMode: (NSString*)mode
{
  NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];

#ifdef    __CYGWIN__
  int fdMessageQueue;
#define WIN_MSG_QUEUE_FNAME    "/dev/windows"

  // Open a file descriptor for the windows message queue
  fdMessageQueue = open (WIN_MSG_QUEUE_FNAME, O_RDONLY);
  if (fdMessageQueue == -1)
    {
      NSLog(@"Failed opening %s\n", WIN_MSG_QUEUE_FNAME);
      exit(1);
    }
  [currentRunLoop addEvent: (void*)fdMessageQueue
                  type: ET_RDESC
                  watcher: (id<RunLoopEvents>)self
                  forMode: mode];
#else 
  [currentRunLoop addEvent: (void*)0
                  type: ET_WINMSG
                  watcher: (id<RunLoopEvents>)self
                  forMode: mode];
#endif
}

@end

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, 
                             WPARAM wParam, LPARAM lParam) 
{ 
  switch (uMsg) 
    { 
    case WM_CREATE: 
      // Add the window to the clipboard viewer chain. 
      hwndNextViewer = SetClipboardViewer(hwnd); 
      break;
      
    case WM_CHANGECBCHAIN: 
      // If the next window is closing, repair the chain. 
      if ((HWND) wParam == hwndNextViewer) 
        hwndNextViewer = (HWND) lParam; 
      // Otherwise, pass the message to the next link. 
      else if (hwndNextViewer != NULL) 
        SendMessage(hwndNextViewer, uMsg, wParam, lParam); 
      break;

    case WM_DESTROY: 
      ChangeClipboardChain(hwnd, hwndNextViewer); 
      PostQuitMessage(0); 
      break;

    case WM_DRAWCLIPBOARD:
      // clipboard contents changed. 
      if (wpb != nil)
        [wpb clipboardHasData];

      // Pass the message to the next window in clipboard 
      // viewer chain. 
      if (hwndNextViewer != NULL) 
        SendMessage(hwndNextViewer, uMsg, wParam, lParam); 
      break; 

    case WM_RENDERFORMAT: 
      [wpb setClipboardData];
      break; 
 
    case WM_RENDERALLFORMATS:
      if (!OpenClipboard(hwnd))
	{
	  NSWarnMLog(@"Failed to get the Win32 clipboard. %d", GetLastError());
	}
      else if (GetClipboardOwner() == hwnd)
	{
	  if (!EmptyClipboard())
	    {
	      NSWarnMLog(@"Failed to get the Win32 clipboard. %d", GetLastError());
	    }
	  else
	    {
	      SendMessage(hwnd, WM_RENDERFORMAT, CF_UNICODETEXT, 0);
	      CloseClipboard();
	    }
	}
      break;
      
    default:
      return DefWindowProc(hwnd, uMsg, wParam, lParam);
    } 

  return (LRESULT) NULL; 
}

