#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <OS.h>
#include <image.h>
#include <Application.h>
#include <Bitmap.h>
#include <StorageDefs.h>
#include <MessageFilter.h>
#include <MessageRunner.h>
#include <TranslationUtils.h>
#include <List.h>
#include <View.h>
#include <Window.h>

/* maximum view depth */
#define MAX_VIEWS 10
/* max number of descriptors */
#define MAX_DESC 32
/* view polling interval */
#define POLLING_INTERVAL 2000000

/* loader vars */
static char gExePath[B_PATH_NAME_LENGTH];
static image_id gExeImg = -1;
static int (*gExeMainProc)(int argc, char **argv);

static thread_id gWaitForBAppThID = -1;

extern char **original_argv;
extern char **argv_save;
extern char **__libc_argv;
extern int original_argc;
extern int __libc_argc;

/* descriptor types */

struct tr_specifier {
  bool active;
  bool any;
  uint32 id;
  char *name;
};

struct tr_geometry {
  BRect rect;
  bool clip;
  bool scale;
};

struct tr_descriptor {
  bool active;
  /* specifiers */
  struct tr_specifier window;
  struct tr_specifier view[MAX_VIEWS];
  /* colors */
  bool set_view_color;
  bool set_low_color;
  bool set_high_color;
  rgb_color view_color;
  rgb_color low_color;
  rgb_color high_color;
  /* source */
  const char *src_name;
  bool src_desktop; /* use desktop bitmap */
  BBitmap *bitmap;
  BBitmap *desktop_bitmaps[32];
  struct tr_geometry src_geom;
  /* destination */
  struct tr_geometry dst_geom;
};


static struct tr_descriptor gDescriptors[MAX_DESC];
static int gNumDescriptors = 0;

static BMessenger gMyHandlerMsgr;

class MyHandler : public BHandler {
public:
  MyHandler();
  ~MyHandler();
  void MessageReceived(BMessage *msg);
  void UpdateViewBitmap(BView *view, struct tr_descriptor &desc);
  void ParseView(BView *view, int depth, uint32 mask);
  void ParseWindow(BWindow *win);
private:
  BList fWindowList;
  BList fDoneViewList;
};

filter_result msg_filter(BMessage *message, 
			  BHandler **target, 
			  BMessageFilter *filter)
{
  int i;
  switch (message->what) {
  case B_VIEW_MOVED:
  case B_VIEW_RESIZED:
  case B_WINDOW_MOVED:
  case B_WINDOW_RESIZED:
    return B_DISPATCH_MESSAGE;
  }
  return B_DISPATCH_MESSAGE;
}

static status_t build_specifier(struct tr_specifier &spec, const char *str)
{
  int i, len;
  bool hasalpha = false;
  spec.active = true;
  if (!strcmp(str, "*")) {
    spec.any = true;
    return B_OK;
  }
  len = strlen(str);
  for (i = 0; i < len; i++) {
    if (!isdigit(str[i]))
      hasalpha = true;
  }
  if (hasalpha) {
    spec.name = strdup(str);
    return B_OK;
  }
  spec.id = atol(str);
  return B_OK;
}

static int build_specifiers(struct tr_descriptor &desc, int argc, char **argv)
{
  int i;
  int viewcount = 0;
  int usedargs = 0;
  bool gotwin = false;
  for (i = 0; i < argc; i++) {
    if (!strcmp(argv[i], "of"))
      continue;
    if (!strcmp(argv[i], "Window")) {
      if (((i + 1) >= argc) || !viewcount)
        return -1; /* error */
      gotwin = true;
      usedargs = i + 2;
      break;
    }
    if (!strcmp(argv[i], "View")) {
      i++;
      viewcount++;
      if (viewcount > MAX_VIEWS)
        return -2;
      if (i >= argc)
        return -1;
    }
  }
  if (!gotwin)
    return -1;
  for (i = 0; i < argc; i++) {
    if (!strcmp(argv[i], "of"))
      continue;
    if (!strcmp(argv[i], "Window")) {
      i++;
      build_specifier(desc.window, argv[i]);
      break;
    }
    if (!strcmp(argv[i], "View")) {
      i++;
      build_specifier(desc.view[viewcount-1], argv[i]);
      viewcount--;
    }
  }
  return usedargs;
}


