#include "mforms/popover.h"
#include "../lf_mforms.h"
#include "../lf_view.h"

#include <gtkmm/window.h>
#include <gtkmm/fixed.h>

#include "gtk_helpers.h"

namespace
{
//==============================================================================
//
//==============================================================================
class PopoverWidget : public Gtk::Window
{
  public:
    PopoverWidget(Gtk::Window* parent);

    void show_popover(const int x, const int y, const mforms::StartPosition pos);
    void set_size(const int w, const int h);
    void set_content(Gtk::Widget* w);

  protected:
    virtual bool on_expose_event(GdkEventExpose* event);
    virtual bool on_configure_event(GdkEventConfigure* ce);

  private:
    bool parent_configure_event(GdkEvent* e);
    void create_shape_path(cairo_t* cr, const int offset);
    void recalc_shape_mask();
    void adjust_position();

    int                     _old_parent_x; // last saved x coord of parent window
    int                     _old_parent_y; // last saved y coord of parent window
    mforms::StartPosition   _content_pos;
    int                     _handle_x;
    int                     _handle_y;

    Gtk::Fixed              _fixed;
    int                     _old_w;
    int                     _old_h;
    bool                    _ignore_configure;

    const int               R;      // Radius of round corner
    const int               hh;     // Length of tear(handle) triangle side
    const int               hh1;    // Height of tear(handle) triangle
};

//------------------------------------------------------------------------------
PopoverWidget::PopoverWidget(Gtk::Window* parent)
        : Gtk::Window(Gtk::WINDOW_TOPLEVEL)
        , _old_parent_x(-1)
        , _old_parent_y(-1)
        , _content_pos(mforms::Left)
        , _handle_x(-1)
        , _handle_y(-1)
        , _old_w(-1)
        , _old_h(-1)
        , _ignore_configure(false)
        , R(30)
        , hh(30)
        , hh1(0.883025 * hh)
{
  set_decorated(false);
  set_transient_for(*parent);
  parent->signal_event().connect(sigc::mem_fun(this, &PopoverWidget::parent_configure_event));

  _fixed.set_has_window(false);
  add(_fixed);
  _fixed.show_all();
}

//------------------------------------------------------------------------------
bool PopoverWidget::parent_configure_event(GdkEvent* ev)
{
  if (ev->type != GDK_CONFIGURE)
    return false;

  GdkEventConfigure* e = (GdkEventConfigure*)ev;

  if (_old_parent_y >= 0 && _old_parent_x >= 0)
  {
    const int diff_x = e->x - _old_parent_x;
    const int diff_y = e->y - _old_parent_y;
    int this_x = 0;
    int this_y = 0;
    get_position(this_x, this_y);
    move(this_x + diff_x, this_y + diff_y);
    _old_parent_x = e->x;
    _old_parent_y = e->y;
  }
  else
  {
    _old_parent_x = e->x;
    _old_parent_y = e->y;
  }

  return false;
}

//------------------------------------------------------------------------------
void PopoverWidget::create_shape_path(cairo_t* cr, const int offset)
{
  int x = offset;
  int y = offset;
  int W = get_width() - (offset ? (1+offset) : 0);
  int H = get_height() - (offset ? (1+offset) : 0);

  //printf("create_shape_path x=%i, y=%i, W=%i, H=%i\n", x, y, W, H);

  switch (_content_pos)
  {
    case mforms::Left   : {W -= hh1; break;}
    case mforms::Right  : {x += hh1; W -= hh1; break;}
    case mforms::Above  : {H -= hh1; break;}
    case mforms::Below  : {y += hh1; H -= hh1; break;}
  }

  cairo_new_path(cr);
  cairo_move_to(cr, x + R, y);                    // 1
  if (_content_pos == mforms::Below)
  {
    cairo_line_to(cr, x + (W/4) - hh1/2, y);                // 2
    cairo_line_to(cr, x + (W/4),         y - hh1);          // 2
    cairo_line_to(cr, x + (W/4) + hh1/2, y);                // 2
    cairo_line_to(cr, x + (W-R),         y);                // 2
  }
  else
    cairo_line_to(cr, x + (W-R), y);                // 2
  cairo_curve_to(cr, x+W, y, x+W, y, x+W, y+R);     // 3
  if (_content_pos == mforms::Left)
  {
    cairo_line_to(cr, x+W,       y + H/4 - hh1/2);          // 4
    cairo_line_to(cr, x+W + hh1, y + H/4);                  // 4
    cairo_line_to(cr, x+W,       y + H/4 + hh1/2);          // 4
    cairo_line_to(cr, x+W,       y + (H-R));                // 4
  }
  else
    cairo_line_to(cr, x+W, y+(H-R));                        // 4
  cairo_curve_to(cr, x+W, y+H, x+W, y+H, x+(W-R), y+H);   // 5
  if (_content_pos == mforms::Above)
  {
    cairo_line_to(cr, x + W/4 + hh1/2, y+H);                  // 6
    cairo_line_to(cr, x + W/4,         y+H + hh1);                  // 6
    cairo_line_to(cr, x + W/4 - hh1/2, y+H);                  // 6
    cairo_line_to(cr, x+R, y+H);                  // 6
  }
  else
    cairo_line_to(cr, x+R, y+H);                  // 6
  cairo_curve_to(cr, x, y+H, x, y+H, x, y+(H-R));   // 7
  if (_content_pos == mforms::Right)
  {
    cairo_line_to(cr, x      , y + H/4 + hh1/2);                  // 8
    cairo_line_to(cr, x - hh1, y + H/4);                  // 8
    cairo_line_to(cr, x      , y + H/4 - hh1/2);                  // 8
    cairo_line_to(cr, x, y+R);                  // 8
  }
  else
    cairo_line_to(cr, x, y+R);                  // 8
  cairo_curve_to(cr, x, y, x, y, x+R, y);     // 9
  cairo_close_path(cr);
}

//------------------------------------------------------------------------------
void PopoverWidget::set_size(const int w, const int h)
{
  resize(w, h);
}

//------------------------------------------------------------------------------
void PopoverWidget::set_content(Gtk::Widget* w)
{
  int x = 0;
  int y = 0;
  int W = get_width();
  int H = get_height();

  switch (_content_pos)
  {
    case mforms::Left   : {W -= hh1; break;}
    case mforms::Right  : {x += hh1; W -= hh1; break;}
    case mforms::Above  : {H -= hh1; break;}
    case mforms::Below  : {y += hh1; H -= hh1; break;}
  }

  // clear existing content
  _fixed.put(*w, x + 10, y + 10);
  _fixed.show_all();
}

//------------------------------------------------------------------------------
void PopoverWidget::recalc_shape_mask()
{
  const int w = get_width();
  const int h = get_height();
  //printf("recalc shape for %i,%i\n", w, h);

  // recalc mask
  Glib::RefPtr<Gdk::Pixmap> mask = Gdk::Pixmap::create(Glib::RefPtr<Gdk::Drawable>(0), w, h, 1);
  Cairo::RefPtr<Cairo::Context> context = mask->create_cairo_context();

  cairo_t *cr = context->cobj();

  if (cr)
  {
    // Draw round corners
    const int W = w;
    const int H = h;

    cairo_save (cr);
    cairo_rectangle (cr, 0, 0, w, h);
    cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
    cairo_fill (cr);
    cairo_restore (cr);

    cairo_set_source_rgb(cr, 0.7, 0.7, 0.7);
    cairo_set_line_width(cr, 0.2);
    create_shape_path(cr, 0);
    cairo_fill_preserve(cr);
  }

  gtk_widget_shape_combine_mask(GTK_WIDGET(gobj()), mask->gobj(), 0, 0);
}

//------------------------------------------------------------------------------
void PopoverWidget::adjust_position()
{
  int w = get_width();
  int h = get_height();
  int x = _handle_x;
  int y = _handle_y;

  switch (_content_pos)
  {
    case mforms::Above  : {x -= 3 * w / 4;    y -= h;           break;}
    case mforms::Below  : {x -= w / 4;                          break;}
    case mforms::Right  : {                   y -= 3 * h / 4;   break;}
    case mforms::Left   : {x -= w;            y -= h /4;        break;}
  }

  move(x, y);
}

//------------------------------------------------------------------------------
void PopoverWidget::show_popover(const int rx, const int ry, const mforms::StartPosition pos)
{
  _content_pos = pos;
  _handle_x = rx;
  _handle_y = ry;

  adjust_position();
  recalc_shape_mask();
  show();
}

//------------------------------------------------------------------------------
bool PopoverWidget::on_expose_event(GdkEventExpose* event)
{
  Gtk::Window::on_expose_event(event);

  if (_handle_x >= 0 && _handle_y >= 0)
  {
    Cairo::RefPtr<Cairo::Context> context(get_window()->create_cairo_context());
    cairo_t *cr = context->cobj();

    if (cr)
    {
      cairo_save(cr);
      create_shape_path(cr, 1);
      cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
      cairo_set_line_width(cr, 0.5);
      cairo_stroke(cr);
      cairo_restore(cr);
    }
  }

  return false;
}

//------------------------------------------------------------------------------
bool PopoverWidget::on_configure_event(GdkEventConfigure* ce)
{
  if (_ignore_configure)
  {
    _ignore_configure = false;
    return false;
  }

  if (_handle_x >= 0 && _handle_y >= 0)
  {
    if (_old_w != ce->width || _old_h != ce->height)
    {
      _old_w = ce->width;
      _old_h = ce->height;
      recalc_shape_mask(); // calc only on resize

      std::vector<Gtk::Widget*>  widgets = _fixed.get_children();
      if (widgets.size() > 0)
      {
        Gtk::Widget* w = widgets[0];
        int W = ce->width;
        int H = ce->height;

        switch (_content_pos)
        {
          case mforms::Left   :
          case mforms::Right  : {W -= hh1; break;}
          case mforms::Above  :
          case mforms::Below  : {H -= hh1; break;}
        }

        w->set_size_request(W-20, H-20);
        adjust_position();
        _ignore_configure = true;
      }
    }
  }

  return false;
}
} // namespace

