/* 
 * © 2007-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#ifndef _EDITOR_BASE_H_
#define _EDITOR_BASE_H_


#include "ui_form.h"
#include <grtpp.h>
#include "grt/grt_manager.h"
#include <grtpp_undo_manager.h>
#include "sqlide/sql_editor_be.h"
#include "refresh_ui.h"

#include "wbpublic_public_interface.h"


// How Editor Undo Works
//
// - Each editor has an undo stack
// - When the editor is created, it will save the ref to the object being
// edited and take a snapshot of it
// - Any changes are made to the snapshot copy
// - Whenever a change is started, a snapshot of the snapshot is taken
// and when the change is finished, the diff between snapshots is saved in the stack
// and the new snapshot replaces the old one, which is discarded
// - When an undo is requested, a snapshot is also created and diffed, for use in redo
// - When the user presses Apply, the latest version of object is diffed against the
// live object and the changes are applied to the live object
//
class GrtObject;


namespace bec {
  struct ChangeSet;

  struct WBPUBLICBACKEND_PUBLIC_FUNC RefreshCentry
  {
    BaseEditor &_editor;
    RefreshCentry(BaseEditor &editor);

    ~RefreshCentry();
  };
  
  
  class WBPUBLICBACKEND_PUBLIC_FUNC UndoObjectChangeGroup : public grt::UndoGroup
  {
    std::string _object_id;
    std::string _member;
    
  public:
    UndoObjectChangeGroup(const std::string &object_id, const std::string &member);

    virtual bool matches_group(UndoGroup *group) const;
  };


  class WBPUBLICBACKEND_PUBLIC_FUNC BaseEditor : public UIForm, public RefreshUI 
  {
  public:
    BaseEditor(GRTManager *grtm, const grt::Ref<GrtObject> &object);
    virtual ~BaseEditor();
    
    virtual std::string get_form_context_name() const;

    virtual Sql_editor * get_sql_editor() { return NULL; }

    virtual bool should_close_on_delete_of(const std::string &oid) { return get_object().id() == oid; }

    // must return a copy of the object
    virtual grt::Ref<GrtObject> get_object()= 0;

    //sigc::signal<void, BaseEditor*> signal_unlocked() { return _unlocked_signal; };
    void add_unlocked_handler(const sigc::slot<void, BaseEditor*>& slot) { _unlocked_signal.connect(slot); }

    grt::GRT *get_grt() { return _grtm->get_grt(); }
    GRTManager *get_grt_manager() { return _grtm; }

  public: // of interest for frontend
    // lock when frontend starts using the editor, unlock when done
    void lock();
    void unlock();

  protected:
    GRTManager *_grtm;

    std::set<std::string> _ignored_object_fields_for_ui_refresh;

    sigc::signal<void, BaseEditor*> _unlocked_signal;

    void add_listeners(const grt::Ref<GrtObject> &object);

    void run_from_grt(const sigc::slot<void> &slot);

  private:
    friend class AutoUndoEdit;

    grt::Ref<GrtObject> _object;
    void object_member_changed(const std::string &member, const grt::ValueRef &ovalue);
    virtual void on_object_changed();

    void undo_applied();

    bool _locked;
  };

  
  class WBPUBLICBACKEND_PUBLIC_FUNC AutoUndoEdit : public grt::AutoUndo
  {
    typedef std::pair<sigc::connection, sigc::connection> SignalConnections;
    
    static void undo_applied(grt::UndoAction *applied, grt::UndoGroup *group, BaseEditor *editor)
    {
      if (group == applied)
        editor->undo_applied();
    }
    
    static void *editor_destroyed(void *data)
    {
      SignalConnections *sigs= reinterpret_cast<SignalConnections*>(data);
      
      sigs->first.disconnect();
      sigs->second.disconnect();
      delete sigs;
      
      return NULL;
    }
    
    void mark_for_delete_when_destroyed(BaseEditor *editor, sigc::connection &conn1, sigc::connection &conn2)
    {
      SignalConnections *sigs= new SignalConnections(conn1, conn2);
  
      editor->add_destroy_notify_callback(sigs, AutoUndoEdit::editor_destroyed);
    }

  public:
    AutoUndoEdit(BaseEditor *editor)
      : grt::AutoUndo(editor->get_grt())
    {
      if (group)
      {
        sigc::connection conn1, conn2;
          
        conn1= editor->get_grt()->get_undo_manager()->
          signal_undo().connect(sigc::bind(sigc::ptr_fun(&AutoUndoEdit::undo_applied), group, editor));

        conn2= editor->get_grt()->get_undo_manager()->
          signal_redo().connect(sigc::bind(sigc::ptr_fun(&AutoUndoEdit::undo_applied), group, editor));
        
        mark_for_delete_when_destroyed(editor, conn1, conn2);
      }
    }

    AutoUndoEdit(BaseEditor *editor, const grt::ObjectRef &object, const std::string &member)
      : grt::AutoUndo(editor->get_grt(), new UndoObjectChangeGroup(object.id(), member))
    {
      if (group)
      {
        sigc::connection conn1, conn2;
    
        conn1= editor->get_grt()->get_undo_manager()->
          signal_undo().connect(sigc::bind(sigc::ptr_fun(&AutoUndoEdit::undo_applied), group, editor));

        conn2= editor->get_grt()->get_undo_manager()->
          signal_redo().connect(sigc::bind(sigc::ptr_fun(&AutoUndoEdit::undo_applied), group, editor));
        
        mark_for_delete_when_destroyed(editor, conn1, conn2);
      }
    }
  };
};

#endif /* _EDITOR_BASE_H_ */
