#pragma once
/*
*/
// Windows Header Files:
#include "exception.h"
#include "base_window.h"
#include "dpi.h"
#define XBYAK64
#include "xbyak.h"
// DLLのリンク
#pragma comment(lib,"d2d1.lib")
#pragma comment(lib,"winmm.lib")
#pragma comment(lib,"dwrite.lib")
#pragma comment(lib,"dwmapi.lib")

//#include "input.h"

_COM_SMARTPTR_TYPEDEF(ID2D1Factory,__uuidof(ID2D1Factory));
_COM_SMARTPTR_TYPEDEF(IWICImagingFactory, __uuidof(IWICImagingFactory));
_COM_SMARTPTR_TYPEDEF(IDWriteFactory , __uuidof(IDWriteFactory));
_COM_SMARTPTR_TYPEDEF(IDWriteGdiInterop , __uuidof(IDWriteGdiInterop));
_COM_SMARTPTR_TYPEDEF(IDWriteFontFace , __uuidof(IDWriteFontFace));
_COM_SMARTPTR_TYPEDEF(IDWriteFont , __uuidof(IDWriteFont));
_COM_SMARTPTR_TYPEDEF(IDWriteFontFamily , __uuidof(IDWriteFontFamily));
_COM_SMARTPTR_TYPEDEF(IDWriteFontCollection , __uuidof(IDWriteFontCollection));
_COM_SMARTPTR_TYPEDEF(IDWriteLocalizedStrings , __uuidof(IDWriteLocalizedStrings));
_COM_SMARTPTR_TYPEDEF(ID2D1HwndRenderTarget , __uuidof(ID2D1HwndRenderTarget));
_COM_SMARTPTR_TYPEDEF(ID2D1BitmapRenderTarget , __uuidof(ID2D1BitmapRenderTarget));
_COM_SMARTPTR_TYPEDEF(ID2D1GdiInteropRenderTarget , __uuidof(ID2D1GdiInteropRenderTarget));
_COM_SMARTPTR_TYPEDEF(ID2D1DCRenderTarget , __uuidof(ID2D1DCRenderTarget));
_COM_SMARTPTR_TYPEDEF(IDWriteTextFormat, __uuidof(IDWriteTextFormat));
_COM_SMARTPTR_TYPEDEF(IDWriteTextLayout, __uuidof(IDWriteTextLayout));
_COM_SMARTPTR_TYPEDEF(ID2D1PathGeometry , __uuidof(ID2D1PathGeometry));
_COM_SMARTPTR_TYPEDEF(ID2D1LinearGradientBrush , __uuidof(ID2D1LinearGradientBrush));
_COM_SMARTPTR_TYPEDEF(ID2D1GradientStopCollection , __uuidof(ID2D1GradientStopCollection));
_COM_SMARTPTR_TYPEDEF(ID2D1SolidColorBrush , __uuidof(ID2D1SolidColorBrush));
_COM_SMARTPTR_TYPEDEF(ID2D1BitmapBrush , __uuidof(ID2D1BitmapBrush));
_COM_SMARTPTR_TYPEDEF(ID2D1Bitmap , __uuidof(ID2D1Bitmap));
_COM_SMARTPTR_TYPEDEF(IWICBitmapDecoder,__uuidof(IWICBitmapDecoder));
_COM_SMARTPTR_TYPEDEF(IWICBitmapFrameDecode,__uuidof(IWICBitmapFrameDecode));
_COM_SMARTPTR_TYPEDEF(IWICStream,__uuidof(IWICStream));
_COM_SMARTPTR_TYPEDEF(IWICFormatConverter,__uuidof(IWICFormatConverter));
_COM_SMARTPTR_TYPEDEF(IWICBitmapScaler,__uuidof(IWICBitmapScaler));
_COM_SMARTPTR_TYPEDEF(ITaskbarList3,__uuidof(ITaskbarList3));

template <class COM_SMART_PTR > inline void safe_release(COM_SMART_PTR& ptr)
{
  if(ptr)
  {
    ptr.Release();
  }
};

namespace sf{

  template <class Exc = win32_error_exception> struct throw_if_err
  {
    void operator()(HRESULT hr) {if(hr != S_OK){throw Exc(hr);}}
  };