//------------------------------------------------------------------------------
static void delete_PopoverWidget(void* o)
{
  delete (PopoverWidget*)o;
}

//------------------------------------------------------------------------------
static bool create(mforms::Popover* self)
{
  PopoverWidget* w = new PopoverWidget(get_mainwindow());
  self->set_data(w, delete_PopoverWidget);
  return w;
}

//------------------------------------------------------------------------------
static void set_content(mforms::Popover* self, mforms::View* content)
{
  PopoverWidget *w = self->get_data<PopoverWidget>();
  mforms::gtk::ViewImpl *view = content->get_data<mforms::gtk::ViewImpl>();
  w->set_content(view->get_outer());
}

//------------------------------------------------------------------------------
static void set_size(mforms::Popover* self, int w, int h)
{
  PopoverWidget *widget = self->get_data<PopoverWidget>();
  widget->set_size(w, h);
}

//------------------------------------------------------------------------------
static void show(mforms::Popover* self, int x, int y, mforms::StartPosition pos)
{
  PopoverWidget *widget = self->get_data<PopoverWidget>();
  widget->show_popover(x, y, pos);
}

//------------------------------------------------------------------------------
static void close(mforms::Popover* self)
{
  PopoverWidget *w = self->get_data<PopoverWidget>();
  w->hide();
}

namespace mforms
{
namespace gtk
{
void Popover_init()
{
  ::mforms::ControlFactory *f = ::mforms::ControlFactory::get_instance();

  f->_popover_impl.create       = create;
  f->_popover_impl.set_content  = set_content;
  f->_popover_impl.set_size     = set_size;
  f->_popover_impl.show         = show;
  f->_popover_impl.close        = close;
}
}
}
