/* Copyright (C) 2002-2003 RealVNC Ltd.  All Rights Reserved.
 *    
 * This 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 2 of the License, or
 * (at your option) any later version.
 * 
 * This software 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 software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */

// -=- WMHooks.cxx

#include <wm_hooks/wm_hooks.h>

#include <rfb_win32/WMHooks.h>
#include <rfb_win32/Service.h>
#include <rfb/Threading.h>
#include <rfb/LogWriter.h>

#include <list>

using namespace rfb;
using namespace rfb::win32;

static LogWriter vlog("WMHooks");

class WMHooksThread : public Thread {
public:
  WMHooksThread() : Thread("WMHookThread"), active(true) {}
  virtual void run();
  virtual Thread* join();
protected:
  bool active;
};

WMHooksThread* hook_mgr = 0;
std::list<WMHooks*> hooks;
std::list<WMCursorHooks*> cursor_hooks;
Mutex hook_mgr_lock;
HCURSOR hook_cursor = (HCURSOR)LoadImage(0, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED);


bool
StartHookThread() {
  if (hook_mgr) return true;
  vlog.debug("opening hook thread");
  hook_mgr = new WMHooksThread();
  if (!WM_Hooks_Install(hook_mgr->getThreadId(), 0)) {
    vlog.error("failed to initialise hooks");
    delete hook_mgr->join();
    hook_mgr = 0;
    return false;
  }
  hook_mgr->start();
  return true;
}

void
StopHookThread() {
  if (!hook_mgr) return;
  if (!hooks.empty() || !cursor_hooks.empty()) return;
  vlog.debug("closing hook thread");
  delete hook_mgr->join();
  hook_mgr = 0;
}


bool
AddHook(WMHooks* hook) {
  vlog.debug("adding hook");
  Lock l(hook_mgr_lock);
  if (!StartHookThread()) return false;
  hooks.push_back(hook);
  return true;
}

bool
AddCursorHook(WMCursorHooks* hook) {
  vlog.debug("adding cursor hook");
  Lock l(hook_mgr_lock);
  if (cursor_hooks.empty()) WM_Hooks_EnableCursorShape(TRUE);
  if (!StartHookThread()) return false;
  cursor_hooks.push_back(hook);
  return true;
}

bool
RemHook(WMHooks* hook) {
  {
    vlog.debug("removing hook");
    Lock l(hook_mgr_lock);
    hooks.remove(hook);
  }
  StopHookThread();
  return true;
}

bool
RemCursorHook(WMCursorHooks* hook) {
  {
    vlog.debug("removing cursor hook");
    Lock l(hook_mgr_lock);
    cursor_hooks.remove(hook);
  }
  StopHookThread();
  if (cursor_hooks.empty()) WM_Hooks_EnableCursorShape(FALSE);
  return true;
}

void
NotifyHooksRegion(const Region& r) {
  Lock l(hook_mgr_lock);
  std::list<WMHooks*>::iterator i;
  for (i=hooks.begin(); i!=hooks.end(); i++) {
    (*i)->new_changes.add_changed(r);
    if (!(*i)->notified) {
      (*i)->notified = true;
      PostMessage((*i)->getHandle(), WM_USER, 0, 0);
    }
  }
}

void
NotifyHooksCursor(HCURSOR c) {
  Lock l(hook_mgr_lock);
  hook_cursor = c;
}

