#include "gtk/lf_mforms.h"

#include "lf_wizard.h"
#include "lf_utilities.h"
#include "mforms_grttreeview.h"
#include <mforms/mforms.h>
#include <gtkmm.h>
#include <stdio.h>
#include <sys/wait.h>
#include "program.h"
#include "gtk_helpers.h"
#include "base/string_utilities.h"
#include "base/log.h"
#include <gdk/gdkx.h>

using base::strfmt;

#include "config.h"

#ifdef EDITION_OSS
# define SPLASH_IMAGE "wb_splashscreen_oss.png"
#else
# ifdef EDITION_SE
#  define SPLASH_IMAGE "wb_splashscreen_se.png"
# endif
#endif

//==============================================================================
// We need recursive mutex to lock gtk main loop. For that we supply enter/leave
// callbacks to glib via gdk_threads_set_lock_functions(). Out lock/unlock
// functions operate on static rec mutex.
static GStaticRecMutex custom_gdk_rec_mutex = G_STATIC_REC_MUTEX_INIT;

static void custom_gdk_threads_enter()
{
  g_static_rec_mutex_lock(&custom_gdk_rec_mutex);
}

static void custom_gdk_threads_leave()
{
  g_static_rec_mutex_unlock(&custom_gdk_rec_mutex);
}

inline void init_gdk_thread_callbacks()
{
  gdk_threads_set_lock_functions(G_CALLBACK(&custom_gdk_threads_enter), G_CALLBACK(&custom_gdk_threads_leave));
}

//==============================================================================


static Gtk::Window *show_splash()
{
  {    
    const char *datadir= getenv("MWB_DATA_DIR");
    if (datadir)
    {
      Gtk::RC::add_default_file(std::string(datadir).append("/workbench.rc"));
      
      Gtk::Window *window= new Gtk::Window(Gtk::WINDOW_TOPLEVEL);
      window->set_decorated(false);
      window->set_default_size(560, 322);
      window->modify_bg_pixmap(Gtk::STATE_NORMAL, std::string(datadir).append("/images/"SPLASH_IMAGE));
      window->set_position(Gtk::WIN_POS_CENTER);
      
      Gtk::Fixed *fixed= Gtk::manage(new Gtk::Fixed());
      window->add(*fixed);
      
      Gtk::Label *label= Gtk::manage(new Gtk::Label("Version " PACKAGE_VERSION));
      fixed->put(*label, 460, 150);
      label->set_alignment(1.0, 0.0);
      label->set_justify(Gtk::JUSTIFY_RIGHT);
      label->modify_fg(Gtk::STATE_NORMAL, label->get_style()->get_black());
      fixed->show();
      label->show();
      
      window->show_now();
      
      return window;
    }
  }
  return 0;
}

static bool close_splash(Gtk::Window *w)
{
  w->hide();
  delete w;
  return false;
}