static void free_specifier(struct tr_specifier &spec)
{
  if (spec.name)
    free(spec.name);
}

static bool match_specifier(struct tr_specifier &spec, BHandler *hand)
{
  int32 i;
  BView *v;
  BWindow *w;
  if (!spec.active)
    return false;
  if (spec.any)
    return true;
  v = dynamic_cast<BView *>(hand);
  w = dynamic_cast<BWindow *>(hand);
  if (!spec.name) {
    BView *pv;
    BWindow *pw;
    if (w) {
      bool thisisit = false;
      be_app->Lock();
      thisisit = (w == be_app->WindowAt(spec.id));
      be_app->Unlock();
      if (thisisit)
        return true;
    }
    if (v) {
      pv = v->Parent();
      pw = v->Window();
      if (pv && (pv->ChildAt(spec.id) == v))
        return true;
      if (pw && (pw->ChildAt(spec.id) == v))
        return true;
    }
  }
  if (w && spec.name && !strcmp(w->Title(), spec.name))
    return true;
  if (v && spec.name && !strcmp(v->Name(), spec.name))
    return true;
  return false;
}

static bool match_descriptor(struct tr_descriptor &desc, BHandler *hand, int depth)
{
	int i;
	if (!desc.window.active)
		return false;
	if (!match_specifier(desc.window, hand))
		return false;
	for (i = 0; i < MAX_VIEWS; i++) {
		if (desc.view[i].active && !match_specifier(desc.view[i], hand))
			return false;
	}
	return true;
}


static BBitmap *load_bitmap(const char *filename)
{
  return BTranslationUtils::GetBitmap(filename);
}

static status_t reload_desktop_bitmaps(void)
{
	return B_OK;
}

static status_t load_bitmaps(void)
{
	int i;
	for (i = 0; i < gNumDescriptors; i++) {
		if (!gDescriptors[i].active)
			continue;
		if (gDescriptors[i].src_name)
			gDescriptors[i].bitmap = load_bitmap(gDescriptors[i].src_name);
	}
	return B_OK;
}

static void dump_descriptors(void)
{
	int i, j;
	for (i = 0; i < gNumDescriptors; i++) {
		struct tr_specifier *sp;
		if (!gDescriptors[i].active) {
			printf("[%d] dis\n", i);
			continue;
		}
		printf("[%d] {", i);
		sp = &(gDescriptors[i].window);
		printf("w{%d, %d, %s} ", (int)sp->any, sp->id, sp->name);
		for (j = 0; j < MAX_VIEWS; j++) {
			sp = &(gDescriptors[i].view[j]);
			if (!sp->active)
				break;
			printf("v{%d, %d, %s} ", (int)sp->any, sp->id, sp->name);
		}
		printf("b:%s @%p ", gDescriptors[i].src_name, gDescriptors[i].bitmap);
		printf("}\n");
	}
}

MyHandler::MyHandler()
  :BHandler("transpexec spying handler")
{
  fWindowList.MakeEmpty();
  fDoneViewList.MakeEmpty();
}

MyHandler::~MyHandler()
{
}

void MyHandler::MessageReceived(BMessage *msg)
{
  int i;
  BMessageFilter *afilter;
  switch (msg->what) {
  case 'plop':
    i = be_app->CountWindows();
    for (; i; i--) {
      BWindow *win = be_app->WindowAt(i-1);
      if (win && !fWindowList.HasItem(win)) {
        fWindowList.AddItem(win);
        afilter = new BMessageFilter(B_ANY_DELIVERY, 
                                     B_ANY_SOURCE, 
                                     msg_filter);
        win->Lock();
        win->AddCommonFilter(afilter);
        win->Unlock();
      }
      win->Lock();
      ParseWindow(win);
      win->Unlock();
    }
    break;
  }
  BHandler::MessageReceived(msg);
}

