/*
 * FixScript GUI v0.2 - http://www.fixscript.org/
 * Copyright (c) 2019-2020 Martin Dvorak <jezek2@advel.cz>
 *
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the authors be held liable for any damages arising from
 * the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose, 
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <pthread.h>
#include <AppKit.h>
#include <InterfaceKit.h>
#include <StorageKit.h>
#include <Deskbar.h>

extern "C" {
#include "fiximage.h"
#include "fixgui_common.h"
}

enum {
   MSG_INIT_APP,
   MSG_ASYNC_MSG_RESULT,
   MSG_WINDOW_RESIZED,
   MSG_WINDOW_CLOSE,
   MSG_DRAW_CANVAS,
   MSG_BUTTON_CLICKED,
   MSG_MENU_ITEM_ACTION,
   MSG_POPUP_MENU_DELETED,
   MSG_POPUP_ACTION,
   MSG_POPUP_MENU
};

enum {
   FIXVALUE_TYPE = 'fxvl',
   FIXIMAGE_TYPE = 'fxim'
};

enum {
   DESKBAR_FIXMSG = 'fXmG'
};

enum {
   DMT_CREATED,
   DMT_DESTROYED,
   DMT_SET_ICON,
   DMT_DO_ACTION,
   DMT_SHOW_MENU
};

struct View {
   ViewCommon common;
   BView *view;
   BLooper *locked_looper;
   union {
      struct {
         BWindow *win;
         BView *contents;
         int created;
      } window;
      struct {
         BView *canvas;
         BScrollView *scroll;
         int flags;
      } canvas;
   };
};

struct Menu {
   MenuCommon common;
   BMenu *menu;
};

struct Worker {
   WorkerCommon common;
};

struct NotifyIcon {
   NotifyIconCommon common;
   BMessenger *messenger;
   int width, height;
   uint32_t *pixels;
   Menu *menu;
};

struct SystemFont {
   BFont *font;
};

class ScriptLooper;

extern "C" void __cocoa_threads_init();

static pthread_mutex_t mutex;
static pthread_cond_t cond;
static int deskbar_mode = 0;
static char *deskbar_name = NULL;

static const char *app_signature;
static entry_ref self_ref;
static int remove_deskbar_item = 0;
static ScriptLooper *script_looper;
static NotifyIcon *active_notify_icon = NULL;

static void AddValue(BMessage *msg, const char *name, Value value);
static Value GetValue(BMessage *msg, const char *name);
static int get_alert_button(int msg_type, int idx);
static void prepare_event_finish(BMessage *msg);
static void notify_event_finish(BMessage *msg);
static void wait_event_finish(BMessage *msg);
static void update_menu_after_destroying(Menu *menu);
static void menu_real_create(Menu *menu, const char *popup_title);
static void script_looper_post_message(BMessage *msg);

static void free_bitmap(void *data)
{
   BBitmap *bitmap = (BBitmap *)data;
   delete bitmap;
}

static inline void log(const char *fmt, ...)
{
   FILE *f;
   va_list ap;
   f = fopen("/boot/home/log.txt", "ab");
   va_start(ap, fmt);
   vfprintf(f, fmt, ap);
   va_end(ap);
   fclose(f);
}

class FixPopUpMenu: public BPopUpMenu
{
public:
   Menu *menu;

public:
   FixPopUpMenu(const char *name): BPopUpMenu(name)
   {
   }

   virtual ~FixPopUpMenu()
   {
      BMessage msg;
      msg.what = MSG_POPUP_MENU_DELETED;
      msg.AddPointer("menu", menu);
      prepare_event_finish(&msg);
      script_looper_post_message(&msg);
      wait_event_finish(&msg);
   }
};

class ScriptLooper: public BLooper
{
public:
   ScriptLooper(): BLooper("main thread")
   {
   }

   virtual ~ScriptLooper()
   {
   }

   virtual void MessageReceived(BMessage *msg)
   {
      switch (msg->what) {
         case MSG_INIT_APP: {
            int argc = msg->GetInt32("argc", 0);
            char **argv = (char **)msg->GetPointer("argv");
            app_main(argc, argv);
            break;
         }

         case MSG_ASYNC_MSG_RESULT: {
            int idx = msg->GetInt32("which", 0);
            int type = msg->GetInt32("type", 0);
            Heap *heap = (Heap *)msg->GetPointer("heap");
            Value func = GetValue(msg, "func");
            Value data = GetValue(msg, "data");
            Value error;

            if (func.value) {
               fixscript_call(heap, func, 2, &error, data, fixscript_int(get_alert_button(type, idx)));
               if (error.value) {
                  fprintf(stderr, "error while running async message callback:\n");
                  fixscript_dump_value(heap, error, 1);
               }
            }

            fixscript_unref(heap, data);
            break;
         }
         
         case MSG_WINDOW_RESIZED: {
            View *view = (View *)msg->GetPointer("view");
            call_view_callback(view, CALLBACK_WINDOW_RESIZE);
            if (!msg->GetBool("async", false)) {
               notify_event_finish(msg);
            }
            break;
         }
         
         case MSG_WINDOW_CLOSE: {
            View *view = (View *)msg->GetPointer("view");
            call_view_callback(view, CALLBACK_WINDOW_CLOSE);
            break;
         }
         
         case MSG_DRAW_CANVAS: {
            View *view = (View *)msg->GetPointer("view");
            BBitmap *bitmap = (BBitmap *)msg->GetPointer("bitmap");
            int offset_x = msg->GetInt32("offset_x", 0);
            int offset_y = msg->GetInt32("offset_y", 0);
            int *finished_flag = (int *)msg->GetPointer("finished_flag");
            Heap *heap = view->common.heap;
            int width = bitmap->Bounds().IntegerWidth();
            int height = bitmap->Bounds().IntegerHeight();
            Value img, painter, error = fixscript_int(0);

            img = fiximage_create_from_pixels(heap, width, height, bitmap->BytesPerRow()/4, (uint32_t *)bitmap->Bits(), free_bitmap, bitmap, -1);
            if (!img.value) {
               fixscript_error(heap, &error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
            }
            fixscript_ref(heap, img);

            if (!error.value) {
               painter = fiximage_create_painter(heap, img, -offset_x, -offset_y);
               if (!painter.value) {
                  fixscript_error(heap, &error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
               }
            }

            if (!error.value) {
               call_view_callback_with_value(view, CALLBACK_CANVAS_PAINT, painter);
            }
            
            if (error.value) {
               fprintf(stderr, "error while painting:\n");
               fixscript_dump_value(heap, error, 1);
            }

            pthread_mutex_lock(&mutex);
            *finished_flag = 1;
            pthread_cond_broadcast(&cond);
            while (*finished_flag != 2) {
               pthread_cond_wait(&cond, &mutex);
            }
            pthread_mutex_unlock(&mutex);

            fixscript_unref(heap, img);

            pthread_mutex_lock(&mutex);
            *finished_flag = 3;
            pthread_cond_broadcast(&cond);
            pthread_mutex_unlock(&mutex);
            break;
         }
         
         case MSG_BUTTON_CLICKED: {
            View *view = (View *)msg->GetPointer("view");
            call_action_callback(view, CALLBACK_BUTTON_ACTION);
            break;
         }
         
         case MSG_MENU_ITEM_ACTION: {
            Menu *menu = (Menu *)msg->GetPointer("menu");
            MenuItem *item = (MenuItem *)msg->GetPointer("item");
            MenuItem *mi;
            int i;

            for (i=0,mi=menu->common.items; mi; mi=mi->next, i++) {
               if (mi == item) {
                  call_menu_callback(menu, i);
                  break;
               }
            }
            break;
         }
         
         case MSG_POPUP_MENU_DELETED: {
            Menu *menu = (Menu *)msg->GetPointer("menu");
            update_menu_after_destroying(menu);
            fixscript_unref(menu->common.heap, menu->common.instance);
            notify_event_finish(msg);
            break;
         }

         case MSG_POPUP_ACTION: {
            NotifyIcon *icon = (NotifyIcon *)msg->GetPointer("icon");
            call_notify_icon_click_callback(icon);
            break;
         }

         case MSG_POPUP_MENU: {
            NotifyIcon *icon = (NotifyIcon *)msg->GetPointer("icon");
            FixPopUpMenu *popup;
            int x = msg->GetInt32("screen_x", 0);
            int y = msg->GetInt32("screen_y", 0);

            fixscript_ref(icon->menu->common.heap, icon->menu->common.instance);
            menu_real_create(icon->menu, "popup menu");
            popup = (FixPopUpMenu *)icon->menu->menu;
            popup->menu = icon->menu;
            popup->SetAsyncAutoDestruct(true);
            popup->Go(BPoint(x, y), true, false, true);
            break;
         }   

         default:
            BLooper::MessageReceived(msg);
      }
   }
};

static void script_looper_post_message(BMessage *msg)
{
   script_looper->PostMessage(msg);
}

static void init_notify_icon();

class FixApp: public BApplication
{
public:
   FixApp(const char *signature): BApplication(signature)
   {
   }
   
   virtual void MessageReceived(BMessage *msg)
   {
      if (msg->what == DESKBAR_FIXMSG) {
         int type = msg->GetInt32("type", -1);
         switch (type) {
            case DMT_CREATED:
               init_notify_icon();
               break;

            case DMT_DO_ACTION: {
               NotifyIcon *icon = active_notify_icon;
               if (icon) {
                  BMessage msg2;
                  msg2.what = MSG_POPUP_ACTION;
                  msg2.AddPointer("icon", icon);
                  script_looper->PostMessage(&msg2);
               }
               break;
            }

            case DMT_SHOW_MENU: {
               int x = msg->GetInt32("screen_x", 0);
               int y = msg->GetInt32("screen_y", 0);
               NotifyIcon *icon = active_notify_icon;

               if (!icon || !icon->menu || icon->menu->menu) break;
               
               BMessage msg2;
               msg2.what = MSG_POPUP_MENU;
               msg2.AddPointer("icon", icon);
               msg2.AddInt32("screen_x", x);
               msg2.AddInt32("screen_y", y);
               script_looper->PostMessage(&msg2);
               break;
            }
         }
         return;
      }
      BApplication::MessageReceived(msg);
   }
};

class FixWindow: public BWindow
{
private:
   View *view;

public:
   FixWindow(BRect rect, const char *title, window_type type, uint32 flags, View *view): BWindow(rect, title, type, flags)
   {
      this->view = view;
   }

   virtual bool QuitRequested()
   {
      BMessage msg;
      msg.what = MSG_WINDOW_CLOSE;
      msg.AddPointer("view", view);
      script_looper->PostMessage(&msg);
      return false;
   }
};

class FixWindowView: public BView
{
private:
   View *view;

public:
   FixWindowView(BRect rect, View *view): BView(rect, NULL, B_FOLLOW_ALL_SIDES, B_FRAME_EVENTS)
   {
      this->view = view;
      SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
   }

   virtual void AttachedToWindow()
   {
      BMessage msg;
      msg.what = MSG_WINDOW_RESIZED;
      msg.AddPointer("view", view);
      msg.AddBool("async", true);

      script_looper->PostMessage(&msg);
   }

   virtual void FrameResized(float new_width, float new_height)
   {
      BMessage msg;
      msg.what = MSG_WINDOW_RESIZED;
      msg.AddPointer("view", view);
      prepare_event_finish(&msg);

      UnlockLooper();
      script_looper->PostMessage(&msg);
      wait_event_finish(&msg);
      LockLooper();
   }
};

class FixCanvas: public BView
{
private:
   View *view;

public:
   FixCanvas(BRect rect, View *view): BView(rect, NULL, 0, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE)
   {
      this->view = view;
      SetViewColor(B_TRANSPARENT_COLOR);
   }

   virtual void Draw(BRect rect)
   {
      int *finished_flag = (int *)calloc(1, sizeof(int));

      int x1 = (int)floorf(rect.left);
      int y1 = (int)floorf(rect.top);
      int x2 = (int)ceilf(rect.right)+2; // TODO: why is this needed to be incremented by 2 and not 1?
      int y2 = (int)ceilf(rect.bottom)+2;

      BBitmap *bitmap = new BBitmap(BRect(0, 0, x2-x1-1, y2-y1-1), B_RGB32);

      BMessage msg;
      msg.what = MSG_DRAW_CANVAS;
      msg.AddPointer("view", view);
      msg.AddPointer("bitmap", bitmap);
      msg.AddInt32("offset_x", x1);
      msg.AddInt32("offset_y", y1);
      msg.AddPointer("finished_flag", finished_flag);

      UnlockLooper();
      script_looper->PostMessage(&msg);

      pthread_mutex_lock(&mutex);
      while (*finished_flag != 1) {
         pthread_cond_wait(&cond, &mutex);
      }
      pthread_mutex_unlock(&mutex);

      LockLooper();
      DrawBitmap(bitmap, BRect(x1, y1, x2-1, y2-1));

      pthread_mutex_lock(&mutex);
      *finished_flag = 2;
      pthread_cond_broadcast(&cond);
      while (*finished_flag != 3) {
         pthread_cond_wait(&cond, &mutex);
      }
      pthread_mutex_unlock(&mutex);

      free(finished_flag);
   }
};

class FixDeskbarView;
static int notify_deskbar_view(FixDeskbarView *view, BMessage *msg);

class FixDeskbarFilter: public BMessageFilter
{
private:
   FixDeskbarView *view;

public:
   FixDeskbarFilter(FixDeskbarView *view): BMessageFilter(DESKBAR_FIXMSG)
   {
      this->view = view;
   }

   virtual filter_result Filter(BMessage *msg, BHandler **target)
   {
      if (notify_deskbar_view(view, msg)) {
         return B_DISPATCH_MESSAGE;
      }
      return B_SKIP_MESSAGE;
   }
};

class FixDeskbarView: public BView
{
public:
   char *signature;
   FixDeskbarFilter *filter;
   BMessenger messenger;
   BBitmap *bitmap;

public:
   FixDeskbarView(const char *signature, const char *name, int width, int height): BView(BRect(0, 0, width-1, height-1), name, B_FOLLOW_ALL_SIDES, B_WILL_DRAW)
   {
      this->signature = strdup(signature);
      this->filter = NULL;
      this->bitmap = NULL;
   }
   
   FixDeskbarView(BMessage *archive): BView(archive)
   {
      signature = strdup(archive->GetString("signature"));
      if (deskbar_mode) {
         filter = new FixDeskbarFilter(this);
         be_app->Lock();
         be_app->AddFilter(filter);
         be_app->Unlock();

         messenger.SetTo(signature);

         BMessage *msg = new BMessage(DESKBAR_FIXMSG);
         msg->AddInt32("type", DMT_CREATED);
         messenger.SendMessage(msg);
      }
      SetViewColor(B_TRANSPARENT_COLOR);
      this->bitmap = NULL;
   }

   virtual ~FixDeskbarView()
   {
      if (filter) {
         BMessage *msg = new BMessage(DESKBAR_FIXMSG);
         msg->AddInt32("type", DMT_DESTROYED);
         messenger.SendMessage(msg);
         be_app->Lock();
         be_app->RemoveFilter(filter);
         be_app->Unlock();
         delete filter;
      }
      if (bitmap) {
         delete bitmap;
      }
      free(signature);
   }

   virtual status_t Archive(BMessage *data, bool deep=true) const
   {
      BView::Archive(data, deep);
      data->AddString("signature", signature);
      return B_OK;
   }

   static BArchivable *Instantiate2(BMessage *data)
   {
      if (!validate_instantiation(data, "FixDeskbarView")) {
         return NULL;
      }
      return new FixDeskbarView(data);
   }

   void receive_app_msg(int type, BMessage *msg)
   {
      switch (type) {
         case DMT_SET_ICON: {
            int width = msg->GetInt32("width", 0);
            int height = msg->GetInt32("height", 0);
            const uint32_t *pixels;
            ssize_t num_bytes;
            msg->FindData("pixels", FIXIMAGE_TYPE, 0, (const void **)&pixels, &num_bytes);
            if (bitmap) {
               delete bitmap;
            }
            bitmap = new BBitmap(BRect(0, 0, width-1, height-1), B_RGBA32);
            bitmap->ImportBits(pixels, width*height*4, width*4, 0, B_RGBA32);
            Invalidate();
            break;
         }
      }
   }
   
   virtual void Draw(BRect rect)
   {
      if (bitmap) {
         SetDrawingMode(B_OP_OVER);
         DrawBitmap(bitmap, Bounds());
      }
   }
   
   virtual void MessageReceived(BMessage *msg)
   {
      switch (msg->what) {
         case B_MOUSE_DOWN: {
            int buttons = msg->GetInt32("buttons", 0);
            int type = -1;
            if (buttons & B_PRIMARY_MOUSE_BUTTON) {
               type = DMT_DO_ACTION;
            }
            if (buttons & B_SECONDARY_MOUSE_BUTTON) {
               type = DMT_SHOW_MENU;
            }
            if (type == -1) break;
            
            BPoint point;
            msg->FindPoint("screen_where", &point);

            BMessage *msg = new BMessage(DESKBAR_FIXMSG);
            msg->AddInt32("type", type);
            msg->AddInt32("screen_x", (int)point.x);
            msg->AddInt32("screen_y", (int)point.y);
            messenger.SendMessage(msg);
            break;
         }

         default:
            BView::MessageReceived(msg);
      }
   }
};

extern "C" void *Instantiate__14FixDeskbarViewP8BMessage(void *data)
{
   return FixDeskbarView::Instantiate2((BMessage *)data);
}

static int notify_deskbar_view(FixDeskbarView *view, BMessage *msg)
{
   const char *sig;
   
   sig = msg->GetString("signature");
   if (!sig || strcmp(sig, view->signature) != 0) {
      return 0;
   }
   view->LockLooper();
   view->receive_app_msg(msg->GetInt32("type", -1), msg);
   view->UnlockLooper();
   return 1;
}


static void AddValue(BMessage *msg, const char *name, Value value)
{
   msg->AddData(name, FIXVALUE_TYPE, &value, sizeof(Value));
}


static Value GetValue(BMessage *msg, const char *name)
{
   const void *data;
   ssize_t num_bytes;
   
   if (msg->FindData(name, FIXVALUE_TYPE, 0, &data, &num_bytes) != B_OK) {
      return fixscript_int(0);
   }
   if (num_bytes != sizeof(Value)) {
      return fixscript_int(0);
   }
   return *(Value *)data;
}


static void prepare_event_finish(BMessage *msg)
{
   int *event_finished = (int *)calloc(1, sizeof(int));

   msg->AddPointer("event_finished", event_finished);
}


static void notify_event_finish(BMessage *msg)
{
   int *event_finished = (int *)msg->GetPointer("event_finished");
   
   pthread_mutex_lock(&mutex);
   *event_finished = 1;
   pthread_cond_broadcast(&cond);
   pthread_mutex_unlock(&mutex);
}


static void wait_event_finish(BMessage *msg)
{
   int *event_finished = (int *)msg->GetPointer("event_finished");

   pthread_mutex_lock(&mutex);
   while (*event_finished == 0) {
      pthread_cond_wait(&cond, &mutex);
   }
   pthread_mutex_unlock(&mutex);

   free(event_finished);
}


static void view_lock(View *view)
{
   BLooper *looper;
   
   if (view->common.type == TYPE_WINDOW) {
      looper = view->window.win;
   }
   else {
      if (!view->view) return;
      looper = view->view->Looper();
   }

   if (!looper) {
      return;
   }

   if (looper->Lock()) {
      view->locked_looper = looper;
   }
}


static void view_unlock(View *view)
{
   if (view->locked_looper) {
      view->locked_looper->Unlock();
      view->locked_looper = NULL;
   }
}


static inline uint32_t div255(uint32_t a)
{
   return ((a << 8) + a + 255) >> 16;
}


void trigger_delayed_gc(Heap *heap)
{
}


void free_view(View *view)
{
   free(view);
}


void free_menu(Menu *menu)
{
   free(menu);
}


void free_notify_icon(NotifyIcon *icon)
{
   delete icon->messenger;
   free(icon->pixels);
   free(icon);
}


void view_destroy(View *view)
{
   if (view->common.type == TYPE_WINDOW) {
      call_view_callback(view, CALLBACK_WINDOW_DESTROY);
      view->window.win->Lock();
      view->window.win->Quit();
   }
}


void view_get_rect(View *view, Rect *rect)
{
   BRect brect;
   
   if (view->common.type == TYPE_WINDOW) {
      view_lock(view);
      brect = view->window.win->Frame();
      view_unlock(view);
   }
   else if (view->view) {
      view_lock(view);
      brect = view->view->Frame();
      view_unlock(view);
   }
   else {
      return;
   }

   rect->x1 = (int)roundf(brect.left);
   rect->y1 = (int)roundf(brect.top);
   rect->x2 = (int)roundf(brect.right)+1;
   rect->y2 = (int)roundf(brect.bottom)+1;
}


void view_set_rect(View *view, Rect *rect)
{
   if (!view->view) return;

   view_lock(view);
   view->view->MoveTo(rect->x1, rect->y1);
   view->view->ResizeTo(rect->x2 - rect->x1, rect->y2 - rect->y1);
   view_unlock(view);
}


void view_get_content_rect(View *view, Rect *rect)
{
   BView *bview;
   BRect brect;
   
   if (view->common.type == TYPE_WINDOW) {
      bview = view->window.contents;
   }
   else if (view->view) {
      bview = view->view;
   }
   else {
      return;
   }

   if (!bview) return;
   
   view_lock(view);
   brect = bview->Bounds();
   view_unlock(view);

   rect->x1 = (int)roundf(brect.left);
   rect->y1 = (int)roundf(brect.top);
   rect->x2 = (int)roundf(brect.right)+1;
   rect->y2 = (int)roundf(brect.bottom)+1;
}


void view_get_inner_rect(View *view, Rect *rect)
{
   view_get_content_rect(view, rect);
}


void view_set_visible(View *view, int visible)
{
   if (view->common.type == TYPE_WINDOW) {
      BWindow *win = view->window.win;
      if (visible) {
         if (!view->window.created) {
            win->Show();
         }
         else {
            view_lock(view);
            win->Show();
            view_unlock(view);
         }
      }
      else {
         if (view->window.created) {
            view_lock(view);
            win->Hide();
            view_unlock(view);
         }
      }
   }
}


int view_add(View *parent, View *view)
{
   BView *parent_bview;

   if (!view->view) return 0;

   if (parent->common.type == TYPE_WINDOW) {
      parent_bview = parent->window.contents;
   }
   else if (parent->common.type == TYPE_CANVAS) {
      // TODO: add support for canvases
      //parent_bview = parent->canvas.canvas;
      return 0;
   }
   else {
      parent_bview = parent->view;
   }

   view_lock(parent);
   parent_bview->AddChild(view->view);
   view_unlock(parent);
   return 1;
}


void view_focus(View *view)
{
   if (!view->view) return;

   view_lock(view);
   view->view->MakeFocus();
   view_unlock(view);
}


int view_has_focus(View *view)
{
   if (!view->view) return 0;

   view_lock(view);
   int ret = view->view->IsFocus();
   view_unlock(view);
   return ret;
}


void view_get_sizing(View *view, float *grid_x, float *grid_y, int *form_small, int *form_medium, int *form_large, int *view_small, int *view_medium, int *view_large)
{
   *grid_x = 4;
   *grid_y = 4;
   *form_small = 4;
   *form_medium = 8;
   *form_large = 16;
   *view_small = 4;
   *view_medium = 8;
   *view_large = 16;
}


void view_get_default_size(View *view, int *width, int *height)
{
   if (view->common.type == TYPE_BUTTON) {
      *width = 64;
      *height = 31-1;
   }
   else {
      *width = 64;
      *height = 25;
   }
}


float view_get_scale(View *view)
{
   return 1.0f;
}


void view_set_cursor(View *view, int cursor)
{
}


int view_get_cursor(View *view)
{
   return CURSOR_DEFAULT;
}


View *window_create(plat_char *title, int width, int height, int flags)
{
   BWindow *win;
   View *view;
   uint32 win_flags = 0;
   
   view = (View *)calloc(1, sizeof(View));
   if (!view) return NULL;

   if ((flags & WIN_RESIZABLE) == 0) {
      win_flags |= B_NOT_RESIZABLE | B_NOT_ZOOMABLE;
   }

   view->window.win = new FixWindow(BRect(50, 50, width, height), title, B_TITLED_WINDOW, win_flags, view);
   win = view->window.win;

   view->window.contents = new FixWindowView(win->Bounds(), view);
   win->AddChild(view->window.contents);
   
   if (flags & WIN_CENTER) {
      win->CenterOnScreen();
   }
   if (flags & WIN_MAXIMIZE) {
      win->Zoom();
   }
   if (flags & WIN_MINIMIZE) {
      // TODO: not working
      win->Minimize(true);
   }
   return view;
}


plat_char *window_get_title(View *view)
{
   char *ret;

   view_lock(view);
   ret = strdup(view->window.win->Title());
   view_unlock(view);
   return ret;
}


void window_set_title(View *view, plat_char *title)
{
   view_lock(view);
   view->window.win->SetTitle(title);
   view_unlock(view);
}


void window_set_minimum_size(View *view, int width, int height)
{
   view_lock(view);
   view->window.win->SetSizeLimits(width, 1000000.0f, height, 1000000.0f);
   view_unlock(view);
}


int window_is_maximized(View *view)
{
   return 0;
}


void window_set_status_text(View *view, plat_char *text)
{
}


int window_set_menu(View *view, Menu *old_menu, Menu *new_menu)
{
   return 1;
}


View *label_create(plat_char *label)
{
   View *view;
   
   view = (View *)calloc(1, sizeof(View));
   if (!view) return NULL;

   view->view = new BStringView(BRect(0, 0, 100, 100), NULL, label);
   return view;
}


plat_char *label_get_label(View *view)
{
   return strdup(((BStringView *)view->view)->Text());
}


void label_set_label(View *view, plat_char *label)
{
   ((BStringView *)view->view)->SetText(label);
}


View *text_field_create()
{
   View *view;
   
   view = (View *)calloc(1, sizeof(View));
   if (!view) return NULL;

   view->view = new BTextControl(BRect(0, 0, 100, 100), NULL, NULL, "text", NULL);
   return view;
}


plat_char *text_field_get_text(View *view)
{
   return strdup(((BTextControl *)view->view)->Text());
}


void text_field_set_text(View *view, plat_char *text)
{
   ((BTextControl *)view->view)->SetText(text);
}


View *button_create(plat_char *label, int flags)
{
   View *view;
   BMessage *msg;
   
   view = (View *)calloc(1, sizeof(View));
   if (!view) return NULL;

   msg = new BMessage(MSG_BUTTON_CLICKED);
   msg->AddPointer("view", view);

   view->view = new BButton(BRect(0, 0, 100, 100), NULL, label, msg);
   ((BButton *)view->view)->SetTarget(script_looper);

   return view;
}


plat_char *button_get_label(View *view)
{
   return strdup(((BButton *)view->view)->Label());
}


void button_set_label(View *view, plat_char *label)
{
   ((BButton *)view->view)->SetLabel(label);
}


View *canvas_create(int flags)
{
   View *view;
   
   view = (View *)calloc(1, sizeof(View));
   if (!view) return NULL;

   view->canvas.canvas = new FixCanvas(BRect(0, 0, 100, 100), view);
   view->canvas.flags = flags;

   if (flags & (CANVAS_SCROLLABLE | CANVAS_BORDER)) {
      bool scroll = flags & CANVAS_SCROLLABLE;
      view->canvas.scroll = new BScrollView(NULL, view->canvas.canvas, 0, scroll, scroll, flags & CANVAS_BORDER? B_PLAIN_BORDER : B_NO_BORDER);
      view->view = view->canvas.scroll;
   }
   else {
      view->view = view->canvas.canvas;
   }

   return view;
}


void canvas_set_scroll_state(View *view, int type, int pos, int max, int page_size, int always_show)
{
   BScrollBar *bar;
   
   if ((view->canvas.flags & CANVAS_SCROLLABLE) == 0) return;

   view_lock(view);
   bar = view->canvas.scroll->ScrollBar(type == SCROLL_HORIZ? B_HORIZONTAL : B_VERTICAL);
   bar->SetRange(0, max);
   bar->SetValue(pos);
   bar->SetProportion(max > 0? (float)page_size / (float)max : 0.0f);
   bar->SetSteps(16, page_size);
   view_unlock(view);
}


void canvas_set_scroll_position(View *view, int type, int pos)
{
   BScrollBar *bar;
   
   if ((view->canvas.flags & CANVAS_SCROLLABLE) == 0) return;

   view_lock(view);
   bar = view->canvas.scroll->ScrollBar(type == SCROLL_HORIZ? B_HORIZONTAL : B_VERTICAL);
   bar->SetValue(pos);
   view_unlock(view);
}


int canvas_get_scroll_position(View *view, int type)
{
   BScrollBar *bar;
   int ret;
   
   if ((view->canvas.flags & CANVAS_SCROLLABLE) == 0) return 0;

   view_lock(view);
   bar = view->canvas.scroll->ScrollBar(type == SCROLL_HORIZ? B_HORIZONTAL : B_VERTICAL);
   ret = (int)roundf(bar->Value());
   view_unlock(view);
   return ret;
}


void canvas_set_active_rendering(View *view, int enable)
{
}


int canvas_get_active_rendering(View *view)
{
   return 0;
}


void canvas_set_overdraw_size(View *view, int size)
{
}


int canvas_get_overdraw_size(View *view)
{
   return 0;
}


void canvas_set_focusable(View *view, int enable)
{
   view_lock(view);
   view->canvas.canvas->SetFlags(view->canvas.canvas->Flags() | B_NAVIGABLE);
   view_unlock(view);
}


int canvas_is_focusable(View *view)
{
   view_lock(view);
   int ret = (view->canvas.canvas->Flags() & B_NAVIGABLE) != 0;
   view_unlock(view);
   return ret;
}


void canvas_repaint(View *view, Rect *rect)
{
   view_lock(view);
   if (rect) {
      view->canvas.canvas->Invalidate(BRect(rect->x1, rect->y1, rect->x2, rect->y2));
   }
   else {
      view->canvas.canvas->Invalidate();
   }
   view_unlock(view);
}


Menu *menu_create()
{
   Menu *menu;
   
   menu = (Menu *)calloc(1, sizeof(Menu));
   if (!menu) return NULL;

   return menu;
}


static void menu_real_create(Menu *menu, const char *popup_title)
{
   MenuItem *item;
   BMessage *msg;

   if (popup_title) {
      menu->menu = new FixPopUpMenu(popup_title);
   }
   else {
      menu->menu = new BMenuBar(BRect(0, 0, 32, 32), "main menu");
   }

   for (item=menu->common.items; item; item=item->next) {
      if (item->submenu) {
         menu_real_create(item->submenu, item->title);
         menu->menu->AddItem(item->submenu->menu);
      }
      else if (item->title) {
         msg = new BMessage(MSG_MENU_ITEM_ACTION);
         msg->AddPointer("menu", menu);
         msg->AddPointer("item", item);
         BMenuItem *bitem = new BMenuItem(item->title, msg);
         bitem->SetTarget(script_looper);
         menu->menu->AddItem(bitem);
      }
      else {
         menu->menu->AddSeparatorItem();
      }
   }
}


static void update_menu_after_destroying(Menu *menu)
{
   MenuItem *item;

   menu->menu = NULL;

   for (item=menu->common.items; item; item=item->next) {
      if (item->submenu) {
         update_menu_after_destroying(item->submenu);
      }
   }
}


void menu_insert_item(Menu *menu, int idx, plat_char *title, MenuItem *item)
{
   if (menu->menu) {
      BMessage *msg = new BMessage(MSG_MENU_ITEM_ACTION);
      msg->AddPointer("menu", menu);
      msg->AddPointer("item", item);
      BMenuItem *bitem = new BMenuItem(title, msg);
      bitem->SetTarget(script_looper);
      bool locked = menu->menu->LockLooper();
      if (idx < 0) {
         menu->menu->AddItem(bitem);
      }
      else {
         menu->menu->AddItem(bitem, idx);
      }
      if (locked) menu->menu->UnlockLooper();
   }
}


void menu_insert_separator(Menu *menu, int idx)
{
   if (menu->menu) {
      bool locked = menu->menu->LockLooper();
      if (idx < 0) {
         menu->menu->AddSeparatorItem();
      }
      else {
         menu->menu->AddItem(new BSeparatorItem(), idx);
      }
      if (locked) menu->menu->UnlockLooper();
   }
}


int menu_insert_submenu(Menu *menu, int idx, plat_char *title, Menu *submenu)
{
   if (submenu->menu) return 0;

   if (menu->menu) {
      menu_real_create(submenu, title);
      bool locked = menu->menu->LockLooper();
      if (idx < 0) {
         menu->menu->AddItem(submenu->menu);
      }
      else {
         menu->menu->AddItem(submenu->menu, idx);
      }
      if (locked) menu->menu->UnlockLooper();
   }
   return 1;
}


void menu_remove_item(Menu *menu, int idx, MenuItem *item)
{
   if (menu->menu) {
      bool locked = menu->menu->LockLooper();
      menu->menu->RemoveItem(idx);
      if (item->submenu) {
         if (item->submenu->menu) {
            delete item->submenu->menu;
            update_menu_after_destroying(item->submenu);
         }
      }
      if (locked) menu->menu->UnlockLooper();
   }
}


void menu_show(Menu *menu, View *view, int x, int y)
{
   BView *bview;
   BPoint point = BPoint(x, y);
   FixPopUpMenu *popup;
   
   if (menu->menu) return;

   if (view->common.type == TYPE_WINDOW) {
      bview = view->window.contents;
   }
   else {
      bview = view->view;
   }
   if (!bview) return;

   bool locked = bview->LockLooper();
   bview->ConvertToScreen(&point);
   if (locked) bview->UnlockLooper();

   fixscript_ref(menu->common.heap, menu->common.instance);
   menu_real_create(menu, "popup menu");
   popup = (FixPopUpMenu *)menu->menu;
   popup->menu = menu;
   popup->SetAsyncAutoDestruct(true);
   popup->Go(point, true, false, true);
}


static BAlert *create_alert(int msg_type, plat_char *title, plat_char *msg)
{
   const char *btn1, *btn2 = NULL, *btn3 = NULL;
   alert_type type;
   
   switch (msg_type & 0xFF) {
      default:
      case MSG_OK:            btn1 = "OK"; break;
      case MSG_OK_CANCEL:     btn1 = "Cancel"; btn2 = "OK"; break;
      case MSG_YES_NO:        btn1 = "No"; btn2 = "Yes"; break;
      case MSG_YES_NO_CANCEL: btn1 = "Cancel"; btn2 = "No"; btn3 = "Yes"; break;
   }

   switch (msg_type & 0xFF00) {
      default:
      case MSG_ICON_INFO:     type = B_INFO_ALERT; break;
      case MSG_ICON_QUESTION: type = B_IDEA_ALERT; break;
      case MSG_ICON_ERROR:    type = B_STOP_ALERT; break;
      case MSG_ICON_WARNING:  type = B_WARNING_ALERT; break;
   }

   return new BAlert(title, msg, btn1, btn2, btn3, B_WIDTH_AS_USUAL, btn3? B_OFFSET_SPACING : B_EVEN_SPACING, type);
}


static int get_alert_button(int msg_type, int idx)
{
   int ret = MSG_BTN_CANCEL;

   switch (msg_type & 0xFF) {
      default:
      case MSG_OK:
         if (idx == 0) { ret = MSG_BTN_OK; break; }
         break;

      case MSG_OK_CANCEL:
         if (idx == 0) { ret = MSG_BTN_CANCEL; break; }
         if (idx == 1) { ret = MSG_BTN_OK; break; }
         break;

      case MSG_YES_NO:
         if (idx == 0) { ret = MSG_BTN_NO; break; }
         if (idx == 1) { ret = MSG_BTN_YES; break; }
         break;

      case MSG_YES_NO_CANCEL:
         if (idx == 0) { ret = MSG_BTN_CANCEL; break; }
         if (idx == 1) { ret = MSG_BTN_NO; break; }
         if (idx == 2) { ret = MSG_BTN_YES; break; }
         break;
   }

   return ret;
}


int show_message(View *window, int type, plat_char *title, plat_char *msg)
{
   BAlert *alert;

   alert = create_alert(type, title, msg);
   return get_alert_button(type, alert->Go());
}


Worker *worker_create()
{
   Worker *worker;
   
   worker = (Worker *)calloc(1, sizeof(Worker));
   if (!worker) return NULL;

   return worker;
}


int worker_start(Worker *worker)
{
   return 0;
}


void worker_notify(Worker *worker)
{
}


void worker_lock(Worker *worker)
{
}


void worker_wait(Worker *worker, int timeout)
{
}


void worker_unlock(Worker *worker)
{
}


void worker_destroy(Worker *worker)
{
}


uint32_t timer_get_time()
{
   return 0;
}


uint32_t timer_get_micro_time()
{
   return 0;
}


int timer_is_active(Heap *heap, Value instance)
{
   return 0;
}


void timer_start(Heap *heap, Value instance, int interval, int restart)
{
}


void timer_stop(Heap *heap, Value instance)
{
}


void clipboard_set_text(plat_char *text)
{
}


plat_char *clipboard_get_text()
{
   return NULL;
}


SystemFont *system_font_create(Heap *heap, plat_char *family, float size, int style)
{
   SystemFont *font;
   const char *style_str;
   
   font = (SystemFont *)calloc(1, sizeof(SystemFont));
   if (!font) return NULL;

   if (style & FONT_BOLD) {
      if (style & FONT_ITALIC) {
         style_str = "bold italic";
      }
      else {
         style_str = "bold";
      }
   }
   else {
      if (style & FONT_ITALIC) {
         style_str = "italic";
      }
      else {
         style_str = "plain";
      }
   }

   font->font = new BFont();
   font->font->SetFamilyAndStyle(family, style_str);
   font->font->SetSize(size);
   return font;
}


void system_font_destroy(SystemFont *font)
{
   delete font->font;
   free(font);
}


int system_font_get_size(SystemFont *font)
{
   return (int)roundf(font->font->Size());
}


int system_font_get_ascent(SystemFont *font)
{
   font_height fh;
   font->font->GetHeight(&fh);
   return (int)roundf(fh.ascent);
}


int system_font_get_descent(SystemFont *font)
{
   font_height fh;
   font->font->GetHeight(&fh);
   return (int)roundf(fh.descent);
}


int system_font_get_height(SystemFont *font)
{
   font_height fh;
   font->font->GetHeight(&fh);
   return (int)roundf(fh.leading);
}


int system_font_get_string_advance(SystemFont *font, plat_char *s)
{
   return (int)ceilf(font->font->StringWidth(s));
}


float system_font_get_string_position(SystemFont *font, plat_char *text, int x)
{
   char *s;
   int width;
   int min, max, middle, w, w1, w2, pos;
   
   if (x < 0) return 0.0f;
   width = system_font_get_string_advance(font, text);
   if (x >= width) return (float)strlen(text);

   s = (char *)malloc(strlen(text)+1);

   min = 0;
   max = strlen(text);
   while (min < max) {
      middle = min+(max-min)/2;
      strcpy(s, text);
      s[middle] = 0;
      w = system_font_get_string_advance(font, s);
      if (w < x) {
         min = middle+1;
      }
      else {
         max = middle;
      }
   }

   pos = min-1;
   if (pos < 0) pos = 0;

   strcpy(s, text);
   s[pos+1] = 0;
   w2 = system_font_get_string_advance(font, s);
   s[pos] = 0;
   w1 = system_font_get_string_advance(font, s);

   free(s);

   return pos + (x - w1) / (float)(w2 - w1);
}


void system_font_draw_string(SystemFont *font, int x, int y, plat_char *text, uint32_t color, uint32_t *pixels, int dest_width, int dest_height, int dest_stride)
{
   font_height fh;
   float size;
   int offset, width, height, ascent, descent, off_x, off_y;
   int i, j;
   int a, r, g, b;
   uint32_t p;
   rgb_color rgb_color;

   font->font->GetHeight(&fh);
   size = font->font->Size();
   offset = (int)ceilf(0.2f*size);
   ascent = (int)ceilf(fh.ascent * 1.025f);
   descent = (int)ceilf(fh.descent * 1.1f);
   width = (int)ceilf(font->font->StringWidth(text)) + offset*2;
   height = ascent+descent;
   off_x = offset;
   off_y = ascent;

   x -= offset;
   y -= ascent;

   if (x+width <= 0 || x >= dest_width) return;
   if (x < 0) {
      off_x += x;
      width += x;
      x = 0;
   }
   if (x+width > dest_width) {
      width = dest_width - x;
      if (width <= 0) return;
   }

   if (y+height <= 0 || y >= dest_height) return;
   if (y < 0) {
      off_y += y;
      height += y;
      y = 0;
   }
   if (y+height > dest_height) {
      height = dest_height - y;
      if (height <= 0) return;
   }

   BBitmap bitmap(BRect(0, 0, width-1, height-1), B_RGBA32, true);
   BView view(BRect(0, 0, width-1, height-1), "", 0, 0);

   uint32_t *bits = (uint32_t *)bitmap.Bits();
   int stride = bitmap.BytesPerRow()/4;

   for (i=0; i<height; i++) {
      for (j=0; j<width; j++) {
         p = pixels[(y+i)*dest_stride+(x+j)];
         a = (p >> 24) & 0xFF;
         r = (p >> 16) & 0xFF;
         g = (p >>  8) & 0xFF;
         b = (p >>  0) & 0xFF;
         if (a != 0 && a != 255) {
            r = r * 255 / a;
            g = g * 255 / a;
            b = b * 255 / a;
         }
         bits[i*stride+j] = (a << 24) | (r << 16) | (g << 8) | b;
      }
   }

   rgb_color.alpha = (color >> 24) & 0xFF;
   rgb_color.red = (color >> 16) & 0xFF;
   rgb_color.green = (color >> 8) & 0xFF;
   rgb_color.blue = (color >> 0) & 0xFF;
   if (rgb_color.alpha != 0) {
      rgb_color.red = rgb_color.red * 255 / rgb_color.alpha;
      rgb_color.green = rgb_color.green * 255 / rgb_color.alpha;
      rgb_color.blue = rgb_color.blue * 255 / rgb_color.alpha;
   }

   bitmap.Lock();
   bitmap.AddChild(&view);
   view.SetHighColor(rgb_color);
   view.SetFont(font->font);
   view.DrawString(text, BPoint(off_x, off_y));
   view.Sync();
   bitmap.RemoveChild(&view);
   bitmap.Unlock();

   for (i=0; i<height; i++) {
      for (j=0; j<width; j++) {
         p = pixels[(y+i)*dest_stride+(x+j)];
         a = (p >> 24) & 0xFF;
         r = (p >> 16) & 0xFF;
         g = (p >>  8) & 0xFF;
         b = (p >>  0) & 0xFF;
         if (a != 0 && a != 255) {
            r = r * 255 / a;
            g = g * 255 / a;
            b = b * 255 / a;
         }
         p = bits[i*stride+j];
         if (p != (uint32_t)((a << 24) | (r << 16) | (g << 8) | b)) {
            a = (p >> 24) & 0xFF;
            r = (p >> 16) & 0xFF;
            g = (p >>  8) & 0xFF;
            b = (p >>  0) & 0xFF;
            r = div255(r * a);
            g = div255(g * a);
            b = div255(b * a);
            pixels[(y+i)*dest_stride+(x+j)] = (a << 24) | (r << 16) | (g << 8) | b;
         }
      }
   }
}


static char *retrieve_deskbar_name(char **signature)
{
   BResources res(&self_ref);
   size_t size;
   if (signature) {
      *signature = strdup((const char *)res.LoadResource('MIMS', "BEOS:APP_SIG", &size));
   }
   const char *value = (const char *)res.LoadResource('CSTR', "fixgui:deskbar_name", &size);
   return value? strdup(value) : NULL;
}


static uint32_t *extract_pixels(Heap *heap, Value img, int *width, int *height)
{
   int w, h, stride;
   uint32_t *src, *dest, p;
   int a, r, g, b;
   int i, j;

   if (!fiximage_get_data(heap, img, &w, &h, &stride, &src, NULL, NULL)) {
      return NULL;
   }

   dest = (uint32_t *)malloc(w*h*4);
   if (!dest) {
      return NULL;
   }

   for (i=0; i<h; i++) {
      for (j=0; j<w; j++) {
         p = src[i*stride+j];
         a = (p >> 24) & 0xFF;
         r = (p >> 16) & 0xFF;
         g = (p >>  8) & 0xFF;
         b = (p >>  0) & 0xFF;
         if (a != 0) {
            r = r * 255 / a;
            g = g * 255 / a;
            b = b * 255 / a;
         }
         dest[i*w+j] = (a << 24) | (r << 16) | (g << 8) | b;
      }
   }

   *width = w;
   *height = h;
   return dest;
}


NotifyIcon *notify_icon_create(Heap *heap, Value *images, int num_images, char **error_msg)
{
   NotifyIcon *icon;
   
   if (active_notify_icon) {
      *error_msg = strdup("there can be only one notify icon");
      return NULL;
   }

   if (!deskbar_name) {
      deskbar_name = retrieve_deskbar_name(NULL);
   }
   if (!deskbar_name) {
      *error_msg = strdup("resource 'fixgui:deskbar_name' not found in the executable");
      return NULL;
   }

   if (num_images == 0) {
      return NULL;
   }
   
   icon = (NotifyIcon *)calloc(1, sizeof(NotifyIcon));
   if (!icon) return NULL;

   pthread_mutex_lock(&mutex);
   active_notify_icon = icon;
   pthread_mutex_unlock(&mutex);

   status_t error;
   icon->messenger = new BMessenger("application/x-vnd.Be-TSKB", -1, &error);
   if (error != B_OK) {
      pthread_mutex_lock(&mutex);
      active_notify_icon = NULL;
      pthread_mutex_unlock(&mutex);
      delete icon->messenger;
      free(icon);
      *error_msg = strdup("couldn't register notify icon into Deskbar");
      return NULL;
   }

   icon->pixels = extract_pixels(heap, images[0], &icon->width, &icon->height);

   BDeskbar deskbar;
   if (!deskbar.HasItem(deskbar_name) && deskbar.AddItem(&self_ref) != B_OK) {
      pthread_mutex_lock(&mutex);
      active_notify_icon = NULL;
      pthread_mutex_unlock(&mutex);
      delete icon->messenger;
      free(icon);
      *error_msg = strdup("couldn't register notify icon into Deskbar");
      return NULL;
   }
   remove_deskbar_item = 1;
   return icon;
}


static void init_notify_icon()
{
   NotifyIcon *icon;

   pthread_mutex_lock(&mutex);
   icon = active_notify_icon;
   pthread_mutex_unlock(&mutex);
   if (!icon) return;

   BMessage *msg = new BMessage(DESKBAR_FIXMSG);
   msg->AddString("signature", app_signature);
   msg->AddInt32("type", DMT_SET_ICON);
   msg->AddInt32("width", icon->width);
   msg->AddInt32("height", icon->height);
   msg->AddData("pixels", FIXIMAGE_TYPE, icon->pixels, icon->width*icon->height*4);
   icon->messenger->SendMessage(msg);

   free(icon->pixels);
   icon->pixels = NULL;
}


void notify_icon_get_sizes(int **sizes, int *cnt)
{
   *cnt = 1;
   *sizes = (int *)calloc(*cnt, sizeof(int));
   (*sizes)[0] = 16;
}


void notify_icon_destroy(NotifyIcon *icon)
{
}


int notify_icon_set_menu(NotifyIcon *icon, Menu *menu)
{
   if (menu && menu->menu) return 0;

   icon->menu = menu;
   return 1;
}


void io_notify()
{
}


void post_to_main_thread(void *data)
{
}


int modifiers_cmd_mask()
{
   return SCRIPT_MOD_CMD;
}


void quit_app()
{
   be_app->Lock();
   be_app->Quit();
   be_app->Unlock();
}


static Value func_common_show_async_message(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   char *title = NULL, *msg = NULL;
   int err, type;
   BAlert *alert;
   BMessage *bmsg;

   type = fixscript_get_int(params[1]);
   
   err = fixscript_get_string(heap, params[2], 0, -1, (char **)&title, NULL);
   if (!err) {
      err = fixscript_get_string(heap, params[3], 0, -1, (char **)&msg, NULL);
   }
   if (err) {
      fixscript_error(heap, error, err);
      goto error;
   }

   if (!(type >> 8)) {
      switch (type & 0xFF) {
         case MSG_OK:
            type |= MSG_ICON_INFO;
            break;

         case MSG_OK_CANCEL:     
         case MSG_YES_NO:
         case MSG_YES_NO_CANCEL:
            type |= MSG_ICON_QUESTION;
            break;
      }
   }

   alert = create_alert(type, title, msg);

   bmsg = new BMessage();
   bmsg->what = MSG_ASYNC_MSG_RESULT;
   bmsg->AddInt32("type", type);
   bmsg->AddPointer("heap", heap);
   AddValue(bmsg, "func", params[4]);
   AddValue(bmsg, "data", params[5]);
   fixscript_ref(heap, params[5]);

   alert->Go(new BInvoker(bmsg, BMessenger(script_looper)));

error:
   free(title);
   free(msg);
   return fixscript_int(0);
}


static Value func_haiku_is_present(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   return fixscript_int(1);
}


static Value func_haiku_set_notify_icon_persistent(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   NotifyIcon *icon;

   icon = notify_icon_get_native(heap, error, params[0]);
   if (!icon) return fixscript_int(0);

   remove_deskbar_item = 0;
   return fixscript_int(0);
}


void register_platform_gui_functions(Heap *heap)
{
   fixscript_register_native_func(heap, "common_show_async_message#6", func_common_show_async_message, NULL);
   fixscript_register_native_func(heap, "haiku_is_present#0", func_haiku_is_present, NULL);
   fixscript_register_native_func(heap, "haiku_set_notify_icon_persistent#1", func_haiku_set_notify_icon_persistent, NULL);
}


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

static void find_self_ref()
{
   image_info info;
   int32 cookie = 0;

   while (get_next_image_info(0, &cookie, &info) == B_OK) {
      if ((intptr_t)main >= (intptr_t)info.text && (intptr_t)main < (intptr_t)info.text + info.text_size) {
         get_ref_for_path(info.name, &self_ref);
         break;
      }
   }
}


extern "C" _EXPORT BView *instantiate_deskbar_item(float max_width, float max_height)
{
   FixDeskbarView *view;
   char *sig, *name;

   deskbar_mode = 1;
   find_self_ref();
   name = retrieve_deskbar_name(&sig);
   view = new FixDeskbarView(sig, name, (int)max_height, (int)max_height);
   free(sig);
   free(name);
   return view;
}


static void fatal_error(const char *msg)
{
   BApplication *app = new BApplication(app_signature? app_signature : "application/x-vnd.FixGUI-Application");
   BAlert *alert = new BAlert("Error", msg, "OK");
   alert->SetType(B_STOP_ALERT);
   alert->Go(new BInvoker(new BMessage(B_QUIT_REQUESTED), app));
   app->Run();
   delete app;
   exit(1);
}


int main(int argc, char **argv)
{
   FixApp *app;
   BMessage msg;

   pthread_mutex_init(&mutex, NULL);
   pthread_cond_init(&cond, NULL);
   __cocoa_threads_init();

   BResources *res = BApplication::AppResources();
   if (res) {
      size_t size;
      app_signature = (const char *)res->LoadResource('MIMS', "BEOS:APP_SIG", &size);
   }
   if (!app_signature) {
      fatal_error("Executable has no application signature defined in the resources!");
   }

   find_self_ref();
   if (!self_ref.name) {
      fatal_error("Can't find reference to own executable!");
   }

   app = new FixApp(app_signature);

   script_looper = new ScriptLooper();
   script_looper->Run();

   msg.what = MSG_INIT_APP;
   msg.AddInt32("argc", argc);
   msg.AddPointer("argv", argv);
   script_looper->PostMessage(&msg);

   app->Run();

   if (remove_deskbar_item) {
      BDeskbar deskbar;
      deskbar.RemoveItem(deskbar_name);
   }

   delete app;
   return 0;
}
