#include "stdafx.h"
#include "resource.h"
#define BOOST_ASSIGN_MAX_PARAMS 7
#include <boost/assign.hpp>
#include <boost/assign/ptr_list_of.hpp>
#include <boost/assign/ptr_list_inserter.hpp>
#include <boost/foreach.hpp>

#if _DEBUG
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

#include "sf_windows.h"
#include "toplevel_window.h"
#include "icon.h"
#include "timer.h"
#include "exception.h"

#define THROW_IFERR(hres) \
  if (FAILED(hres)) { throw sf::win32_error_exception(hres); }

#ifndef HINST_THISCOMPONENT
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase)
#endif

namespace sf 
{

HRESULT EnableBlurBehind(HWND hwnd)
{
  HRESULT hr = S_OK;

  //Create and populate the BlurBehind structre
  DWM_BLURBEHIND bb = {0};
  //Enable Blur Behind and Blur Region;
  bb.dwFlags = DWM_BB_ENABLE;
  bb.fEnable = true;
  bb.hRgnBlur = NULL;

  //Enable Blur Behind
  hr = DwmEnableBlurBehindWindow(hwnd, &bb);
  if (SUCCEEDED(hr))
  {
    //do more things
  }
  return hr;
}
 
struct toplevel_window::impl : public base_win32_window
{

  impl(const std::wstring& menu_name,const std::wstring& name,bool fit_to_display,float width = 160,float height = 100) 
    : base_win32_window(menu_name,name,fit_to_display,width,height) , timer_(*this,1000),wm_task_bar_create_(0),result_time_(INTERVAL_SEC1),status_(active)
  {
    on_render.connect(boost::bind(&impl::render,this));
  };

  ~impl(){
    safe_release(factory_);
    safe_release(write_factory_);
  };



  // -------------------------------------------------------------------
  // ウィンドウプロシージャ
  // -------------------------------------------------------------------
  LRESULT window_proc(HWND hwnd,uint32_t message, WPARAM wParam, LPARAM lParam){
    switch (message)
    {
    case WM_CREATE:
      {
        // TODO:
        create_device();
        wm_task_bar_create_ = taskbar::register_message();
        text(std::wstring(L"ミニタイマー(活動中)"));
        //MARGINS mgn = {-1,0,0,0};//let,right,top,bottom
        //HRESULT hr = DwmExtendFrameIntoClientArea(hwnd_, &mgn);
      }
      break;
    case WM_SIZE:
      {
        //if (render_target_)
        //{
        //	D2D1_SIZE_U size;
        //	size.width = lParam & 0xFFFF;
        //	size.height = (lParam >> 16) & 0xFFFF; ;

        //	// Note: This method can fail, but it's okay to ignore the
        //	// error here -- it will be repeated on the next call to
        //	// EndDraw.
        //	render_target_->Resize(size);
        //}
      }
      break;
    case WM_PAINT:
      {
        //create_device();

        { 
          paint_struct begin_paint(hwnd);
          //CloseHandle(cb);
          // 描画コードの呼び出し
          render();

        }

//        ::ShowWindow(hwnd_,SW_MINIMIZE);
      }
      return FALSE;
    case WM_DISPLAYCHANGE:
      {
        invalidate_rect();
      }
      break;
    case WM_ERASEBKGND:
      {
//        return FALSE;
      }
      break;
    case WM_MOUSEMOVE:
      {
        //					on_mouse_move(GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam),wParam); 
      }
      break;
    case WM_LBUTTONDOWN:
      {
      }
      break;
    case WM_TIMER:
      {
        switch (status_)
        {
        case active:
          {
            --result_time_;
            taskbar_.progress_value(*this,result_time_,INTERVAL_SEC1);

            if(result_time_ == 0)
            {
              update_status(sleep);
            } else {
              update_icon();
              invalidate_rect();
            }
          }
          break;
        case sleep:
          {

            --result_time_;
            taskbar_.progress_value(*this,result_time_,INTERVAL_SEC2);
            if(result_time_ == 0)
            {
              update_status(active);
            } else {
              update_icon();
              invalidate_rect();
            }
          }
          break;
        }
      }
    case WM_DWMCOMPOSITIONCHANGED:
      {
        //MARGINS mgn = {-1,0,0,0};//let,right,top,bottom
        //HRESULT hr = DwmExtendFrameIntoClientArea(hwnd_, &mgn);
      }
      break;
    case WM_COMMAND:
      if(HIWORD(wParam) == THBN_CLICKED ){
        switch(LOWORD(wParam)){
        case THUMB_START:
          {
            if(status_ != stop)
            {
              update_status(stop);
            } else {
              update_status(active);
            }
          }
          break;
        }
      }
      break;
        //::SetTimer(hwnd_,(UINT_PTR)&timer_id_,1000,NULL);
    }

    if(message == wm_task_bar_create_)
    {
      create_task_bar();
      // タイマーの設定
      //timer_.reset(new sf::timer(*this,1000));
      timer_.start();
//      ::SetTimer(hwnd_,(UINT_PTR)&timer_id_,1000,NULL);
    }

    if(message == WM_CLOSE)
    {
      timer_.stop();
      //::KillTimer(hwnd_,(UINT_PTR)&timer_id_);
      //
      taskbar_.discard();
      // 後始末
      discard_device();
      // レンダーターゲットのリリース
      safe_release(dcr_);
      safe_release(render_target_);
      // Windowの破棄
      BOOL ret(::DestroyWindow(hwnd));
      BOOST_ASSERT(ret != 0);
    }

    if(message == WM_DESTROY)
    {
      ::PostQuitMessage(0);
      return 0;
    }

    return ::DefWindowProcW(hwnd,message,wParam,lParam);
  }
  