void
WMHooksThread::run() {
  UINT windowMsg = WM_Hooks_WindowChanged();
  UINT clientAreaMsg = WM_Hooks_WindowClientAreaChanged();
  UINT borderMsg = WM_Hooks_WindowBorderChanged();
  UINT rectangleMsg = WM_Hooks_RectangleChanged();
  UINT cursorMsg = WM_Hooks_CursorChanged();
#ifdef _DEBUG
  UINT diagnosticMsg = WM_Hooks_Diagnostic();
#endif
  MSG msg;
  RECT wrect;
  HWND hwnd;
  int count = 0;

  vlog.debug("starting hook thread");

  while (active && GetMessage(&msg, NULL, 0, 0)) {
    count++;
    if (msg.message == windowMsg) {
      hwnd = (HWND) msg.lParam;
      if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
          GetWindowRect(hwnd, &wrect) && !IsRectEmpty(&wrect))
      {
        NotifyHooksRegion(Rect(wrect.left, wrect.top,
                               wrect.right, wrect.bottom));

      }
    } else if (msg.message == clientAreaMsg) {
      hwnd = (HWND) msg.lParam;
      if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
          GetClientRect(hwnd, &wrect) && !IsRectEmpty(&wrect))
      {
        POINT pt = {0,0};
        if (ClientToScreen(hwnd, &pt)) {
          NotifyHooksRegion(Rect(wrect.left+pt.x, wrect.top+pt.y,
                                 wrect.right+pt.x, wrect.bottom+pt.y));
        }
      }
    } else if (msg.message == borderMsg) {
      hwnd = (HWND) msg.lParam;
      if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
          GetWindowRect(hwnd, &wrect) && !IsRectEmpty(&wrect))
      {
        Region changed(Rect(wrect.left, wrect.top, wrect.right, wrect.bottom));
        RECT crect;
        POINT pt = {0,0};
        if (GetClientRect(hwnd, &crect) && ClientToScreen(hwnd, &pt) &&
            !IsRectEmpty(&crect))
        {
          changed.assign_subtract(Rect(crect.left+pt.x, crect.top+pt.y,
                                       crect.right+pt.x, crect.bottom+pt.y));
        }
        NotifyHooksRegion(changed);
      }
    } else if (msg.message == rectangleMsg) {
      Rect r = Rect(LOWORD(msg.wParam), HIWORD(msg.wParam),
                    LOWORD(msg.lParam), HIWORD(msg.lParam));
      if (!r.is_empty()) {
        NotifyHooksRegion(r);
      }
    } else if (msg.message == cursorMsg) {
      NotifyHooksCursor((HCURSOR)msg.lParam);
#ifdef _DEBUG
    } else if (msg.message == diagnosticMsg) {
      vlog.info("DIAG msg=%x(%d) wnd=%lx", msg.wParam, msg.wParam, msg.lParam);
#endif
    }
  }

  vlog.debug("stopping hook thread - processed %d events", count);
  WM_Hooks_Remove(getThreadId());
}

Thread*
WMHooksThread::join() {
  vlog.debug("stopping WMHooks thread");
  active = false;
  PostThreadMessage(thread_id, WM_QUIT, 0, 0);
  vlog.debug("joining WMHooks thread");
  return Thread::join();
}

// -=- WMHooks class

rfb::win32::WMHooks::WMHooks()
  : clipper(0), new_changes(true), fg_window(0),
  notified(false), MsgWindow(_T("WMHooks")) {
}

rfb::win32::WMHooks::~WMHooks() {
  RemHook(this);
  if (clipper) delete clipper;
}

LRESULT
rfb::win32::WMHooks::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) {
  switch (msg) {
  case WM_USER:
    {
      // *** Yield, to allow the triggering update event to be processed
      //     BEFORE we try to grab the resulting changes.
      // *** IMPROVES THINGS NOTICABLY ON WinXP
      Sleep(0);
      // ***

      Lock l(hook_mgr_lock);
      notified = false;
      new_changes.get_update(*clipper);
      new_changes.clear();
    }
    break;
  }
  return MsgWindow::processMessage(msg, wParam, lParam);
}

bool
rfb::win32::WMHooks::setClipRect(const Rect& r) {
  clip_region = r;
  if (clipper) clipper->set_clip_region(clip_region);
  return true;
}

bool
rfb::win32::WMHooks::setUpdateTracker(UpdateTracker* ut) {
  if (clipper) delete clipper;
  clipper = new ClippedUpdateTracker(*ut);
  clipper->set_clip_region(clip_region);
  return AddHook(this);
}

#ifdef _DEBUG
void
rfb::win32::WMHooks::setDiagnosticRange(UINT min, UINT max) {
  WM_Hooks_SetDiagnosticRange(min, max);
}
#endif


// -=- WMBlockInput class

Mutex blockMutex;
int blockCount = 0;

rfb::win32::WMBlockInput::WMBlockInput() : active(false) {
}

rfb::win32::WMBlockInput::~WMBlockInput() {
  blockInputs(false);
}

bool rfb::win32::WMBlockInput::blockInputs(bool on) {
  if (on == active) return true;
  vlog.debug("blockInput changed");
  Lock l(blockMutex);
  int newCount = blockCount;
  if (on)
    newCount++;
  else
    newCount--;
  if (WM_Hooks_EnableRealInputs(newCount==0, newCount==0)) {
    vlog.debug("set blocking to %d", newCount);
    blockCount = newCount;
    active = on;
    return true;
  }
  return false;
}


// -=- WMCursorHooks class

rfb::win32::WMCursorHooks::WMCursorHooks() {
}

rfb::win32::WMCursorHooks::~WMCursorHooks() {
  RemCursorHook(this);
}

bool
rfb::win32::WMCursorHooks::start() {
  return AddCursorHook(this);
}

HCURSOR
rfb::win32::WMCursorHooks::getCursor() const {
  return hook_cursor;
}