int main(int argc, char **argv)
{

  // disable gnome keyring if it's not running
  if (!getenv("GNOME_KEYRING_CONTROL") && !getenv("GNOME_KEYRING_SOCKET"))
  {
    g_message("Gnome keyring daemon seems to not be available. Stored passwords will be lost once quit");
    setenv("WB_NO_GNOME_KEYRING", "1", 1);
  }

#ifdef ENABLE_DEBUG
  if (getenv("DEBUG_CRITICAL"))
    g_log_set_always_fatal((GLogLevelFlags)(G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_ERROR));
#endif

  init_gdk_thread_callbacks(); // This call MUST be before g_threads_init is called
  g_thread_init(NULL);
  gdk_threads_init();
  // process cmdline options
  int i= 1;
  wb::WBOptions wboptions;

  wboptions.user_data_dir = std::string(g_get_home_dir()).append("/.mysql/workbench");
  base::Logger log(wboptions.user_data_dir);

  wboptions.basedir = getenv("MWB_DATA_DIR");
  wboptions.plugin_search_path = getenv("MWB_PLUGIN_DIR");
  wboptions.struct_search_path = wboptions.basedir + "/grt";
  wboptions.module_search_path = getenv("MWB_MODULE_DIR");

  g_set_application_name("MySQL Workbench");

  if (!wboptions.parse_args(argv, argc))
    return 1;

  if (getenv("WB_VERBOSE"))
    wboptions.verbose = true;

  if (!getenv("MWB_DATA_DIR"))
  {
    char *script_name = g_strdup(argv[0]);
    if (g_str_has_suffix(script_name, "-bin"))
      script_name[strlen(script_name)-4]= 0;
    printf("To start MySQL Workbench, use %s instead of %s\n", script_name, argv[0]);
    exit(1);
  }

  mforms::gtk::init(getenv("WB_FORCE_SYSTEM_COLORS") != NULL);
  mforms::gtk::GRTTreeViewImpl::init();
  mforms::gtk::WizardImpl::set_icon_path(wboptions.basedir+"/images");

  Gtk::RC::add_default_file(wboptions.basedir+"/workbench.rc");

  gdk_threads_enter();
  Gtk::Main app(argc, argv);

  if (getenv("XSYNC"))
  {
    g_message("Enabling XSynchronize()");
    XSynchronize(gdk_display, 1);
  }

  if (!wboptions.quit_when_done || wboptions.no_logo)
  {
    Gtk::Window *splash = show_splash();
    splash->get_window()->freeze_updates(); // Added to stop idle window update requests storm in gnome-3

    while (app.events_pending())
      app.iteration(false);

    Glib::signal_idle().connect(sigc::bind(sigc::ptr_fun(close_splash), splash));
  }

  Program program(wboptions);
  
  mforms::gtk::check();
  
  for (;;)
  {
    try
    {
      app.run();
      break;
    }
    catch (const std::exception &exc)
    {
      g_warning("ERROR: unhandled exception %s", exc.what());

      Gtk::MessageDialog dlg(strfmt("<b>Unhandled Exception</b>\nAn unhandled exception has occurred (%s).\nInternal state may be inconsystent, please save your work to a temporary file and restart Workbench.\nPlease report this with details on how to repeat at http://bugs.mysql.com", exc.what()),
                             true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
      dlg.set_title(_("Error"));

      dlg.set_transient_for(*get_mainwindow());
      dlg.run();
    }
    catch (const Glib::Exception &exc)
    {
      g_warning("ERROR: unhandled exception %s", exc.what().c_str());

      Gtk::MessageDialog dlg(strfmt("<b>Unhandled Exception</b>\nAn unhandled exception has occurred (%s).\nInternal state may be inconsystent, please save your work to a temporary file and restart Workbench.\nPlease report this with details on how to repeat at http://bugs.mysql.com", exc.what().c_str()),
                             true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
      dlg.set_title(_("Error"));
      dlg.set_transient_for(*get_mainwindow());
      dlg.run();
    }
    catch (...)
    {
      g_warning("ERROR: unhandled exception");

      Gtk::MessageDialog dlg(strfmt("<b>Unhandled Exception</b>\nAn unhandled exception has occurred.\nInternal state may be inconsystent, please save your work to a temporary file and restart Workbench.\nPlease report this with details on how to repeat at http://bugs.mysql.com"),
                             true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
      dlg.set_title(_("Error"));

      dlg.set_transient_for(*get_mainwindow());
      dlg.run();
    }
  }

  program.shutdown();
  gdk_threads_leave();

  return 0;
}








#ifdef ENABLE_DEBUG
#ifdef __GNUC__

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <dlfcn.h>
#include <cxxabi.h>
#include <sys/time.h>

static struct timeval start_time;
static int trace_depth= 0;
static FILE *trace_file= 0;
bool trace_on= true;

extern "C" {

// instrumentation code for tracing function calls. must compile with -finstrument-functions
void __cyg_profile_func_enter(void *func_address, void *call_site) __attribute__((no_instrument_function));
void __cyg_profile_func_exit(void *func_address, void *call_site) __attribute__((no_instrument_function));
static char *resolve_function(void *addr) __attribute__((no_instrument_function));

static char *resolve_function(void *addr)
{
  Dl_info info;
  int s;

  dladdr(addr, &info);

  return __cxxabiv1::__cxa_demangle(info.dli_sname, NULL, NULL, &s);
}

void __cyg_profile_func_enter(void *func_address, void *call_site)
{
  if (!trace_file)
  {
    gettimeofday(&start_time, NULL);
    trace_file= fopen("trace.txt", "w+");
  }
  
  if (trace_on)
  {
    char *s= resolve_function(func_address);
    struct timeval t;
    gettimeofday(&t, NULL);
    

    ++trace_depth;

    fprintf(trace_file ?: stdout, "TRACE:%.4f:%*senter %s\n",
            (t.tv_sec + t.tv_usec / 1000000.0) - (start_time.tv_sec + start_time.tv_usec / 1000000.0),
            trace_depth, "", s);
    free(s);
  }
}


void __cyg_profile_func_exit(void *func_address, void *call_site)
{
  if (trace_on)
  {
    char *s= resolve_function(func_address);
    struct timeval t;
    gettimeofday(&t, NULL);

    fprintf(trace_file ?: stdout, "TRACE:%.4f:%*sleave %s\n",
            (t.tv_sec + t.tv_usec / 1000000.0) - (start_time.tv_sec + start_time.tv_usec / 1000000.0),
            trace_depth, "", s);

    --trace_depth;

    free(s);
  }
}

};

#endif
#endif // ENABLE_DEBUG