void MyHandler::UpdateViewBitmap(BView *view, struct tr_descriptor &desc)
{
	rgb_color vc, lc, hc;
	if (!view)
		return;
	if (fDoneViewList.HasItem(view))
		return;
	fDoneViewList.AddItem(view);
	if (desc.bitmap)
		view->SetViewBitmap(desc.bitmap);
	if (desc.set_view_color)
		view->SetViewColor(desc.view_color);
	if (desc.set_low_color)
		view->SetLowColor(desc.low_color);
	if (desc.set_high_color)
		view->SetHighColor(desc.high_color);
	vc = view->ViewColor();
	lc = view->LowColor();
	hc = view->HighColor();
	printf("view '%s' vc {%d,%d,%d,%d}, lc {%d,%d,%d,%d}, hc {%d,%d,%d,%d}\n",
			view->Name(),
			vc.red, vc.green, vc.blue, vc.alpha,
			lc.red, lc.green, lc.blue, lc.alpha,
			hc.red, hc.green, hc.blue, hc.alpha);
}

void MyHandler::ParseView(BView *view, int depth, uint32 mask)
{
	int i;
	//int j;
	bool donehere = false;
	if (depth >= MAX_VIEWS)
		return;
	for (i = 0; i < gNumDescriptors; i++) {
		if (!(mask & (1 << i)))
			continue;
		if (!match_specifier(gDescriptors[i].view[depth], view)) {
			mask &= ~(1 << i);
			continue;
		}
		if (!donehere && (depth + 1 < MAX_VIEWS) && !gDescriptors[i].view[depth+1].active) {
			donehere = true;
			if (!fDoneViewList.HasItem(view)) {
				UpdateViewBitmap(view, gDescriptors[i]);
				view->Invalidate(view->Bounds());
				break;
			}
		}
	}
	for (i = 0; view->ChildAt(i); i++)
		ParseView(view->ChildAt(i), depth + 1, mask);
}

void MyHandler::ParseWindow(BWindow *win)
{
	int i;
	uint32 mask = 0;
	for (i = 0; i < gNumDescriptors; i++)
		if (match_specifier(gDescriptors[i].window, win))
			mask |= (1 << i);
	for (i = 0; win->ChildAt(i); i++) {
		ParseView(win->ChildAt(i), 0, mask);
	}
}

int32 wait_for_loopers(void *arg)
{
  MyHandler *myh;
  /* wait for BApplication */
  while (!be_app)
    snooze(50000);
  myh = new MyHandler;
  load_bitmaps();
  //dump_descriptors();
  be_app->Lock();
  be_app->AddHandler(myh);
  be_app->Unlock();
  gMyHandlerMsgr = BMessenger(myh);
  new BMessageRunner(gMyHandlerMsgr, new BMessage('plop'), POLLING_INTERVAL);
  return 0;
}



static int usage(char *argv0)
{
	printf("usage:\n");
	//printf("%s [-firstw <n>] [-view <vn>] app [args...]\n", argv0);
	printf("%s Descriptor [Descriptor...] /path/to/app [args...]\n\n", argv0);
	printf("Descriptor is: File bitmapfile|Desktop [ViewColor r.g.b.a|transparent]\n");
	printf(" [LowColor r.g.b.a|transparent] [HighColor r.g.b.a|transparent]\n");
	printf(" [Geometry g] [Tint [t|r,g,b,a]] [[...] View v2] View v Window w\n\n");
	printf("File specifies the bitmap filename.\n");
	printf("Desktop tells to use the desktop bitmap.\n");
	printf("Geometry specifies the target geometry.\n");
	printf("Tint specifies the tint color.\n");
	printf("View ... specifies the target view, as with hey\n");
	printf("(can use 'View v of ...'), use '*' for wildcard.\n");
	printf("\n");
	return 0;
}

static int badarg(const char *argv0, const char *reason)
{
	printf("%s: %s\n", argv0, reason);
	return 1;
}

