// Copyright (C) 2010, Guy Barrand. All rights reserved.
// See the file tools.license for terms.

#ifndef toolx_Windows_window
#define toolx_Windows_window

#include <windows.h>
#include <windowsx.h>
#include <string>

namespace toolx {
namespace Windows {

class window {
  static const std::string& s_class() {
    static const std::string s_v("toolx::Windows::window");
    return s_v;
  }
  static void register_class(){
    static bool s_done = false; //not const, then not thread safe.
    if(!s_done) {
      WNDCLASS         wc;
      wc.style         = CS_HREDRAW | CS_VREDRAW;
      wc.lpfnWndProc   = (WNDPROC)proc;
      wc.cbClsExtra    = 0;
      wc.cbWndExtra    = 0;
      wc.hInstance     = ::GetModuleHandle(NULL);
      wc.hIcon         = LoadIcon(NULL,IDI_APPLICATION);
      wc.hCursor       = LoadCursor(NULL,IDC_ARROW);
      wc.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);
      wc.lpszMenuName  = (PTSTR)s_class().c_str();
      wc.lpszClassName = (PTSTR)s_class().c_str();
      ::RegisterClass(&wc);
      s_done = true;
    }
  }
public:
  virtual void key_up(){}
  virtual void key_down(){}
  virtual void key_left(){}
  virtual void key_right(){}
  virtual void key_escape(){}
  virtual void close(){
    ::PostQuitMessage(0);
  }
public:
  window(const char* a_title,int a_x,int a_y,unsigned int a_w,unsigned int a_h,unsigned int a_mask = WS_OVERLAPPEDWINDOW)
  :m_hwnd(0)
  ,m_key_shift(false)
  ,m_key_ctrl(false)
  ,m_focus_hwnd(0)
  {
    // WARNING : given a_w,a_h may not be the client area because of various decorations.
    //           See set_client_area_size() method to enforce a client area size.
    register_class();
    m_hwnd = ::CreateWindow((PTSTR)s_class().c_str(),
                              NULL,
                              a_mask,
                              a_x,a_y,
                              a_w,a_h,
                              NULL,NULL,
                              ::GetModuleHandle(NULL),
                              NULL);
    if(!m_hwnd) return;
    ::SetWindowText(m_hwnd,(PTSTR)a_title);
    ::SetWindowLongPtr(m_hwnd,GWLP_USERDATA,LONG_PTR(this));
  }
  virtual ~window(){
    if(m_hwnd) {
      ::SetWindowLongPtr(m_hwnd,GWLP_USERDATA,LONG_PTR(NULL));
      ::DestroyWindow(m_hwnd);
      m_hwnd = 0;
    }
  }
protected:
  window(const window& a_from)
  :m_hwnd(0)
  ,m_key_shift(a_from.m_key_shift)
  ,m_key_ctrl(a_from.m_key_ctrl)
  ,m_focus_hwnd(0)
  { 
  }
  window& operator=(const window& a_from){
    m_key_shift = a_from.m_key_shift;
    m_key_ctrl = a_from.m_key_ctrl;
    return *this;
  }
public:
  const window& get_me() const {return *this;}
  window& get_me() {return *this;}
  bool key_shift() const {return m_key_shift;}
  HWND hwnd() const {return m_hwnd;}

  void set_client_area_size(unsigned int a_w,unsigned int a_h) {
    if(!m_hwnd) return;
    RECT wrect;
    ::GetWindowRect(m_hwnd,&wrect);
    unsigned int ww = wrect.right-wrect.left;
    unsigned int wh = wrect.bottom-wrect.top;

    RECT carect;
    ::GetClientRect(m_hwnd,&carect);
    unsigned int cw = carect.right-carect.left;
    unsigned int ch = carect.bottom-carect.top;

    unsigned int woffset = ww-cw;
    unsigned int hoffset = wh-ch;

    ::MoveWindow(m_hwnd,wrect.left,wrect.top,a_w+woffset,a_h+hoffset,TRUE);
  }
  void move(unsigned int a_x,unsigned int a_y) {
    if(!m_hwnd) return;
    RECT rect;
    ::GetWindowRect(m_hwnd,&rect);
    unsigned int w = rect.right-rect.left;
    unsigned int h = rect.bottom-rect.top;
    ::MoveWindow(m_hwnd,a_x,a_y,w,h,TRUE);
  }
  
