
#include "model_diagram_panel.h"
#include "image_cache.h"
#include "grtdb/db_object_helpers.h"
#include "workbench/wb_context_ui.h"
#include "toolbar_manager.h"
#include <gtkmm.h>
#include <gdkmm/cursor.h>
#include <gdk/gdkkeysyms.h>



class CanvasViewer : public mdc::GtkCanvas
{
  wb::ModelDiagramForm *_be;
  
  // override the event handlers from the base canvas widget
  virtual bool on_button_press_event(GdkEventButton *event)
  {
    mdc::MouseButton button= mdc::ButtonLeft;

    grab_focus();
    
    switch (event->button)
    {
    case 1: button= mdc::ButtonLeft; break;
    case 2: button= mdc::ButtonMiddle; break;
    case 3: button= mdc::ButtonRight; break;
    }

    _be->handle_mouse_button(button, true, event->x, event->y,
                             get_event_state(event->state));
    
    return true;
  }

  virtual bool on_button_release_event(GdkEventButton *event)
  {
    mdc::MouseButton button= mdc::ButtonLeft;

    switch (event->button)
    {
    case 1: button= mdc::ButtonLeft; break;
    case 2: button= mdc::ButtonMiddle; break;
    case 3: button= mdc::ButtonRight; break;
    }

    _be->handle_mouse_button(button, false, event->x, event->y,
                             get_event_state(event->state));

    return true;
  }

  virtual bool on_motion_notify_event(GdkEventMotion *event)
  {
    _be->handle_mouse_move(event->x, event->y,
                           get_event_state(event->state));

    return true;
  }

  mdc::KeyInfo getKeyInfo(GdkEventKey *e)
  {
    static struct KeyCodeMapping {
      guint key;
      mdc::KeyCode kcode;
    } keycodes[]= {
      {GDK_BackSpace, mdc::KBackspace},
      {GDK_Delete,  mdc::KDelete},
      {GDK_Down,    mdc::KDown},
      {GDK_End,     mdc::KEnd},
      {GDK_Return,  mdc::KEnter},
      {GDK_Escape,  mdc::KEscape},
      {GDK_F1,      mdc::KF1},
      {GDK_F2,      mdc::KF2},
      {GDK_F3,      mdc::KF3},
      {GDK_F4,      mdc::KF4},
      {GDK_F5,      mdc::KF5},
      {GDK_F6,      mdc::KF6},
      {GDK_F7,      mdc::KF7},
      {GDK_F8,      mdc::KF8},
      {GDK_F9,      mdc::KF9},
      {GDK_F10,     mdc::KF10},
      {GDK_F11,     mdc::KF11},
      {GDK_F12,     mdc::KF12},
      {GDK_Home,    mdc::KHome},
      {GDK_Insert,  mdc::KInsert},
      {GDK_Left,    mdc::KLeft},
      {GDK_Next,    mdc::KPageDown},
      {GDK_Page_Down,mdc::KPageDown},
      {GDK_Page_Up,  mdc::KPageUp},
      {GDK_Prior,   mdc::KPageUp},
      {GDK_Return,  mdc::KEnter},
      {GDK_Shift_L, mdc::KShift},
      {GDK_Shift_R, mdc::KShift},
      {GDK_Tab,     mdc::KTab},

      {GDK_space,   mdc::KSpace},
      {GDK_period, mdc::KPeriod},
      {GDK_comma,   mdc::KComma},
      {GDK_semicolon, mdc::KSemicolon}
    };

    mdc::KeyInfo k;

    k.keycode= mdc::KNone;
    k.string= "";
    for (unsigned int i= 0; i < sizeof(keycodes)/sizeof(*keycodes); i++)
    {
      if (keycodes[i].key == e->keyval)
      {
        k.keycode= keycodes[i].kcode;
        break;
      }
    }

    if (k.keycode == 0 && e->string)
    {
      k.string= e->string;
    }

    return k;
  }

  virtual bool on_key_press_event(GdkEventKey *event)
  {
    mdc::KeyInfo key= getKeyInfo(event);
    mdc::EventState state= get_event_state(event->state);

    _be->handle_key(key, true, state);
    return true;
  }

  virtual bool on_key_release_event(GdkEventKey *event)
  {
    mdc::KeyInfo key= getKeyInfo(event);
    mdc::EventState state= get_event_state(event->state);

    _be->handle_key(key, false, state);

    return true;
  }

public:
  CanvasViewer(wb::ModelDiagramForm *be, bool use_gl)
    : mdc::GtkCanvas(use_gl ? mdc::GtkCanvas::OpenGLCanvasType : mdc::GtkCanvas::BufferedXlibCanvasType), _be(be)
  {
  }
};



//--------------------------------------------------------------------------------

