/* 
 * Copyright (c) 2008, 2009, 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 <memory>
#include <gtk/gtk.h>
#include <gtkmm/viewport.h>
#include <gtkmm/separator.h>
#include "gtk_helpers.h"
#include "properties_tree.h"
#include "grtpp_util.h"
#include "grt/grt_value_inspector.h"

//------------------------------------------------------------------------------
PropertyValue::PropertyValue(PropertyInspector* owner, const bec::NodeId& node)
              : _owner(owner)
              , _prop_node(node)
{
  add(_text);
  _text.set_alignment(Gtk::ALIGN_LEFT);
  _text.set_single_line_mode(true);
  _text.set_ellipsize(Pango::ELLIPSIZE_END);
  _text.show();
  _conn = signal_event().connect(sigc::mem_fun(this, &PropertyValue::on_event));

  modify_bg(Gtk::STATE_NORMAL, Gdk::Color("#f5f5f5"));
}
    
//------------------------------------------------------------------------------
PropertyValue::~PropertyValue()
{
  _conn.disconnect();
}

//------------------------------------------------------------------------------
void PropertyValue::set_text(const std::string& text)
{
  _text.set_text(text);
}

//------------------------------------------------------------------------------
void PropertyValue::stop_edit()
{
  stop_editing();
  remove();
  add(label());
}

//------------------------------------------------------------------------------
void PropertyValue::start_edit()
{
  remove();
  add(editor());
  
  editor().property_width_request() = get_width()-2;
  editor().show();  

  start_editing();
}

//------------------------------------------------------------------------------
bool PropertyValue::on_event(GdkEvent* event)
{
  if ( event->type == GDK_BUTTON_PRESS && event->button.button == 1 )
  {
    _owner->handle_click(this);
    return true;
  }
  else if ( event->type == GDK_KEY_RELEASE )
  {
    const int key = event->key.keyval;                                                                                                  
    if ( key == GDK_Escape)
    {  
      _owner->edit_canceled();
      stop_edit();
      return true;
    }
    else if (key == GDK_Return)
    {   
      set_text(get_new_value());
      _owner->edit_done(this, true);
      stop_edit();
      return true;
    }
  }
  return false;
}

//==============================================================================
PropertyString::PropertyString(PropertyInspector* owner, const bec::NodeId& node) 
               : PropertyValue(owner,node)
{
  _entry.set_has_frame(false);
  _entry.set_alignment(Gtk::ALIGN_LEFT);
}

//------------------------------------------------------------------------------
std::string PropertyString::get_new_value() const
{
  return _entry.get_text();
}

//------------------------------------------------------------------------------
void PropertyString::start_editing()
{
  _entry.set_text(get_text());
  _entry.show();
}

//------------------------------------------------------------------------------
Gtk::Widget& PropertyString::editor()
{
  return _entry;
}

//==============================================================================
PropertyBool::PropertyBool(PropertyInspector* owner, const bec::NodeId& node) 
               : PropertyValue(owner,node)
{
  _conn = _button.signal_toggled().connect(sigc::mem_fun(this, &PropertyBool::on_value_changed));

  start_edit(); // That's because when PropertyValue set ups label its vtable points to PropertyValue
                // So we need to update Table with our control. As we have the same CheckButton
                // for label and for editor it is ok to call start_edit. It will replace label
                // widget with editor widget. But at this moment label() and editor() will
                // return PropertyBool's widgets.
}

//------------------------------------------------------------------------------
std::string PropertyBool::get_new_value() const
{
  return _button.get_active() ? "1" : "0";
}

//------------------------------------------------------------------------------
void PropertyBool::set_text(const std::string& text)
{   
  _conn.block();
  _button.set_active(text == "1" ? 1 : 0);
  _conn.unblock();
}

//------------------------------------------------------------------------------
std::string PropertyBool::get_text() const
{
  return _button.get_active() ? "1" : "0";
}

//------------------------------------------------------------------------------
void PropertyBool::start_editing()
{
  _button.show();
}

//------------------------------------------------------------------------------
Gtk::Widget& PropertyBool::editor()
{
  return _button;
}

//------------------------------------------------------------------------------
Gtk::Widget& PropertyBool::label()
{
  return _button;
}

//------------------------------------------------------------------------------
void PropertyBool::on_value_changed()
{
  PropertyValue::set_text(_button.get_active() ? "True" : "False");
  _owner->edit_done(this);
}

//==============================================================================
PropertyColor::PropertyColor(PropertyInspector* owner, const bec::NodeId& node)
              : PropertyValue(owner,node)
              , _button("...")
{
  _hbox.pack_start(_entry);
  _hbox.pack_end(_button, false, false);
  _hbox.show_all();

  _button.signal_clicked().connect(sigc::mem_fun(this, &PropertyColor::show_dlg));
}

//------------------------------------------------------------------------------
std::string PropertyColor::get_new_value() const
{
  return _entry.get_text();
}

//------------------------------------------------------------------------------
void PropertyColor::start_editing()
{
  _entry.set_text(get_text());
}

//------------------------------------------------------------------------------
void PropertyColor::show_dlg()
{
  _dlg.get_colorsel()->set_current_color(Gdk::Color(get_text()));
  const int resp = _dlg.run();

  if ( resp == Gtk::RESPONSE_OK )
  {     
    const Gdk::Color color(_dlg.get_colorsel()->get_current_color());
    char buffer[32];
    snprintf(buffer, sizeof(buffer)-1, "#%02x%02x%02x", color.get_red()>>8, color.get_green()>>8, color.get_blue()>>8);
    buffer[sizeof(buffer)-1] = 0;

    _entry.set_text(buffer);
    set_text(buffer);
    _owner->edit_done(this);
    _entry.grab_focus();
  }
  _dlg.hide();
}

//------------------------------------------------------------------------------
Gtk::Widget& PropertyColor::editor()    
{
  return _hbox;
}

//==============================================================================
PropertyText::PropertyText(PropertyInspector* owner, const bec::NodeId& node)
             : PropertyValue(owner, node)
             , _ctrl(false)
{
  _scroll.add(_text);
  _wnd.get_vbox()->add(_scroll);
  _wnd.set_position(Gtk::WIN_POS_MOUSE);
  _wnd.set_has_separator(false);
  _wnd.set_transient_for(*get_mainwindow());
  _wnd.resize(128,96);
  _text.signal_event().connect(sigc::mem_fun(this, &PropertyText::handle_event));
}

//------------------------------------------------------------------------------
std::string PropertyText::get_new_value() const
{
  Gtk::TextView *tv = const_cast<Gtk::TextView*>(&_text);
  return tv->get_buffer()->get_text();
}

//------------------------------------------------------------------------------
void PropertyText::start_editing()
{
  _text.get_buffer()->set_text(get_text());
  _wnd.show_all();
}

//------------------------------------------------------------------------------
void PropertyText::stop_editing()
{
  _wnd.hide();
}

//------------------------------------------------------------------------------
bool PropertyText::handle_event(GdkEvent* event)
{
  if ( event->type == GDK_KEY_PRESS )
  {
    const int key = event->key.keyval;                                                                                                  
    if ( key == GDK_Control_L || key == GDK_Control_R )
      _ctrl = true;
  }
  if ( event->type == GDK_KEY_RELEASE )
  {
    const int key = event->key.keyval;                                                                                                  
    if ( key == GDK_Control_L || key == GDK_Control_R )
      _ctrl = false;
      
    if ( key == GDK_Escape)
    {
      _owner->edit_canceled();
      stop_edit();
      return true;
    }
    else if (key == GDK_KP_Enter || (_ctrl && key == GDK_Return))
    {   
      set_text(get_new_value());
      _owner->edit_done(this, true);
      stop_edit();
      return true;
    }
  }
  return false;
}

//==============================================================================
PropertyInspector::PropertyInspector()
                  : _edited_property(0)
                  , _updating(0)
{
  _table = new Gtk::Table();
  _table->set_border_width(4);
  add(*_table);
}

//------------------------------------------------------------------------------
static void remove_child(Gtk::Widget& w)
{
  w.hide();
  delete &w;
}

//------------------------------------------------------------------------------
PropertyInspector::~PropertyInspector()
{
  clear();
  delete _table;
}

//------------------------------------------------------------------------------
void PropertyInspector::clear()
{
  _edited_property = 0;
  _table->foreach(sigc::ptr_fun(&::remove_child));
  _properties.clear();
  remove();
  delete _table;
  _table = new Gtk::Table();
  _table->set_border_width(4);
  _table->set_row_spacings(2);
  add(*_table);
  _table->show();
  Gtk::Viewport* vp = static_cast<Gtk::Viewport*>(_table->get_parent());
  vp->modify_bg(Gtk::STATE_NORMAL, Gdk::Color("#ffffff"));
}

//------------------------------------------------------------------------------
void PropertyInspector::populate()
{
  _updating = 1;
  clear();
  
  _table->property_n_columns() = 2;
  const int count = _get_properties_count();
  if ( count > 0 )
  {
    _table->property_n_rows() = count+1;
    
    for ( int i = 0; i < count; ++i )
    {
      const int i2 = i*2;
      bec::NodeId node;
      node.append(i);
      
      const std::string type  = _get_type_slot(node);
      const std::string name  = _get_value_slot(node,true); // true means name 

      Gtk::Label* name_label = new Gtk::Label(name);
      name_label->property_xalign() = 0.0;
      _table->attach(*name_label, 0, 1, i2, i2+1, Gtk::FILL, Gtk::AttachOptions());

      //g_message("prop %s, type %s", name.c_str(), type.c_str());
      PropertyValue *prop;
      if (type.empty())
        prop = new PropertyString(this, node);
      else if ("bool" == type)
        prop = new PropertyBool(this, node);
      else if ("color" == type)
        prop = new PropertyColor(this, node);
      else if ("longtext" == type)
        prop = new PropertyText(this, node);
      else  
        prop = new PropertyString(this, node);
  
      _table->attach(*prop, 1, 2, i2, i2+1, Gtk::EXPAND|Gtk::FILL, Gtk::AttachOptions());

      Gtk::HSeparator *sep = new Gtk::HSeparator();
      _table->attach(*sep, 0, 1, i2+1, i2+2, Gtk::FILL, Gtk::AttachOptions());
      sep = new Gtk::HSeparator();
      _table->attach(*sep, 1, 2, i2+1, i2+2, Gtk::FILL, Gtk::AttachOptions());

      _properties.push_back(prop);
    }
  }
  
  _table->show_all_children();
  _updating = 0;
}
    
//------------------------------------------------------------------------------
void PropertyInspector::update()
{
  _updating = 1;
  const int count = _properties.size();

  if ( count > 0 )
  {
    PropertyValue* prop = 0;
    for ( int i = 0; i < count; ++i )
    {
      prop = _properties[i];
      prop->set_text(_get_value_slot(prop->node(), false));
    }
  }
  _updating = 0;
}
    

//------------------------------------------------------------------------------
void PropertyInspector::handle_click(PropertyValue* property)
{
  if ( !_updating && _edited_property != property )
  {
    if ( _edited_property )
    {
      edit_done(_edited_property);
      _edited_property->stop_edit();
    }
    
    _edited_property = property;
    property->start_edit();
  }
}

//------------------------------------------------------------------------------
void PropertyInspector::edit_done(PropertyValue* property, const bool finish)
{
  if ( !_updating )
  {
    _set_value_slot(property->node(), property->get_new_value(), property->type());
    property->set_text(_get_value_slot(property->node(), false));
    if ( finish )
      _edited_property = 0;
  }
}
    
//------------------------------------------------------------------------------
void PropertyInspector::edit_canceled()
{
  _edited_property = 0;
}

//------------------------------------------------------------------------------
PropertiesTree::PropertiesTree(wb::WBContextUI *wb)
  : _wb(wb), _inspector(0)
{
  pack_start(_inspector_view, true, true);

  _inspector_view.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
  _inspector_view.set_shadow_type(Gtk::SHADOW_IN);

  show_all();
}

//------------------------------------------------------------------------------
PropertiesTree::~PropertiesTree()
{
  delete _inspector;
}

//------------------------------------------------------------------------------
void PropertiesTree::update()
{
  std::vector<std::string> items;
  
  delete _inspector;
  _inspector = 0;
  _inspector = _wb->create_inspector_for_selection(_wb->get_active_main_form(), items);
  
  _inspector_view.clear();
  if (_inspector)
  {
    _inspector_view.set_count_slot(sigc::mem_fun(this, &PropertiesTree::get_properties_count));
    _inspector_view.set_value_slot_setter(sigc::mem_fun(this, &PropertiesTree::set_value));
    _inspector_view.set_value_slot_getter(sigc::mem_fun(this, &PropertiesTree::get_value));
    _inspector_view.set_type_slot_getter(sigc::mem_fun(this, &PropertiesTree::get_prop_type));

    _inspector_view.populate();
    _inspector_view.update();

    _inspector->set_refresh_ui_slot(sigc::mem_fun(this, &PropertiesTree::refresh));
  }
}


//------------------------------------------------------------------------------
void PropertiesTree::refresh()
{
  if (_inspector)
    _inspector_view.update();
}

//------------------------------------------------------------------------------
int PropertiesTree::get_properties_count() const
{
  return _inspector ? _inspector->count() : 0;
}

//------------------------------------------------------------------------------
std::string PropertiesTree::get_prop_type(const bec::NodeId& node) const
{
  std::string type;
  _inspector->get_field(node, ::bec::ValueInspectorBE::EditMethod, type);
  return type;  
}

//------------------------------------------------------------------------------
void PropertiesTree::set_value(const bec::NodeId& node, const std::string& value, const grt::Type type)
{ 
  if (node.is_valid())
  {
    switch ( type )
    {
      case grt::IntegerType:
      {
        const int iv = atoi(value.c_str());
        _inspector->set_field(node, ::bec::ValueInspectorBE::Value, iv);
        break;
      }
      case grt::DoubleType:
      {
        const int dv = atof(value.c_str());
        _inspector->set_field(node, ::bec::ValueInspectorBE::Value, dv);
        break;
      }
      case grt::StringType:
      {
        _inspector->set_field(node, ::bec::ValueInspectorBE::Value, value);
        break;
      }
      default:
        g_message("PropertiesTree::set_value: unhandled value type");
    }
  }
}

//------------------------------------------------------------------------------
std::string PropertiesTree::get_value(const bec::NodeId& node, const bool is_name) const
{
  if (node.is_valid())
  {
    std::string val;
    _inspector->get_field(node, is_name ? bec::ValueInspectorBE::Name : bec::ValueInspectorBE::Value, val);
    return val;
  }
  return "";
}