 /* inline template <class Exc = win32_error_exception> void throw_if_err<>()(HRESULT hr)
  {
    if(hr != S_OK){throw Exc(hr);}
  };*/


  ID2D1BitmapPtr load_bitmap_from_file(
    ID2D1HwndRenderTargetPtr render_target,
    IWICImagingFactoryPtr wic_factory,
    std::wstring uri,
    uint32_t destination_width = 0,
    uint32_t destination_height = 0
    );

  /** WNDCLASSEXラッパクラス */
  struct window_class_ex
  {
    window_class_ex(
      const wchar_t*  menu_name ,
      const std::wstring&  class_name ,
      HINSTANCE   hInstance = NULL,
      WNDPROC     lpfnWndProc = ::DefWindowProcW,
      uint32_t        style = CS_HREDRAW | CS_VREDRAW,
      boost::int32_t     cbClsExtra  = 0,
      HICON       hIcon = ::LoadIcon(NULL,IDI_APPLICATION),
      HCURSOR     hCursor = ::LoadCursor(NULL, IDC_ARROW),
      HBRUSH      hbrBackground = ::CreateSolidBrush(0xff000000),
      HICON       hIconSm = NULL
      ) : is_register_(false)
    {

      if(::GetClassInfoExW(hInstance,class_name.c_str(),&wndclass_) == 0)
      {
        if(::GetLastError() == ERROR_CLASS_DOES_NOT_EXIST)
        { 
          ::ZeroMemory(&wndclass_,sizeof(wndclass_));
          wndclass_.lpszMenuName = (LPCWSTR)menu_name;
          wndclass_.lpszClassName = class_name.c_str();
          wndclass_.cbSize = sizeof(::WNDCLASSEXW);
          wndclass_.cbWndExtra = sizeof(LONG_PTR);
          wndclass_.hInstance = hInstance;
          wndclass_.lpfnWndProc = lpfnWndProc;
          wndclass_.style = style;
          wndclass_.cbClsExtra = cbClsExtra;
          wndclass_.hIcon = hIcon;
          wndclass_.hCursor = hCursor;
          wndclass_.hbrBackground = hbrBackground;
          wndclass_.hIconSm = hIconSm;
          atom_ = ::RegisterClassExW(&wndclass_) ;
          BOOST_ASSERT(atom_ != 0);
          is_register_ = true;
        } else {
          throw win32_error_exception();
        }
      } else {
        is_register_ = false;
      }
    };

    ~window_class_ex()
    {
      if(is_register_){
        ::UnregisterClassW(wndclass_.lpszClassName,wndclass_.hInstance);
      }
    }

  private:
    bool is_register_;
    ATOM atom_;
    ::WNDCLASSEXW wndclass_;
  };

  struct get_dc {
    get_dc(HWND hwnd) : hwnd_(hwnd),hdc_(GetDC(hwnd)) {}
    HDC get(){return hdc_;}
    ~get_dc(){::ReleaseDC(hwnd_,hdc_);}
  private:
    HDC hdc_;
    HWND hwnd_;
  };

  struct compatible_dc {
    compatible_dc(HDC hdc) : hdc_(::CreateCompatibleDC(hdc)){}; 
    ~compatible_dc(){::DeleteDC(hdc_);};
    HDC get() { return hdc_;};
  private:
    HDC hdc_;
  };

  struct ref_dc {
    ref_dc(HDC& hdc) : hdc_(hdc) {};
    ~ref_dc(){};
    HDC get() { return hdc_;};
  private:
    HDC& hdc_;
  };

  struct d2_dc {
    d2_dc(ID2D1GdiInteropRenderTargetPtr& ptr,D2D1_DC_INITIALIZE_MODE mode) :hdc_(0),ptr_(ptr)
    {
      hr_ = ptr->GetDC(mode,&hdc_);
    };
    ~d2_dc(){ptr_->ReleaseDC(NULL);};
    HDC get() { return hdc_;};
  private:
    HRESULT hr_;
    HDC hdc_;
    ID2D1GdiInteropRenderTargetPtr& ptr_;
  };