ModelDiagramPanel::InlineEditor::InlineEditor(ModelDiagramPanel *owner)
  : _owner(owner)
{
  _editing= false;
  _edit_field= Gtk::manage(new Gtk::Entry());

}


void ModelDiagramPanel::InlineEditor::begin_editing(int x, int y, int width, int height, const std::string &text)
{
  if (!_edit_field->get_parent())
  {
    _owner->_canvas->put(*_edit_field, x, y);
  }
  _edit_field->grab_focus();
  _edit_field->select_region(0, -1);
  _edit_field->set_text(text);
  _edit_field->show();
}


void ModelDiagramPanel::InlineEditor::end_editing()
{
  _edit_field->hide();
}


void ModelDiagramPanel::InlineEditor::set_font_size(float size)
{
}


void ModelDiagramPanel::InlineEditor::set_multiline(bool flag)
{
}

//--------------------------------------------------------------------------------

ModelDiagramPanel::ModelDiagramPanel(wb::WBContextUI *wb, ToolbarManager *toolbar_manager)
  : Gtk::HBox(false, 0), _wb(wb), _be(0), _canvas(0), _cursor(0), _sidebar(0), _inline_editor(this), _toolbar_manager(toolbar_manager)
{
  _tools_toolbar.set_name("tools_toolbar");
  _options_toolbar.set_name("options_toolbar");
}



void ModelDiagramPanel::view_realized()
{
  _canvas->get_canvas()->set_user_data(dynamic_cast<FormViewBase*>(this));

  // changing zoom or scrolling should cancel any editing in place
  _canvas->get_canvas()->signal_viewport_changed().connect(sigc::mem_fun(_be, &wb::ModelDiagramForm::stop_editing));
}


void ModelDiagramPanel::init(const std::string &view_id)
{
  _be= _wb->get_diagram_form_for_diagram_id(view_id);
  _be->set_frontend_data(dynamic_cast<FormViewBase*>(this));

  _be->set_inline_editor_context(&_inline_editor);
  
  pack_start(_tools_toolbar, false, false);
  pack_start(_vbox, true, true);
  
  _vbox.pack_start(_options_toolbar, false, false);
  _vbox.pack_start(_scroller, true, true);

  _canvas= Gtk::manage(new CanvasViewer(_be, _wb->get_wb()->use_opengl()));
  _scroller.add(*_canvas);  
  
  std::vector<Gtk::TargetEntry> targets;
  std::vector<std::string> types= _be->get_accepted_drop_types();
  for (std::vector<std::string>::const_iterator iter= types.begin(); iter != types.end(); ++iter)
    targets.push_back(Gtk::TargetEntry(*iter, Gtk::TARGET_SAME_APP));
  _canvas->drag_dest_set(targets);

  _canvas->signal_drag_drop().connect(sigc::mem_fun(this, &ModelDiagramPanel::drag_drop));
  _canvas->signal_drag_data_received().connect(sigc::mem_fun(this, &ModelDiagramPanel::drag_data_received));

  _canvas->signal_realize().connect(sigc::mem_fun(this, &ModelDiagramPanel::view_realized));
  
  show_all();

  // must show before attaching signal because this initial show shouldn't be accounted
  // for changnig the closed state of the diagram
  signal_show().connect(sigc::bind(sigc::mem_fun(_be, &wb::ModelDiagramForm::set_closed), false));
  signal_hide().connect(sigc::bind(sigc::mem_fun(_be, &wb::ModelDiagramForm::set_closed), true));

  _options_toolbar.hide();
}


ModelDiagramPanel::~ModelDiagramPanel()
{
  delete _cursor;
}



bool ModelDiagramPanel::drag_drop(const Glib::RefPtr<Gdk::DragContext> &context, int x, int y, guint time)
{
  //std::vector<Gtk::TargetEntry> targets(context->get_targets());
  
  //if (targets
  {
    Glib::ustring type= "x-mysql-wb/db.DatabaseObject";
    
    drag_get_data(context, type, time);
  }
  
  return true;
}


void ModelDiagramPanel::drag_data_received(const Glib::RefPtr<Gdk::DragContext> &context, 
                                        int x, int y, const Gtk::SelectionData &selection_data,
                                        guint, guint time)
{
  std::string type= selection_data.get_data_type();
  _be->perform_drop(x, y, type, selection_data.get_data_as_string());

  context->drag_finish(false, false, time);
}


