#include "wb_config.h"
#include "lf_mforms.h"
#include "lf_utilities.h"
#include "lf_wizard.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 "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 pid_t show_splash(Glib::Dispatcher &dispatcher, int argc, char **argv)
{
  pid_t pid= fork();

  if (pid == 0)
  {
    Gtk::Main main(argc, argv);
    
    dispatcher.connect(sigc::ptr_fun(&Gtk::Main::quit));
    
    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_POPUP);
      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, 270, 150);
      label->modify_fg(Gtk::STATE_NORMAL, label->get_style()->get_white());
      fixed->show();
      label->show();
      
      window->show_now();    
      
      main.run();
    }
    _exit(0);
  }
  return pid;
}


static bool close_splash(Glib::Dispatcher *disp, pid_t splash_pid)
{
  disp->emit();
  
  delete disp;
  
  waitpid(splash_pid, NULL, 0);
  
  return false;
}


static void show_help()
{
  printf("mysql-workbench [<options>] [<model file>]\n");
  printf("Options:\n");
  printf("  --force-sw-render      Force Xlib rendering\n");
  printf("  --force-opengl-render  Force OpenGL rendering\n");
  printf("  --help, -h             Show command line options and exit\n");
}

int main(int argc, char **argv)
{
  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;

  while (i < argc)
  {
    if (strcmp(argv[i], "--force-sw-render") == 0)
      wboptions.force_sw_rendering= true;
    else if (strcmp(argv[i], "--force-opengl-render") == 0)
      wboptions.force_opengl_rendering= true;
    else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0)
    {
      show_help();
      exit(0);
    }
    else if (*argv[i] != '-')
    {
      wboptions.open_at_startup= argv[i];
    }
    else
    {
      printf("Unknown option %s\n", argv[i]);
      //show_help();
      //exit(0);
    }
    i++;
  }

  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");
  wboptions.user_data_dir = std::string(g_get_home_dir()).append("/.mysql/workbench");


  Glib::Dispatcher *disp= new Glib::Dispatcher();
  pid_t splash_pid= show_splash(*disp, argc, argv);

  mforms::gtk::init();
  mforms::gtk::GRTTreeViewImpl::init();
  mforms::gtk::WizardImpl::set_icon_path(wboptions.basedir+"/images");
  mforms::gtk::UtilitiesImpl::set_open_url_slot(sigc::ptr_fun(open_url));

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

  Glib::signal_idle().connect(sigc::bind(sigc::ptr_fun(close_splash), disp, splash_pid));

  Program program(wboptions);
  
  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
