#ifndef __CANVAS_H__
#define __CANVAS_H__

#include "mdc_canvas_view_windows.h"
#include "mdc_canvas_view_image.h"

#include <glib.h>

#include "Grt.h"
#include "GrtTemplates.h"
#include "Exceptions.h"

#ifdef _MSC_VER
//#include  <vcclr.h> // .net interop helpers
using namespace MySQL::Grt;
using namespace System;
using namespace System::Windows::Forms;
using namespace System::Collections::Generic;
using namespace System::Runtime::InteropServices;
#endif

namespace MySQL {
namespace GUI {
namespace Mdc {

struct KeyCodeMapping {
  Keys key;
  mdc::KeyCode kcode;
};



public ref class BaseWindowsCanvasView
{
public:

  delegate void Void4IntDelegate(int,int,int,int);
  delegate void VoidVoidDelegate();

protected:

  ::mdc::CanvasView *inner;

private:
  // needed for fixed pointer
  GCHandle m_gch;

  Form^ owner_form;

  // void (int,int,int,int)
  delegate void Void4IntWrapperDelegate(int,int,int,int);
  typedef void (*BaseWindowsCanvasView::VOID_4INT_CB)(int,int,int,int);
  // void ()
  delegate void VoidVoidWrapperDelegate();
  typedef void (*BaseWindowsCanvasView::VOID_VOID_CB)();

  Void4IntDelegate^ on_needs_repaint_delegate;
  Void4IntWrapperDelegate^ on_needs_repaint_wrapper_delegate;

  void on_needs_repaint_wrapper(int x, int y, int w, int h)
  {
    on_needs_repaint_delegate(x, y, w, h);
  }

  VoidVoidDelegate^ on_viewport_changed_delegate;
  VoidVoidWrapperDelegate^ on_viewport_changed_wrapper_delegate;

  void on_viewport_changed_wrapper()
  {
    on_viewport_changed_delegate();
  }

public:
  BaseWindowsCanvasView() : inner(nullptr)
  {
  }

  ~BaseWindowsCanvasView()
  {
    //delete inner;
    ReleaseHandle();
  }

  ::mdc::CanvasView *get_unmanaged_object()
  {
    return inner;
  }

  // Returns a fixed pointer to this object that will not be modified by the GC
  IntPtr GetFixedId()
  {
    if (!m_gch.IsAllocated)
       m_gch = GCHandle::Alloc( this );
     return GCHandle::ToIntPtr( m_gch );
  }

  // Needs to be called when destroying the object
  void ReleaseHandle()
  {
    m_gch.Free();
  }

  // Returns the object based on the fixed pointer retrieved by GetFixedId()
  static BaseWindowsCanvasView^ GetFromFixedId( IntPtr ip )
  {
    GCHandle gcHandle = GCHandle::FromIntPtr( ip );
    return (BaseWindowsCanvasView^)gcHandle.Target;
  }

  void set_on_needs_repaint(Void4IntDelegate^ dt)
  {
    on_needs_repaint_delegate= dt;
    on_needs_repaint_wrapper_delegate= 
      gcnew Void4IntWrapperDelegate(this, &BaseWindowsCanvasView::on_needs_repaint_wrapper);
    IntPtr ip = Marshal::GetFunctionPointerForDelegate(on_needs_repaint_wrapper_delegate);
    VOID_4INT_CB cb = static_cast<VOID_4INT_CB>(ip.ToPointer());
    inner->signal_repaint().connect(sigc::ptr_fun(cb));
  }

  void set_on_viewport_changed(VoidVoidDelegate^ dt)
  {
    on_viewport_changed_delegate= dt;
    on_viewport_changed_wrapper_delegate= 
      gcnew VoidVoidWrapperDelegate(this, &BaseWindowsCanvasView::on_viewport_changed_wrapper);
    IntPtr ip = Marshal::GetFunctionPointerForDelegate(on_viewport_changed_wrapper_delegate);
    VOID_VOID_CB cb = static_cast<VOID_VOID_CB>(ip.ToPointer());
    inner->signal_viewport_changed().connect(sigc::ptr_fun(cb));
  }

  bool initialize()
  {
    return get_unmanaged_object()->initialize();
  }

  void repaint(int x, int y, int width, int height)
  {
    try
    {
      get_unmanaged_object()->repaint(x, y, width, height);
    }
    catch(std::exception *ex)
    {
      throw gcnew BackendException(ex);
    }
    catch(std::exception &ex)
    {
      throw gcnew BackendException(&ex);
    }
    catch(...)
    {
      throw gcnew UnknownBackendException(__FILE__, __LINE__);    
    }
  }

  void repaint()
  {
    get_unmanaged_object()->repaint();
  }

  double get_fps()
  {
    return get_unmanaged_object()->get_fps();
  }

  //void update_view_size(int width, int height)
  //{
  //  get_unmanaged_object()->update_view_size(width, height);
  //}

  void OnMouseMove(MouseEventArgs^ e, Keys keystate, MouseButtons buttons)
  {
    get_unmanaged_object()->handle_mouse_move(e->X, e->Y, getEventState(keystate, buttons));
  }

  void OnMouseDown(MouseEventArgs^ e, Keys keystate, MouseButtons buttons)
  {
    mdc::MouseButton butt= mdc::ButtonLeft;
    switch(e->Button)
    {
    case MouseButtons::Left: butt= mdc::ButtonLeft; break;
    case MouseButtons::Middle: butt= mdc::ButtonMiddle; break;
    case MouseButtons::Right: butt= mdc::ButtonRight; break;
    }
    get_unmanaged_object()->handle_mouse_button(butt, true, e->X, e->Y, getEventState(keystate, buttons));
  }

  void OnMouseUp(MouseEventArgs^ e, Keys keystate, MouseButtons buttons)
  {
    mdc::MouseButton butt= mdc::ButtonLeft;
    switch(e->Button)
    {
    case MouseButtons::Left: butt= mdc::ButtonLeft; break;
    case MouseButtons::Middle: butt= mdc::ButtonMiddle; break;
    case MouseButtons::Right: butt= mdc::ButtonRight; break;
    }
    get_unmanaged_object()->handle_mouse_button(butt, false, e->X, e->Y, getEventState(keystate, buttons));
  }

  void OnKeyDown(KeyEventArgs^ e, Keys keystate)
  {
    get_unmanaged_object()->handle_key(getKeyInfo(e), true, getEventState(keystate, MouseButtons::None));
  }

  void OnKeyUp(KeyEventArgs^ e, Keys keystate)
  {
    get_unmanaged_object()->handle_key(getKeyInfo(e), false, getEventState(keystate, MouseButtons::None));
  }

  void OnSizeChanged(int w, int h)
  {
    ::mdc::CanvasView *canvas = get_unmanaged_object();

    if (w < 1) w= 1;
    if (h < 1) h= 1;

    if (canvas != nullptr)
      canvas->update_view_size(w, h);
  }

  void SetOwnerForm(Form^ ownerForm)
  {
    owner_form = ownerForm;
  }

  Form^ GetOwnerForm()
  {
    return owner_form;
  }

  void get_viewport_range([Out] double %x, [Out] double %y, [Out] double %w, [Out] double %h)
  {
    mdc::Rect rect;
    rect= get_unmanaged_object()->get_viewport_range();
    x= rect.xmin();
    y= rect.ymin();
    w= rect.width();
    h= rect.height();
  }

  void get_viewport([Out] double %x, [Out] double %y, [Out] double %w, [Out] double %h)
  {
    mdc::Rect rect;
    rect= get_unmanaged_object()->get_viewport();
    x= rect.xmin();
    y= rect.ymin();
    w= rect.width();
    h= rect.height();
  }

  void set_offset(double x, double y)
  {
    get_unmanaged_object()->set_offset(mdc::Point(x, y));
  }

  void scroll_to(double x, double y)
  {
    get_unmanaged_object()->scroll_to(mdc::Point(x, y));
  }

  void get_total_view_size([Out] double %w, [Out] double %h)
  {
    mdc::Size size;
    size= get_unmanaged_object()->get_total_view_size();
    w= size.width;
    h= size.height;
  }

  void window_to_canvas(int x, int y, [Out] double %ox, [Out] double %oy)
  {
    mdc::Point p= get_unmanaged_object()->window_to_canvas(x, y);
    ox= p.x;
    oy= p.y;
  }

  void window_to_canvas(int x, int y, int w, int h,  [Out] double %ox, [Out] double %oy,  [Out] double %ow, [Out] double %oh)
  {
    mdc::Rect r= get_unmanaged_object()->window_to_canvas(x, y, w, h);
    ox= r.pos.x;
    oy= r.pos.y;
    ow= r.size.width;
    oh= r.size.height;
  }

  void update_view_size(int w, int h)
  {
    get_unmanaged_object()->update_view_size(w, h);
  }


  static mdc::EventState getEventState(Keys keys, MouseButtons buttons);
  static mdc::KeyInfo getKeyInfo(KeyEventArgs ^e);


  // TODO: wrap these
  //virtual void canvas_to_window(const Point &pt, int &x, int &y) const;
  //virtual void canvas_to_window(const Rect &rect, int &x, int &y, int &w, int &h) const;


};



public ref class WindowsGLCanvasView : public BaseWindowsCanvasView
{
public:
  WindowsGLCanvasView(IntPtr window, int width, int height)
  {
    inner= new ::mdc::WindowsGLCanvasView((HWND)window.ToPointer(), width, height);

    // get a fixed pointer to this object
    IntPtr ip = this->GetFixedId();

    // set it as the user data
    inner->set_user_data((void*)(intptr_t)ip);
  }
};



public ref class WindowsGDICanvasView : public BaseWindowsCanvasView
{
public:
  WindowsGDICanvasView(IntPtr window, int width, int height, bool double_buffer)
  {
    if (double_buffer)
      inner= new ::mdc::BufferedWindowsCanvasView((HWND)window.ToPointer(), width, height);
    else
      inner= new ::mdc::WindowsCanvasView((HWND)window.ToPointer(), width, height);

    // get a fixed pointer to this object
    IntPtr ip = this->GetFixedId();

    // set it as the user data
    inner->set_user_data((void*)(intptr_t)ip);
  }
};



public ref class ImageCanvasView : public BaseWindowsCanvasView
{
public:
  ImageCanvasView(int width, int height)
  {
    inner= new ::mdc::ImageCanvasView(width, height, CAIRO_FORMAT_RGB24);

    // get a fixed pointer to this object
    IntPtr ip = this->GetFixedId();

    // set it as the user data
    inner->set_user_data((void*)(intptr_t)ip);
  }

  const IntPtr get_image_data([Out] int %size)
  {
    size_t c_size;
    const unsigned char *data= dynamic_cast<mdc::ImageCanvasView*>(inner)->get_image_data(c_size);
    size= (int)c_size;

    return IntPtr((void*)data);
  }
};

} // namespace Workbench
} // namespace GUI
} // namespace MySQL

#endif // __CANVAS_H__