static Gdk::Cursor *load_cursor(const std::string &path)
{
  gsize size;
  guint8 *buffer;
  Glib::RefPtr<Gdk::Pixbuf> pixbuf;
  
  if (g_file_get_contents(path.c_str(), (gchar**)&buffer, &size, NULL))
  {
    if (buffer[0] != 0 || buffer[1] != 0 || buffer[2] != 2 || buffer[3] != 0)
    {
      g_free(buffer);
      return 0;
    }
    // read directory info
    int width= buffer[6+0];
    int height= buffer[6+1];
    int colors= buffer[6+2];
    int xspot= buffer[6+4]|buffer[6+5]<<8;
    int yspot= buffer[6+6]|buffer[6+7]<<8;
    gint32 bmp_size= GINT32_FROM_LE(*(guint32*)&buffer[6+8]);
    gint32 bmp_offset= GINT32_FROM_LE(*(guint32*)&buffer[6+12]);

    if (colors == 2 && width == 32 && height == 32 && bmp_offset + bmp_size <= (gint32)size)
    {
      // read dib info
      guint32 bi_size= GINT32_FROM_LE(*(guint32*)&buffer[bmp_offset+0]);
      gint bi_width= GINT32_FROM_LE(*(guint32*)&buffer[bmp_offset+4]);
      gint bi_height= GINT32_FROM_LE(*(guint32*)&buffer[bmp_offset+8]);
      //gint16 bi_planes; // 12
      gint16 bi_bitcount= GINT16_FROM_LE(*(guint16*)&buffer[bmp_offset+14]);
      //guint32 bi_compression; // 16
      //guint32 bi_sizeimage= GUINT32_FROM_LE(*(guint32*)&buffer[bmp_offset+20]);
      //gint32 bi_resx, bi_resy;
      //guint32 bi_clrused;
      //guint32 bi_clrimportant;

      guint8 *color_table= &buffer[bmp_offset+bi_size];

      const guint8 *xor_mask= (guint8*)&buffer[bmp_offset+bi_size+4*colors];
      const guint8 *and_mask= (guint8*)&buffer[bmp_offset+bi_size+4*colors+bi_width*bi_height/16];

      guint8 *image= (guint8*)g_malloc0(width*height*4);
      int bytes_per_line=(((bi_width*bi_bitcount)+31) & ~31) >> 3;

      for (int y= 0; y < height; y++)
      {
        const guint8 *and_bytes= and_mask + y*bytes_per_line;
        const guint8 *xor_bytes= xor_mask + y*bytes_per_line;

        for (int x= 0; x < width; x++)
        {
          int offs= ((height-1-y)*width+x)*4;
          
          if ((xor_bytes[x/8] & (0x80>>(x%8))) != 0)
          {
            image[offs+0]= color_table[1*4+0];
            image[offs+1]= color_table[1*4+1];
            image[offs+2]= color_table[1*4+2];
          }
          else
          {
            image[offs+0]= color_table[0*4+0];
            image[offs+1]= color_table[0*4+1];
            image[offs+2]= color_table[0*4+2];
          }

          if ((and_bytes[x/8] & (0x80>>(x%8))) != 0)
            image[offs+3]= 0;
          else
            image[offs+3]= 0xff;
        }
      }

      pixbuf= Gdk::Pixbuf::create_from_data(image, Gdk::COLORSPACE_RGB, TRUE, 8, width, height, width*4, sigc::ptr_fun((void(*)(const guint8*))g_free));
    }
    else
      g_message("unsupported icon format in %s", path.c_str());

    g_free(buffer);
    
    if (pixbuf)
      return new Gdk::Cursor(Gdk::Display::get_default(), pixbuf, xspot, yspot);
    return 0;
  }
  return 0;
}


void ModelDiagramPanel::update_tool_cursor()
{
  if (_canvas->is_realized())
  {
    std::string cursor= _be->get_cursor();
    std::string path= bec::IconManager::get_instance()->get_icon_path(cursor+".cur");

    if (path.empty())
    {
      _canvas->get_window()->set_cursor();
      return;
    }
    delete _cursor;

    _cursor= load_cursor(path);
    if (_cursor)
      _canvas->get_window()->set_cursor(*_cursor);
    else
      _canvas->get_window()->set_cursor();
  }
}


void ModelDiagramPanel::set_title(const std::string &title)
{
  _title_changed.emit(title);
}



void ModelDiagramPanel::update_toolbar(const std::string &toolbar)
{
  /*
  if (toolbar == WB_TOOLBAR_TOOLS || toolbar.empty())
  {
    _toolbar_manager->rebuild_toolbar(&_tools_toolbar, WB_TOOLBAR_TOOLS);
  }
  */
  if (toolbar == WB_TOOLBAR_OPTIONS || toolbar.empty())
  {
    _toolbar_manager->rebuild_toolbar(&_options_toolbar, WB_TOOLBAR_OPTIONS);
    if (_options_toolbar.children().empty())
      _options_toolbar.hide();
    else
      _options_toolbar.show();
  }
}
