/* 
 * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of the
 * License.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */
#include "../lf_mforms.h"

#include "../lf_view.h"
#include "base/util_functions.h"
#include "base/log.h"
DEFAULT_LOG_DOMAIN("mforms.linux")

namespace mforms { namespace gtk {


  // get the widget that does the actual work. most of the time it will be the same as the outer one
Gtk::Widget *ViewImpl::get_inner() const
{
  return get_outer();
}

ViewImpl::ViewImpl(::mforms::View *view)
  : ObjectImpl(view)
{}


void ViewImpl::destroy(::mforms::View *self)
{
  //XXX
}

void ViewImpl::show(::mforms::View *self, bool show)
{
  ViewImpl *view = self->get_data<ViewImpl>();

  if ( view )
  {
    Gtk::Widget* widget = view->get_outer();
    if (show)
      widget->show();
    else
      widget->hide();
  }
}

bool ViewImpl::is_shown(::mforms::View *self)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  if (view)
    return view->get_outer()->is_visible();
  return false;
}


void ViewImpl::set_tooltip(::mforms::View *self, const std::string &text)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  if (view)
  {
#if GTK_VERSION_GT(2,10)
    view->get_outer()->set_tooltip_text(text);
    view->get_outer()->set_has_tooltip(!text.empty());
#endif
  }
}

void ViewImpl::set_font(::mforms::View *self, const std::string &fontDescription)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  if (view)
  {
    // apply font settings
  }
}
  
int ViewImpl::get_width(::mforms::View *self)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  if ( view )
  {
    Gtk::Widget* widget = view->get_outer();
    //int w, h;
    //widget->get_size_request(w, h);
    return widget->get_allocation().get_width();
  }
  return 0;
}

int ViewImpl::get_height(::mforms::View *self)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  if ( view )
  {
    Gtk::Widget* widget = view->get_outer();
    //int w, h;
    //widget->get_size_request(w, h);
    return widget->get_allocation().get_height();
  }
  return 0;
}

int ViewImpl::get_preferred_width(::mforms::View *self)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  if ( view )
  {
    return view->get_preferred_width();
  }
  return 0;
}

int ViewImpl::get_preferred_width()
{
  int w, h;
  get_outer()->get_size_request(w, h);
  return w;
}

int ViewImpl::get_preferred_height(::mforms::View *self)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  if ( view )
    return view->get_preferred_height();
  return 0;
}

int ViewImpl::get_preferred_height()
{
  int w, h;
  get_outer()->get_size_request(w, h);
  return h;
}

int ViewImpl::get_x(::mforms::View *self)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  if ( view )
  {
    Gtk::Widget* widget = view->get_outer();
    return widget->get_allocation().get_x();
  }
  return 0;
}

int ViewImpl::get_y(::mforms::View *self)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  if ( view )
  {
    Gtk::Widget* widget = view->get_outer();
    return widget->get_allocation().get_y();
  }
  return 0;
}

void ViewImpl::set_size(::mforms::View *self, int w, int h)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  if ( view )
    view->set_size(w, h);
}

void ViewImpl::set_size(int width, int height)
{
  get_outer()->set_size_request(width, height);
}

void ViewImpl::set_position(::mforms::View *self, int x, int y)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  if (view)
  {
    mforms::View* parent = self->get_parent();
    if (parent)
    {
      ViewImpl* parent_view_impl = parent->get_data<ViewImpl>();
      if (parent_view_impl)
        parent_view_impl->move_child(view, x, y);
    }
  }
}

void ViewImpl::client_to_screen(::mforms::View *self, int& x, int& y)
{
  ViewImpl *view = self->get_data<ViewImpl>();

  if ( view )
  {
    Gtk::Widget* widget = view->get_outer();
    if (widget)
    {
      Glib::RefPtr<Gdk::Window> wnd = widget->get_window();
      if (wnd)
      {
        #if GTK_VERSION_LT(2,18)
         int originx = 0;
         int originy = 0;
         wnd->get_origin(originx, originy);
         point.x += orginx;
         point.y += originy;
        #else
         int newx = x;
         int newy = y;
         wnd->get_root_coords(x, y, newx, newy);
         x = newx;
         y = newy;
        #endif
      }
    }
  }
}
  
void ViewImpl::set_enabled(::mforms::View *self, bool flag)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  if (view)
  {
    Gtk::Widget* widget = view->get_outer();
    widget->set_sensitive(flag);
  }
}

bool ViewImpl::is_enabled(::mforms::View *self)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  if (view)
  {
    Gtk::Widget* widget = view->get_outer();
    return widget->get_sensitive();
  }
  return false;
}


void ViewImpl::set_name(::mforms::View *self, const std::string &name)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  if ( view )
    view->set_name(name);
}

void ViewImpl::set_name(const std::string &name)
{

}

void ViewImpl::relayout(::mforms::View *view)
{
  // noop
}

void ViewImpl::set_needs_repaint(::mforms::View *self)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  if (view)
  {
    Gtk::Widget *widget = view->get_outer();
    if (widget)
    {
//      Glib::RefPtr<Gdk::Window> window = widget->get_window();
//      if (window)
//        window->process_updates(true);
//      else
      widget->queue_draw();
    }
  }
}