int main(int argc, char **argv)
{
	int i;
	status_t err;
	char *trapp_name;
	if (argc < 2)
		return usage(argv[0]);
	trapp_name = argv[0];
	memset(gDescriptors, 0, sizeof(gDescriptors));
	for (i = 1; i < argc; i++) {
		/* that's certainly the path to the app */
		if (!strncmp(argv[i], "/", 1))
			break;
		if (!strcmp(argv[i], "Desktop")) {
			return badarg(argv[0], "Desktop mode unimplemented");
		} else if (!strcmp(argv[i], "File")) {
			i++;
			if (i >= argc)
				return badarg(argv[0], "missing filename");
			if (gDescriptors[gNumDescriptors].bitmap)
				return badarg(argv[0], "bitmap already given");
			gDescriptors[gNumDescriptors].src_name = argv[i];
		} else if (!strcmp(argv[i], "ViewColor")) {
			rgb_color col = B_TRANSPARENT_32_BIT;
			i++;
			if (i >= argc)
				return badarg(argv[0], "missing view color");
			if (strncmp(argv[i], "tr", 2))
				sscanf(argv[i], "%d.%d.%d.%d", &col.red, &col.green, &col.blue, &col.alpha);
			gDescriptors[gNumDescriptors].view_color = col;
			gDescriptors[gNumDescriptors].set_view_color = true;
		} else if (!strcmp(argv[i], "LowColor")) {
			rgb_color col = B_TRANSPARENT_32_BIT;
			i++;
			if (i >= argc)
				return badarg(argv[0], "missing low color");
			if (strncmp(argv[i], "tr", 2))
				sscanf(argv[i], "%d.%d.%d.%d", &col.red, &col.green, &col.blue, &col.alpha);
			gDescriptors[gNumDescriptors].low_color = col;
			gDescriptors[gNumDescriptors].set_low_color = true;
		} else if (!strcmp(argv[i], "HighColor")) {
			rgb_color col = B_TRANSPARENT_32_BIT;
			i++;
			if (i >= argc)
				return badarg(argv[0], "missing high color");
			if (strncmp(argv[i], "tr", 2))
				sscanf(argv[i], "%d.%d.%d.%d", &col.red, &col.green, &col.blue, &col.alpha);
			gDescriptors[gNumDescriptors].high_color = col;
			gDescriptors[gNumDescriptors].set_high_color = true;
		} else if (!strncmp(argv[i], "Geom", 4)) {
			i++;
			if (i >= argc)
				return badarg(argv[0], "missing geometry");
			return badarg(argv[0], "geometry unimplemented");
		} else if (!strcmp(argv[i], "Tint")) {
			i++;
			if (i >= argc)
				return badarg(argv[0], "missing tint argument");
			return badarg(argv[0], "tint unimplemented");
		} else if (!strcmp(argv[i], "View")) {
			int used;
			used = build_specifiers(gDescriptors[gNumDescriptors], argc - i, &argv[i]);
			if (used < -1)
				return badarg(argv[0], "View depth too big");
			if (used < 0)
				return badarg(argv[0], "specifier arg error");
			gDescriptors[gNumDescriptors].active = true;
			gNumDescriptors++;
			i += used - 1;
		} else {
			return usage(argv[0]);
		}
	}
	if (argc - i < 1)
		return usage(argv[0]);
  
  argv += i;
  argc -= i;
  
  /*for (i = 0; i < argc; i++)
    printf("argv[%d] = %s\n", i, argv[i]);*/
  gExePath[0] = '\0';
  if (strncmp(argv[0], "/", 1)) {
    getcwd(gExePath, B_PATH_NAME_LENGTH-10);
    strcat(gExePath, "/");
  }
  strncat(gExePath, argv[0], B_PATH_NAME_LENGTH-1-strlen(gExePath));
  //printf("cmd = %s\n", gExePath);
  gExeImg = load_add_on(gExePath);
  if (gExeImg < B_OK) {
    fprintf(stderr, "load_add_on: %s\n", strerror(gExeImg));
    return 1;
  }
  // original are static...
  //printf("original: %d; %s\n", original_argc, *original_argv);
  //printf("libc: %d; %s\n", __libc_argc, *__libc_argv);
  //printf("save: %s\n", *argv_save);

  //argv[0] = trapp_name;
  __libc_argv = argv;
  __libc_argc = argc;
  argv_save = argv;
  
  err = get_image_symbol(gExeImg, "main", B_SYMBOL_TYPE_TEXT, (void **)&gExeMainProc);
  if (err < B_OK) {
    fprintf(stderr, "get_image_symbol(main): %s\n", strerror(gExeImg));
    return 1;
  }
  //printf("main @ %p\n", gExeMainProc);

  resume_thread(spawn_thread(wait_for_loopers, 
			     "waiting for BLoopers", 
			     B_NORMAL_PRIORITY, NULL));

  return gExeMainProc(argc, argv);
}