  void set_focus_hwnd(HWND a_hwnd) {m_focus_hwnd = a_hwnd;}
  HWND focus_hwnd() const {return m_focus_hwnd;}
protected:
  static LRESULT CALLBACK proc(HWND a_hwnd,UINT a_msg,WPARAM a_wparam,LPARAM a_lparam) {
    switch (a_msg) {
    // Else :
    case WM_SIZE:{ // Assume one child window ! FIXME : have a message if not.
      int width = LOWORD(a_lparam);
      int height = HIWORD(a_lparam);
      HWND hwnd = GetFirstChild(a_hwnd);
      if(hwnd) {
        ::MoveWindow(hwnd,0,0,width,height,TRUE);
      }
    }return 0;

    case WM_SETFOCUS:{
      window* _this = (window*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA);
      if(_this) {
        HWND hwnd = _this->focus_hwnd();
	if(hwnd) ::SetFocus(hwnd);
      }
    }return 0;
    
    case WM_KEYDOWN:{
      switch(a_wparam){
      case VK_SHIFT:{
        window* _this = (window*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA);
        if(_this) _this->m_key_shift = true;
        return 0;
      }
      case VK_CONTROL:{
        window* _this = (window*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA);
        if(_this) _this->m_key_ctrl = true;
        return 0;
      }
      case VK_UP:{
        window* _this = (window*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA);
        if(_this) _this->key_up();
        return 0;
      }
      case VK_DOWN:{
        window* _this = (window*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA);
        if(_this) _this->key_down();
        return 0;
      }
      case VK_LEFT:{
        window* _this = (window*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA);
        if(_this) _this->key_left();
        return 0;
      }
      case VK_RIGHT:{
        window* _this = (window*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA);
        if(_this) _this->key_right();
        return 0;
      }
      case VK_ESCAPE:{
        window* _this = (window*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA);
        if(_this) _this->key_escape();
        return 0;
      }
      } //end switch(a_wparam)
      break;
    }
    case WM_KEYUP:{
      switch(a_wparam){
      case VK_SHIFT:{
        window* _this = (window*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA);
        if(_this) _this->m_key_shift = false;
        return 0;
      }
      case VK_CONTROL:{
        window* _this = (window*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA);
        if(_this) _this->m_key_ctrl = false;
        return 0;
      }
      } //end switch(a_wparam)
      break;
    }

    case WM_CLOSE:{
      window* _this = (window*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA);
      if(_this) _this->close();
    }break; //NOTE : can't be return 0.
    case WM_DESTROY:wm__destroy(a_hwnd);return 0;
    }
    return (DefWindowProc(a_hwnd,a_msg,a_wparam,a_lparam));
  }
  static void wm__destroy(HWND a_hwnd) {
    window* _this = (window*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA);
    if(_this) { //How to be sure that we have a window* ???
      if(_this->m_hwnd!=a_hwnd) {
      }
      _this->m_hwnd = 0;
    }
    ::SetWindowLongPtr(a_hwnd,GWLP_USERDATA,LONG_PTR(NULL));
  }
protected:
  void get_size(HWND a_hwnd,int& a_w,int& a_h){
    RECT rect;
    ::GetWindowRect(a_hwnd,&rect);
    a_w = rect.right-rect.left;
    a_h = rect.bottom-rect.top;
  }
protected:
  HWND m_hwnd;
  bool m_key_shift;
  bool m_key_ctrl;
  HWND m_focus_hwnd;
};

}}

#endif