  template <typename Holder>
  struct device_context
  {
    explicit device_context(Holder* holder) : holder_(holder){};
    ~device_context() {}
    operator HDC(){return holder_->get();}
  private:
    std::unique_ptr<Holder> holder_;
  };

  //struct handle_holder : boost::noncopyable
  //{
  //  explicit handle_holder(HANDLE handle) : handle_(handle) {};
  //  ~handle_holder(){if(handle_) ::CloseHandle(handle_);}
  //  operator HANDLE(){return handle_;}
  //private:
  //  HANDLE handle_;
  //};

  
  
  struct handle_deleter {
    typedef HANDLE pointer;
    void operator ()(HANDLE handle) {
        if (handle != INVALID_HANDLE_VALUE) {
            CloseHandle(handle);
        }
    }
  };

  struct HBITMAP_deleter {
    typedef HBITMAP pointer;
    void operator ()(HBITMAP handle) {
        if (handle) {
           ::DeleteObject(handle);
        }
    }
  };

  //template <typename Handle,typename Handle_Deleter>
  //struct handle_holder {
  //  typedef boost::unique_ptr<Handle,Handle_Deleter> holder_type;
  //  handle_holder(Handle handle) : holder_(handle) {}
  //  operator Handle(){return holder_->get();}
  //private:
  //  holder_type holder_;
  //};

  typedef std::unique_ptr<HANDLE,handle_deleter> handle_holder;
  typedef std::unique_ptr<HBITMAP,HBITMAP_deleter> bitmap_holder;

  typedef device_context<d2_dc> d2_dc_type;

  struct paint_struct 
  {
    paint_struct(HWND hwnd) : hwnd_(hwnd)
    {
      ::BeginPaint(hwnd,&paintstruct_);
    }
    ~paint_struct() {::EndPaint(hwnd_,&paintstruct_);}
    PAINTSTRUCT* operator->(){return &paintstruct_;}
  private:
    HWND hwnd_;
    PAINTSTRUCT paintstruct_;
  };

  // GDI オブジェクト管理テンプレート
  template <class GdiObject> 
    struct gdi_object: boost::noncopyable
  {
    explicit gdi_object(GdiObject obj) : gdiobj_(obj) {}
    ~gdi_object(){::DeleteObject(gdiobj_);}
    operator GdiObject(){return gdiobj_;}
  private:
    GdiObject gdiobj_;
  };

  //
  struct select_object 
  {
    select_object(HDC dc,HGDIOBJ o) : dc_(dc),o_(::SelectObject(dc,o)) {}
    ~select_object(){::SelectObject(dc_,o_);}
  private:
    HDC dc_;
    HGDIOBJ o_;
  };
  
  // Direct2D BeginDrawヘルパ関数
  template <typename T >
  struct begin_draw
  {
    typedef std::function<void(HRESULT hr)> err_handler_type;

    begin_draw(T& render_target,err_handler_type& handler)
      : render_target_(render_target) ,
        is_end_(false),handler_(handler)
    {render_target->BeginDraw();}

    ~begin_draw(){ 
      HRESULT hr = S_OK;
      hr = render_target_->EndDraw();
      if( hr != S_OK)
      {
        handler_(hr);
      }
    }
  private:
    T& render_target_;
    err_handler_type handler_;
  };

  struct mouse
  {
    mouse() : x_(0.0f),y_(0.0f),left_button_(false),middle_button_(false),right_button_(false){}
  private:
    float x_,y_;
    bool left_button_,middle_button_,right_button_;
  };


  /** window ベースクラス */
  struct base_win32_window : public base_window 
  {
    typedef boost::signals2::signal<LRESULT (HWND,uint32_t,WPARAM, LPARAM) > on_message_type;
    on_message_type on_message;
    
    operator HWND() const {return hwnd_;};
    
    virtual void * raw_handle() const {return hwnd_;};
//    virtual void show(uint32_t show_flag);

    virtual void show() {
      ::ShowWindow(hwnd_,SW_SHOW);
      ::GetWindowPlacement(hwnd_,&wp_);
    }