  virtual void create(){
    create_device_independent_resources();
    icon_ = create_icon();
    register_class(this->name_.c_str(),CS_HREDRAW | CS_VREDRAW,0,icon_.get());
    create_window();

    // 半透明ウィンドウを有効にする。
    BOOL dwmEnable;
    DwmIsCompositionEnabled (&dwmEnable); 
    if (dwmEnable) EnableBlurBehind(*this);

  };

  virtual void discard_device()
  {
    safe_release(render_target_);
  }

  virtual void create_device(){
    //		入力_.reset(new input(HINST_THISCOMPONENT,hwnd_));
    HRESULT hr = S_OK;

    //ウィンドウの現在の幅、高さを求める
    RECT rc;
    GetClientRect( hwnd_, &rc );
    uint32_t width = rc.right - rc.left;
    uint32_t height = rc.bottom - rc.top;

    {
      //wic_imaging_factory_.CreateInstance(CLSID_WICImagingFactory);
      //			bitmap_ = load_bitmap_from_file(render_target_,wic_imaging_factory_,L"myship.png");
    }

    if(!render_target_)
    {
      RECT rc;
      GetClientRect(hwnd_, &rc);

      D2D1_SIZE_U size = D2D1::SizeU(
        rc.right - rc.left,
        rc.bottom - rc.top
        );
      
      const D2D1_PIXEL_FORMAT format =
          D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,
                            D2D1_ALPHA_MODE_PREMULTIPLIED);
      
      const D2D1_RENDER_TARGET_PROPERTIES target_prop = 
          D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT,format);

      THROW_IFERR(factory_->CreateHwndRenderTarget(
        target_prop,
        D2D1::HwndRenderTargetProperties(hwnd_, size,D2D1_PRESENT_OPTIONS_IMMEDIATELY),
        &render_target_
        ));
      // Create a DC render target 
      //D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
      //	D2D1_RENDER_TARGET_TYPE_DEFAULT,
      //	D2D1::PixelFormat(
      //		DXGI_FORMAT_B8G8R8A8_UNORM,
      //		D2D1_ALPHA_MODE_IGNORE
      //		) , 0.0, 0.0,
      //	D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE
      //	);

