#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;
}


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("  --query <connection>   Open a query tab to the named connection\n");
  printf("  --admin <instance>     Open a administration tab to the named instance\n");
  printf("  --model <model file>   Open the given EER model file\n");
  printf("  --script <script file> Execute the given Python or Lua script file\n");
  printf("  --run <script>         Execute the given code in default language for GRT shell\n");
  printf("  --run-python <script>  Execute the given code in Python\n");
  printf("  --run-lua <script>     Execute the given code in Lua\n");
  printf("  --quit-when-done       Quit Workbench when the script is done\n");
  printf("  --help, -h             Show command line options and exit\n");
  printf("  --log-level=<level>    Valid levels are: error, warning, info, debug1, debug2, debug3\n");
  printf("  --verbose              Enable diagnostics output\n");
  printf("  --version              Show Workbench version number and exit\n");
}

static bool parse_loglevel(const std::string& line)
{
  bool ret = false;

  const size_t eq_char_pos = line.find("=");
  if (eq_char_pos != std::string::npos)
  {
    std::string level = line.substr(eq_char_pos + 1);
    level = base::tolower(level);
    ret = base::Logger::active_level(level);
    if (ret)
      printf("Logger set to level '%s'. '%s'\n", level.c_str(), base::Logger::get_state().c_str());
  }

  return ret;
}

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");

  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 (strcmp(argv[i], "--model") == 0
            || strcmp(argv[i], "--query") == 0
            || strcmp(argv[i], "--admin") == 0
            || strcmp(argv[i], "--script") == 0)
    {
      wboptions.open_at_startup_type= argv[i]+2;
      if (i+1 < argc)
        wboptions.open_at_startup= argv[++i];
      else
      {
        printf("%s: Missing argument for %s option\n", argv[0], argv[i]);
        exit(1);
      }
    }
    else if (strcmp(argv[i], "--run") == 0)
    {
      if (i+1 < argc)
        wboptions.run_at_startup= argv[++i];
      else
      {
        printf("%s: Missing argument for --run option\n", argv[0]);
        exit(1);
      }
    }
    else if (strncmp(argv[i], "--run-", strlen("--run-")) == 0)
    {
      wboptions.run_language= argv[i]+strlen("--run-");
          
      if (i+1 < argc)
        wboptions.run_at_startup= argv[++i];
      else
      {
        printf("%s: Missing argument for %s option", argv[0], argv[i-1]);
        exit(1);
      }
    }
    else if (strcmp(argv[i], "--quit-when-done") == 0)
      wboptions.quit_when_done= true;
    else if (strcmp(argv[i], "--version") == 0)
    {
      #ifdef EDITION_SE
      const char * const type = "SE";
      #else
      const char * const type = "CE";
      #endif
      printf("MySQL Workbench %s %i.%i.%i %i\n"
               , type
               , APP_MAJOR_NUMBER
               , APP_MINOR_NUMBER
               , APP_RELEASE_NUMBER
               , APP_REVISION_NUMBER
            );

      exit(0);
    }
    else if (strcmp(argv[i], "--verbose") == 0)
      wboptions.verbose = true;
    else if (strncmp(argv[i], "--log-level", 10) == 0)
    {
      if (!parse_loglevel(argv[i]) && (i+1) < argc) // If parse failed try to add next arg from CLI
      {
        std::string line(argv[i]);
        line += argv[i + 1];

        if (!parse_loglevel(line))
        {
          if ( (i + 2) < argc ) // Yet, we may have three CLI args if it was written like --log-level = <level>. Handle extra spaces
          {
            line += argv[i+2];
            if (parse_loglevel(line))
              i += 2; // correct arg count, so we do not parse log-level parts as smth different
          }
        }
        else
          ++i; // correct arg count, so we do not parse log-level parts as smth different
      }
    }
    else if (*argv[i] != '-')
    {
      wboptions.open_at_startup= argv[i];
    }
    else
    {
      printf("Unknown option %s\n", argv[i]);
      //show_help();
      //exit(0);
    }
    i++;
  }

  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();
  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)
  {
    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