    // Window を画面から隠す
    virtual bool is_show() {
      return ( wp_.showCmd == SW_SHOWMAXIMIZED 
        || wp_.showCmd == SW_SHOWMINIMIZED
        || wp_.showCmd == SW_SHOWNORMAL );
    };

    //
    virtual void hide()
    {
      ::ShowWindow(hwnd_,SW_HIDE);
      ::GetWindowPlacement(hwnd_,&wp_);
    };

    virtual void text(std::wstring& text)
    {
      ::SetWindowTextW(*this,text.c_str());
    };

    virtual void update();

  protected:
    
    base_win32_window(
      const std::wstring& title,
      const std::wstring& name,bool fit_to_display,
      float width,float height);
    
    ~base_win32_window();

    void register_class (
      const wchar_t* menu_name,
      uint32_t style, 
      boost::int32_t     cbClsExtra  = 0,
      HICON       hIcon = ::LoadIcon(NULL,IDI_APPLICATION),
      HCURSOR     hCursor = ::LoadCursor(NULL, IDC_ARROW),
      HBRUSH      hbrBackground = ::CreateSolidBrush(0xff000000),
      HICON       hIconSm = NULL
      );		

    /** デフォルト設定 */
    void register_class();
    void create_window();

    // SetWindowLong API
    void set_long(int index,long data)
    {
      SetLastError(0);
      if(::SetWindowLongW(hwnd_,index,data) == 0)
      {
        long err = 0;
        if( (err = GetLastError()) != 0){
          SetLastError(err);
          throw sf::win32_error_exception();
        }
      };
    }

    bool invalidate_rect(bool erase = false,const RECT * rect_ptr = 0)
    {
      return ::InvalidateRect(*this,rect_ptr,erase) == TRUE;
    }

  public:
    virtual LRESULT window_proc(HWND hwnd,uint32_t message, WPARAM wParam, LPARAM lParam);
  protected:
    static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
      base_win32_window* ptr = reinterpret_cast<base_win32_window*>(hwnd);
      hwnd = ptr->hwnd_;
      return ptr->window_proc(hwnd,message,wParam,lParam);
    };

    // thisとhwndをつなぐthunkクラス
    struct hwnd_this_thunk : public Xbyak::CodeGenerator {
      hwnd_this_thunk(base_win32_window* impl,WNDPROC proc)
      {
        // rcxにhwndが格納されているので、それをimpl->hwndに保存
        mov(qword[&(impl->hwnd_)],rcx);
        // 代わりにthisのアドレスをrcxに格納
        mov(rcx,(LONG_PTR)impl);
        // r10にproc(Window プロシージャ)へのアドレスを格納
        mov(r10,(LONG_PTR)proc);
        // Window プロシージャへへジャンプ
        jmp(r10);
      }
    };

    HWND hwnd_;
    hwnd_this_thunk thunk_;
    std::wstring title_;
    std::wstring name_;
    float width_,height_;
    bool fit_to_display_;
    std::shared_ptr<sf::window_class_ex> wnd_class_;
    WNDPROC thunk_proc_;
    dpi dpi_;
    WINDOWPLACEMENT wp_;
   };
  
  struct av_mm_thread_characteristics
  {
    av_mm_thread_characteristics(std::wstring& str) : task_name_(str)
    {
      handle_ = ::AvSetMmThreadCharacteristicsW(str.c_str(),(LPDWORD)&task_index_);
    }

    bool set_priority(AVRT_PRIORITY p){return (::AvSetMmThreadPriority(handle_,p) == TRUE);}

    ~av_mm_thread_characteristics()
    {
      ::AvRevertMmThreadCharacteristics(handle_);
    }

  private:
    std::wstring task_name_;
    uint32_t task_index_;
    HANDLE handle_;
    std::wstring title_;
    std::wstring name_;
    float width_,height_;
    bool fit_to_display_;
    WINDOWPLACEMENT wp_;//
  };

  struct widget
  {
    void draw();
    float x_,y_;
  };

  typedef sf::begin_draw<ID2D1BitmapRenderTargetPtr> begin_draw_bitmap;
  typedef sf::begin_draw<ID2D1HwndRenderTargetPtr> begin_draw_hwnd;
  
}