      //THROW_IFERR(factory_->CreateDCRenderTarget(
      //	&props,
      //	&render_target_
      //	));
    }
  }

  virtual void create_device_independent_resources(){
    // Direct2DFactory の生成

    if(!factory_){
#if defined(DEBUG) || defined(_DEBUG)
      D2D1_FACTORY_OPTIONS options;
      options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION ;
      THROW_IFERR(D2D1CreateFactory(
        D2D1_FACTORY_TYPE_SINGLE_THREADED,
        options,
        &factory_
        ));
#else
      THROW_IFERR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &factory_));
#endif

    }

    if(!write_factory_){
      THROW_IFERR(::DWriteCreateFactory(
        DWRITE_FACTORY_TYPE_SHARED,
        __uuidof(IDWriteFactory),
        reinterpret_cast<IUnknown**>(&write_factory_)
        ));
    }


    //wic_imaging_factory_.CreateInstance(CLSID_WICImagingFactory);

    //thunk_proc_ = (WNDPROC)thunk_.getCode();
    layout_rect_ = D2D1::RectF(0.0f,0.0f,width_,height_);
    // Text Formatの作成
    THROW_IFERR(write_factory_->CreateTextFormat(
    L"メイリオ",                // Font family name.
    NULL,                       // Font collection (NULL sets it to use the system font collection).
    DWRITE_FONT_WEIGHT_REGULAR,
    DWRITE_FONT_STYLE_NORMAL,
    DWRITE_FONT_STRETCH_NORMAL,
    24.0f,
    L"ja-jp",
    &write_text_format_
    ));

  }

  void render(){

    static int t = 0;

    if (render_target_)
    {
      ++t;
      // Retrieve the size of the render target.
      D2D1_SIZE_F renderTargetSize = render_target_->GetSize();
      try {
        render_target_->BeginDraw();
        if(status_ == sleep && (t & 0x1) == 0)
        {
          render_target_->Clear(D2D1::ColorF(D2D1::ColorF::Red,0.5f));
        } else {
          render_target_->Clear(D2D1::ColorF(0,0.0f));
        }
//        render_target_->FillRectangle(D2D1::RectF(0.0f,0.0f,renderTargetSize.width,renderTargetSize.height),);
         
//          (D2D1::ColorF(0,1.0f));
        render_target_->SetTransform(D2D1::Matrix3x2F::Identity());
        ID2D1SolidColorBrushPtr brush;
        render_target_->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::OrangeRed), &brush);

        std::wstring m((boost::wformat(L"残り%d分%d秒") % (result_time_ / 60) % (result_time_ % 60)).str());
        render_target_->DrawTextW(
          m.c_str(),
          m.size(),
          write_text_format_,
          layout_rect_, 
          brush);
        THROW_IFERR(render_target_->EndDraw());

      } catch(sf::win32_error_exception& err)
      {
        if(err.hresult() == D2DERR_RECREATE_TARGET)
        {
          discard_device();
          create_device();
        } else {
          throw;
        }
      } catch(...) {
        throw;
      }
    }
  }
  void create_window()
  {

    // Windowを作成する
    CreateWindowEx(
      WS_EX_APPWINDOW | WS_EX_TOPMOST,
      name_.c_str(),
      title_.c_str(),
      0 ,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      static_cast<uint32_t>(dpi_.scale_x(width_)),
      static_cast<uint32_t>(dpi_.scale_x(height_)),
      NULL,
      NULL,
      HINST_THISCOMPONENT,
      this
      );
  };