void ViewImpl::suspend_layout(::mforms::View *self, bool flag)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  if (view)
    view->suspend_layout(flag);
}

void ViewImpl::size_changed()
{
  if (get_outer() && get_outer()->is_realized())
  {
    ::mforms::View* owner_view = dynamic_cast< ::mforms::View*>(owner);
    if (owner_view)
      (*owner_view->signal_resized())();
  }
}

void ViewImpl::on_focus_grab()
{
  if (get_outer() && get_outer()->is_realized())
  {
    ::mforms::View* owner_view = dynamic_cast< ::mforms::View*>(owner);
    if (owner_view)
      owner_view->focus_changed();
  }
}

void ViewImpl::setup()
{
  get_outer()->signal_size_allocate().connect(sigc::hide(sigc::mem_fun(this, &ViewImpl::size_changed)));
  get_outer()->signal_grab_focus().connect(sigc::mem_fun(this, &ViewImpl::on_focus_grab));
  get_outer()->signal_realize().connect(sigc::mem_fun(this, &ViewImpl::size_changed));
  get_outer()->show();
}


void ViewImpl::move_child(ViewImpl *child, int x, int y)
{
  throw std::logic_error("container does not implement required method");
}

void ViewImpl::set_front_color(::mforms::View *self, const std::string &color)
{
  ViewImpl *view= self->get_data<ViewImpl>();
  Gtk::Widget *w = view->get_inner();
  if (w)
  {
    if (color.empty())
      w->unset_fg(Gtk::STATE_NORMAL);
    else
    {
      Gdk::Color c(color.substr(1));
      w->get_colormap()->alloc_color(c);
      w->modify_fg(Gtk::STATE_NORMAL, c);
    }
  }
  view->set_front_color(color);
}

void ViewImpl::set_back_color(::mforms::View *self, const std::string &color)
{
  ViewImpl *view= self->get_data<ViewImpl>();
  if (view)
  {
    Gtk::Widget *w = view->get_inner();
    if (w)
    {
      if (color.empty())
      {
        w->unset_bg(Gtk::STATE_NORMAL);
        w->unset_base(Gtk::STATE_NORMAL);
      }
      else
      {
        Gdk::Color gcolor(color);
        w->get_colormap()->alloc_color(gcolor);
        w->modify_bg(Gtk::STATE_NORMAL, gcolor);
        w->modify_base(Gtk::STATE_NORMAL, gcolor);
      }
    }
  }
}

std::string ViewImpl::get_front_color(::mforms::View *self)
{
  ViewImpl *view= self->get_data<ViewImpl>();
  //TODO
  return "";
}

std::string ViewImpl::get_back_color(::mforms::View *self)
{
  ViewImpl *view= self->get_data<ViewImpl>();
  double *color = (double*)g_object_get_data(G_OBJECT(view->get_inner()->gobj()), "bg");
  if (color)
  {
    char buffer[32];
    sprintf(buffer, "#%02x%02x%02x", (int)(color[0]*255), (int)(color[1]*255), (int)(color[2]*255));
    return buffer;
  }
  return "";
}


bool ViewImpl::on_expose_event(GdkEventExpose *event, Gtk::Widget *target)
{
  int x, y;
  int iwidth, fwidth;
  int iheight, fheight;

  if (_back_image)
  {
    x = 0;
    y = 0;
    iwidth = _back_image->get_width();
    iheight = _back_image->get_height();
    fwidth = target->get_width();
    fheight = target->get_height();

    switch (_back_image_alignment)
    {
      case mforms::TopLeft:
        x = 0;
        y = 0;
        break;
      case mforms::TopCenter:
        x = (iwidth+fwidth) / 2;
        y = 0;
        break;
      case mforms::TopRight:
        x = fwidth - iwidth;
        y = 0;
        break;
      case mforms::MiddleLeft:
        x = 0;
        y = (iheight+fheight) / 2;
        break;
      case mforms::MiddleCenter:
        x = (iwidth+fwidth) / 2;
        y = (iheight+fheight) / 2;
        break;
      case mforms::MiddleRight:
        x = fwidth - iwidth;
        y = (iheight+fheight) / 2;
        break;
      case mforms::BottomLeft:
        x = 0;
        y = fheight - iheight;
        break;
      case mforms::BottomCenter:
        x = (iwidth+fwidth) / 2;
        y = fheight - iheight;
        break;
      case mforms::BottomRight:
        x = fwidth - iwidth;
        y = fheight - iheight;
        break;
      default:
        break;
    }

    _back_image->render_to_drawable(target->get_window(), target->get_style()->get_fg_gc(Gtk::STATE_NORMAL), 0, 0, x, y, iwidth, iheight, Gdk::RGB_DITHER_NONE, 0, 0);

    return true;
  }
  return false;
}

void ViewImpl::set_back_image(::mforms::View *self, const std::string &path, mforms::Alignment ali)
{
  ViewImpl *view= self->get_data<ViewImpl>();
  view->set_back_image(path, ali);
}

void ViewImpl::set_back_image(const std::string &path, mforms::Alignment alig)
{
  if (path.empty())
  {
    _back_image.reset();
    return;
  }

  std::string file = mforms::App::get()->get_resource_path(path);
  try
  {
    _back_image = Gdk::Pixbuf::create_from_file(file);
    _back_image_alignment = alig;
  }
  catch (Glib::Error &exc)
  {
    log_error("Could not load image for background: %s: %s\n", file.c_str(), exc.what().c_str());
  }
}

void ViewImpl::flush_events(::mforms::View *self)
{
  while (Gtk::Main::events_pending())
    Gtk::Main::iteration();
}

void ViewImpl::set_padding(::mforms::View *self, int left, int top, int right, int bottom)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  view->set_padding_impl(left, top, right, bottom);
}

void ViewImpl::set_padding_impl(int left, int top, int right, int bottom)
{}


bool expose_event_slot(GdkEventExpose* event, Gtk::Widget* widget)
{
  GdkWindow* wnd = event->window;
  double *color = (double*)g_object_get_data(G_OBJECT(widget->gobj()), "bg");
  GObject *obj = G_OBJECT(widget->gobj());
  //log_debug3("expose event, obj %p, color %p", obj, color);

  if (color)
  {
    cairo_t *cr = gdk_cairo_create(wnd);

    cairo_set_source_rgb(cr, *color, *(color + 1), *(color + 2));
    cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);

    gdk_cairo_region(cr, event->region);

    cairo_fill(cr);

    cairo_destroy(cr);
  }
  return false;
}

void set_bgcolor(Gtk::Widget* w, const std::string& strcolor)
{
  bool allocated = false;
  double *color = 0;
  color = (double*)w->get_data("bg");
  if (!color)
  {
    color = (double*)malloc(3 * sizeof(double));
    allocated = true;
  }

  if (html_color_to_triplet(strcolor.c_str(), color, color + 1, color + 2))
  {
    if (allocated)
    {
      g_object_set_data_full(G_OBJECT(w->gobj()), "bg", color, free);
      //GObject *obj = G_OBJECT(w->gobj());
      //log_debug3("set_bgcolor, obj %p, color %p", obj, color);
    }
  }
}


void ViewImpl::focus(::mforms::View *self)
{
  ViewImpl *view = self->get_data<ViewImpl>();
  if (view && view->get_inner())
    view->get_inner()->grab_focus();
}


void ViewImpl::init()
{
  ::mforms::ControlFactory *f= ::mforms::ControlFactory::get_instance();

  f->_view_impl.destroy              = &ViewImpl::destroy;
  f->_view_impl.get_width            = &ViewImpl::get_width;
  f->_view_impl.get_height           = &ViewImpl::get_height;
  f->_view_impl.get_preferred_width  = &ViewImpl::get_preferred_width;
  f->_view_impl.get_preferred_height = &ViewImpl::get_preferred_height;
  f->_view_impl.set_size             = &ViewImpl::set_size;

  f->_view_impl.get_x                = &ViewImpl::get_x;
  f->_view_impl.get_y                = &ViewImpl::get_y;
  f->_view_impl.set_position         = &ViewImpl::set_position;
  f->_view_impl.client_to_screen     = &ViewImpl::client_to_screen;

  f->_view_impl.show                 = &ViewImpl::show;
  f->_view_impl.is_shown             = &ViewImpl::is_shown;

  f->_view_impl.set_tooltip          = &ViewImpl::set_tooltip;
  f->_view_impl.set_font             = &ViewImpl::set_font;
  f->_view_impl.set_name             = &ViewImpl::set_name;

  f->_view_impl.set_enabled          = &ViewImpl::set_enabled;
  f->_view_impl.is_enabled           = &ViewImpl::is_enabled;

  f->_view_impl.suspend_layout       = &ViewImpl::suspend_layout;
  f->_view_impl.relayout             = &ViewImpl::relayout;
  f->_view_impl.set_needs_repaint    = &ViewImpl::set_needs_repaint;
  f->_view_impl.set_front_color      = &ViewImpl::set_front_color;
  f->_view_impl.get_front_color      = &ViewImpl::get_front_color;
  f->_view_impl.set_back_color       = &ViewImpl::set_back_color;
  f->_view_impl.get_back_color       = &ViewImpl::get_back_color;
  f->_view_impl.set_back_image       = &ViewImpl::set_back_image;
  f->_view_impl.flush_events         = &ViewImpl::flush_events;
  f->_view_impl.set_padding          = &ViewImpl::set_padding;
  f->_view_impl.focus                = &ViewImpl::focus;
};


Gtk::Widget* ViewImpl::get_widget_for_view(mforms::View *view)
{
  ViewImpl *vi = view->get_data<ViewImpl>();

  if (vi)
  {
    Gtk::Widget *w = vi->get_outer();
    w->set_data("mforms::View", view);
    return w;
  }
  return NULL;
}


mforms::View *ViewImpl::get_view_for_widget(Gtk::Widget *w)
{
  mforms::View *view = (mforms::View*)w->get_data("mforms::View");
  if (view)
    return view;
  return NULL;
}


};
};