private:
  // 互換ビットマップにDirect2Dで描画し、
  // アイコンにして返すメソッド
  icon create_icon()
  {
    // アイコンサイズの取得
    icon_size_ = D2D1::SizeU(::GetSystemMetrics(SM_CXICON),::GetSystemMetrics(SM_CYICON));
    // ビットマップヘッダのセットアップ
    BITMAPV5HEADER bi = {0};
    bi.bV5Size           = sizeof(BITMAPV5HEADER);
    bi.bV5Width           = icon_size_.width;
    bi.bV5Height          = icon_size_.height;
    bi.bV5Planes = 1;
    bi.bV5BitCount = 32;
    bi.bV5Compression = BI_BITFIELDS;
    bi.bV5RedMask   =  0x00FF0000;
    bi.bV5GreenMask =  0x0000FF00;
    bi.bV5BlueMask  =  0x000000FF;
    bi.bV5AlphaMask =  0xFF000000; 

    // デスクトップHDCの取得
    {
      get_dc dc(NULL);

      // DIBセクションの作成
      bmp_.reset(
        ::CreateDIBSection(
          dc.get(),reinterpret_cast<BITMAPINFO *>(&bi),DIB_RGB_COLORS,0,NULL,0));
      { 

        // DC互換レンダーターゲットのセットアップ
        D2D1_RENDER_TARGET_PROPERTIES 
          props = D2D1::RenderTargetProperties(
          D2D1_RENDER_TARGET_TYPE_DEFAULT,
          D2D1::PixelFormat(
              DXGI_FORMAT_B8G8R8A8_UNORM,
              D2D1_ALPHA_MODE_PREMULTIPLIED),
          0,
          0,
          D2D1_RENDER_TARGET_USAGE_NONE,
          D2D1_FEATURE_LEVEL_DEFAULT
        );

        throw_if_err<>()(factory_->CreateDCRenderTarget(&props,&dcr_));
      }
    }

    draw_icon();
    return std::move(icon(bmp_,icon_size_.width,icon_size_.height));
  };
  void update_icon()
  {
    draw_icon();
    icon_ = icon(bmp_,icon_size_.width,icon_size_.height);
    icon_holder ic_backup((HICON)::SetClassLongPtrW(hwnd_,GCLP_HICON,(LONG_PTR)icon_.get()));
  }

  void update_status(uint32_t s)
  {
    switch(s)
    {
    case stop:
      status_ = stop;
      result_time_ = INTERVAL_SEC1;
      timer_.stop();
      break;
    case active:
      result_time_ = INTERVAL_SEC1;
      if(status_ == stop)
      {
        timer_.start();
      }
      status_ = active;
      break;
    case sleep:
      status_ = sleep;
      result_time_ = INTERVAL_SEC2;
      break;
    }
    update_ui_status();
  }

  void update_ui_status()
  {
    switch(status_)
    {
    case stop:
      {
        // タイトルバー
        text(std::wstring(L"ミニタイマー(停止)"));

        if(taskbar_.is_create()){
          // サムネイル・ツールバー・ボタン
          thumb_button_manager_
            .at(0)
            .tool_tip(L"開始")
            .set_icon(thumb_icons_[0]);
          taskbar_.update_thumb_buttons(*this,thumb_button_manager_);

          // プログレス
          taskbar_.progress_state(*this,taskbar::error);
          taskbar_.progress_value(*this,0,0);
        }

      }
      break;
    case active:
      {
        ::MessageBeep(MB_OK);

        // タイトルバー
        text(std::wstring(L"ミニタイマー(活動中)"));

        if(taskbar_.is_create()){
          // サムネイル・ツールバー・ボタン
          thumb_button_manager_
            .at(0)
            .tool_tip(L"停止")
            .set_icon(thumb_icons_[1]);
          taskbar_.update_thumb_buttons(*this,thumb_button_manager_);

          // プログレス
          taskbar_.progress_state(*this,taskbar::indeterminate);
          taskbar_.progress_value(*this,result_time_,INTERVAL_SEC1);
        }
      }
      break;
    case sleep:
      {
        ::MessageBeep(MB_ICONEXCLAMATION);
        // タイトルバー
        text(std::wstring(L"ミニタイマー(休憩中)"));

        if(taskbar_.is_create()){
          // プログレス
          taskbar_.progress_state(*this,taskbar::paused);
          taskbar_.progress_value(*this,result_time_,INTERVAL_SEC2);
        }

      }
      break;
    }
    update_icon();
    invalidate_rect();
  }

  void draw_icon()
  {
    get_dc dc(NULL);
    // 互換DCの作成
    compatible_dc cdc(dc.get());
    {
      // 描画先への切り替え
      select_object s(cdc.get(),bmp_.get());
      RECT rect = {0,0,icon_size_.width,icon_size_.height};
      // 互換DCへのバインド
      dcr_->BindDC(cdc.get(),&rect);
      //// ブラシのセットアップ(背景の赤丸）
      //ID2D1SolidColorBrushPtr brush_e;
      //throw_if_err<>()(
      //  dcr->CreateSolidColorBrush(
      //    D2D1::ColorF(D2D1::ColorF::Red,0.8f), &brush_e));

      // アイコンに描画する文字の生成
      std::wstring t((boost::wformat(L"%s\n%02d:%02d") % (status_ == active?L"活動中":(status_ == sleep?L"休憩中":L"停止中")) % (result_time_ / 60) % (result_time_ % 60)).str());
      D2D1_RECT_F l = D2D1::RectF(0.0f,0.0f,icon_size_.width,icon_size_.height);
      // Text Formatの作成
      IDWriteTextFormatPtr f;
      write_factory_->CreateTextFormat(
      L"メイリオ",                // Font family name.
      NULL,                       // Font collection (NULL sets it to use the system font collection).
      DWRITE_FONT_WEIGHT_REGULAR,
      DWRITE_FONT_STYLE_NORMAL,
      DWRITE_FONT_STRETCH_NORMAL,
      10.0f,
      L"ja-jp",
      &f
      );

      f->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
      f->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);

      // 文字描画用ブラシのセットアップ
      ID2D1SolidColorBrushPtr brush;
      dcr_->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &brush);
      // 描画開始
      dcr_->BeginDraw();
      // ビットマップクリア
      dcr_->Clear(D2D1::ColorF(D2D1::ColorF::Black,0.0f));
      //// 赤丸を描く
      //dcr_->FillEllipse(D2D1::Ellipse(D2D1::Point2F(16.0f,16.0f),14,14),brush_e);
      // テキストを表示する
      dcr_->DrawTextW(t.c_str(),t.size(),f,&l,brush);
      // 描画終了
      dcr_->EndDraw();
    }


  }

  void create_task_bar()
  {
      taskbar_.create();
      taskbar_.overlay_icon(*this,icon(),std::wstring(L"ミニタイマー"));
      taskbar_.progress_state(*this,taskbar::indeterminate);
      taskbar_.progress_value(*this,100,100);

      thumb_icons_.push_back(icon(THUMB_START));
      thumb_icons_.push_back(icon(THUMB_STOP));

      thumb_button_manager_
        .add_thumb_button(THUMB_START,thumb_icons_[1],std::wstring(L"停止"))
        .enable(true);

      //thumb_button_manager_
      //  .add_thumb_button(THUMB_STOP,thumb_icons_[1],std::wstring(L"停止"))
      //  .enable(true);

      taskbar_.add_thumb_buttons(*this,thumb_button_manager_);
  }

  long wm_task_bar_create_;
  sf::taskbar taskbar_;
  sf::timer timer_;
  static const int INTERVAL_SEC1 = 60 * 15;// 15分
  static const int INTERVAL_SEC2 = 30;// 30秒

  int result_time_;
  enum timer_status 
  {
    stop,
    active,
    sleep
  };

  ID2D1FactoryPtr factory_;
  ID2D1HwndRenderTargetPtr render_target_;
  IDWriteFactoryPtr write_factory_;
  IWICImagingFactoryPtr wic_imaging_factory_;

  timer_status status_;
  IDWriteTextFormatPtr write_text_format_;
  D2D1_RECT_F layout_rect_;

  icon icon_;
  D2D1_SIZE_U icon_size_;
  bitmap_holder bmp_;
  ID2D1DCRenderTargetPtr dcr_;
  std::vector<icon> thumb_icons_;
  thumb_button_manager thumb_button_manager_;
};

  //
  // Creates a Direct2D bitmap from the specified
  // file name.
  //
  ID2D1BitmapPtr load_bitmap_from_file(
    ID2D1HwndRenderTargetPtr render_target,
    IWICImagingFactoryPtr wic_factory,
    std::wstring uri,
    uint32_t destination_width,
    uint32_t destination_height
    )
  {
    HRESULT hr = S_OK;

    IWICBitmapDecoderPtr decoder;
    IWICBitmapFrameDecodePtr decoder_source;
    IWICStreamPtr stream;
    IWICFormatConverterPtr converter;
    IWICBitmapScalerPtr scaler;
    ID2D1BitmapPtr bitmap;

    THROW_IFERR(wic_factory->CreateDecoderFromFilename(
      uri.c_str(),
      NULL,
      GENERIC_READ,
      WICDecodeMetadataCacheOnLoad,
      &decoder
      ));

    // Create the initial frame.
    THROW_IFERR(decoder->GetFrame(0, &decoder_source));

    // Convert the image format to 32bppPBGRA
    // (DXGI_FORMAT_B8G8R8A8_UNORM + D2D1_ALPHA_MODE_PREMULTIPLIED).
    THROW_IFERR(hr = wic_factory->CreateFormatConverter(&converter));

    // If a new width or height was specified, create an
    // IWICBitmapScaler and use it to resize the image.
    if (destination_width != 0 || destination_height != 0)
    {
      uint32_t originalWidth, originalHeight;
      THROW_IFERR(decoder_source->GetSize((UINT*)&originalWidth, (UINT*)&originalHeight));
      if (destination_width == 0)
      {
        FLOAT scalar = static_cast<FLOAT>(destination_height) / static_cast<FLOAT>(originalHeight);
        destination_width = static_cast<uint32_t>(scalar * static_cast<FLOAT>(originalWidth));
      }
      else if (destination_height == 0)
      {
        FLOAT scalar = static_cast<FLOAT>(destination_width) / static_cast<FLOAT>(originalWidth);
        destination_height = static_cast<uint32_t>(scalar * static_cast<FLOAT>(originalHeight));
      }

      THROW_IFERR(wic_factory->CreateBitmapScaler(&scaler));
      THROW_IFERR(scaler->Initialize(
        decoder_source,
        destination_width,
        destination_height,
        WICBitmapInterpolationModeCubic
        ));
      THROW_IFERR(converter->Initialize(
        scaler.GetInterfacePtr(),
        GUID_WICPixelFormat32bppPBGRA,
        WICBitmapDitherTypeNone,
        NULL,
        0.f,
        WICBitmapPaletteTypeMedianCut
        ));
    }
    else // Don't scale the image.
    {
      THROW_IFERR(converter->Initialize(
        decoder_source.GetInterfacePtr(),
        GUID_WICPixelFormat32bppPBGRA,
        WICBitmapDitherTypeNone,
        NULL,
        0.f,
        WICBitmapPaletteTypeMedianCut
        ));
    }

    // Create a Direct2D bitmap from the WIC bitmap.
    THROW_IFERR(render_target->CreateBitmapFromWicBitmap(
      converter.GetInterfacePtr(),
      NULL,
      &bitmap
      ));

    return bitmap;
  }

  toplevel_window::toplevel_window(const std::wstring& menu_name,const std::wstring& name,bool fit_to_display,float width ,float height)
    : impl_(new impl(menu_name,name,fit_to_display,width,height))
  {

  };

  void * toplevel_window::raw_handle() const {return impl_->raw_handle();};
  void toplevel_window::create(){impl_->create();};
  void toplevel_window::show(){impl_->show();};
  void toplevel_window::hide(){impl_->hide();};
  bool toplevel_window::is_show(){return impl_->is_show();};
  void toplevel_window::text(std::wstring& text){impl_->text(text);};
  void toplevel_window::update(){impl_->update();};

 toplevel_window_ptr create_toplevel_window
    (
    const std::wstring& menu_name,
    const std::wstring& name,
    const uint32_t show_flag,
    bool fit_to_display,
    float width,
    float height
    )
  {
    toplevel_window* p = new toplevel_window(menu_name,name,fit_to_display,width,height);
    p->create();
    p->show();
    p->update();
    return toplevel_window_ptr(p);
  }
  
}

