/* Swfdec
 * Copyright (C) 2010 Bob Jiang <bobjiang@msn.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 
 * Boston, MA  02110-1301  USA
 */

#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include "swfdec_np_scripting.h"
#include "flash_event.h"

extern int streamfd;
extern int eventfd;
extern int evretfd;
extern int externalfd;
extern int exterretfd;
extern int terminate_request;

/*** GTK-DOC ***/

/**
 * SECTION:SwfdecPlayerScripting
 * @title: SwfdecPlayerScripting
 * @short_description: integration with external scripts
 *
 * The @SwfdecPlayerScripting object is used to implement the ExternalInterface
 * object inside Flash. By providing your own custom implementation of this 
 * object using swfdec_player_set_scripting(), you can allow a Flash file to 
 * call your own custom functions. And you can call functions inside the Flash 
 * player.
 *
 * The functions are emitted using a custom XML format. The same format is used
 * to call functions in the Flash player. FIXME: describe the XML format in use. 
 */

/**
 * SwfdecPlayerScripting:
 *
 * The object you have to create.
 */

/**
 * SwfdecPlayerScriptingClass:
 * @js_get_id: optional function. Returns the string by which the scripting 
 *             object can reference itself in calls to the @js_call function.
 * @js_call: optional function. Emits Javascript code that you have to 
 *           interpret. You must also implement the @js_get_id function to make
 *           this function work. The return value must be in the custom XML 
 *           format.
 * @xml_call: optional function. If the Javascript functions aren't implemented,
 *            an xml format is used to encode the function call and the call is
 *            then done using this function. The return value is to be provided
 *            in the same custom XML format.
 *
 * You have to implement the virtual functions in this object to get a working
 * scripting implementation.
 */

/*** SWFDEC_PLAYER_SCRIPTING ***/

G_DEFINE_TYPE (SwfdecNPScripting, swfdec_np_scripting, SWFDEC_TYPE_PLAYER_SCRIPTING)

struct CallbackData
{
  SwfdecAsObject * obj;
  SwfdecAsObject *fun;
};

gboolean		swfdec_np_scripting_addCallback(SwfdecPlayerScripting *scripting,
					 const char * name,SwfdecAsObject * obj,SwfdecAsObject *fun)
{ 
  SwfdecAsContext *context;
  char *key = NULL;
  struct CallbackData * pData=NULL;
  GString * url=0;

  if (terminate_request)
  {
  	return 0;
  }

  key = g_strdup(name);
  if(!key)
	return 0;

  pData=(struct CallbackData *)g_malloc(sizeof(struct CallbackData));
  context = swfdec_gc_object_get_context (fun->relay);
  
  printf("----Context %p , Context Global %p\n",context,context->global);
  printf("----swfdec_np_scripting_addCallback name %s obj %p fun %p relay %p\n",name,obj,fun,fun->relay);
  if(!pData)
  {
	g_free(key);
    return 0;
  }

  url=g_string_new("javascript_cb:");
  g_string_append (url,name);
  g_string_append (url,"\n");

  if (externalfd != -1 && exterretfd != -1)
  {
	int maxfd = exterretfd > eventfd ? exterretfd : eventfd;
	int nfds = 0;
	fd_set readset;
	struct timeval tv;
	// send call
	write(externalfd, url->str, strlen(url->str));
	// wait call return
	tv.tv_sec = 5;
	tv.tv_usec = 0;
	FD_ZERO(&readset);
    FD_SET(exterretfd, &readset);
    if (eventfd != -1)
      FD_SET(eventfd, &readset);
    nfds = select(maxfd + 1, &readset, NULL, NULL, &tv);
    if (nfds == -1)
    {
    }
    else if (FD_ISSET(exterretfd, &readset))
    {
      char ret_buf[64];
      int ret_size=read(exterretfd,ret_buf,64);
	  printf("ret_size = %d\n", ret_size);
    }
    else if (FD_ISSET(eventfd, &readset))
    {
	}
  }
  
  g_string_free(url,1);

  pData->obj=obj;
  pData->fun=fun;
  g_hash_table_insert(SWFDEC_NP_SCRIPTING(scripting)->scripting_callbacks,key,pData);
  
  return 1;
}

struct obj_each_prop_data
{
  SwfdecPlayerScripting *scripting;
  GString * str;
};

static gboolean obj_each_prop(SwfdecAsObject *object, 
    const char *variable, SwfdecAsValue *value, guint flags, gpointer data)
{
  GString * url=0;
  SwfdecPlayerScripting *scripting=0;
  if(SWFDEC_AS_VARIABLE_HIDDEN&flags)
    return 1;
  url=((struct obj_each_prop_data *)data)->str;
  scripting=((struct obj_each_prop_data *)data)->scripting;
  
  if(url->str[url->len-1]!='{')
    g_string_append (url,",");
  g_string_append_printf(url,"\"%s\":\"%s\"",variable,
    swfdec_as_value_to_string(&(SWFDEC_NP_SCRIPTING(scripting)->player->context),*value));
  
  return 1;
}

gboolean		swfdec_np_scripting_call(SwfdecPlayerScripting *scripting,
					 const char * name,guint argc, SwfdecAsValue *argv,SwfdecAsValue *ret)
{
  guint i;
  GString * url=0;
  int iret = 0;
  
  printf("----swfdec_np_scripting_call name %s \n",name);

  if (terminate_request)
  {
  	SWFDEC_AS_VALUE_SET_BOOLEAN (ret, 0);
  	return 1;
  }
  
  /*Fixme: Marshal Args*/
  url=g_string_new("javascript:");
  g_string_append (url,name);
  g_string_append (url,"(");
  for(i=0;i<argc;i++)
  {
    printf("--------%d:type %d \n",i,SWFDEC_AS_VALUE_GET_TYPE(argv[i]));
    switch(SWFDEC_AS_VALUE_GET_TYPE(argv[i]))
    {
      case  SWFDEC_AS_TYPE_NULL:
      case  SWFDEC_AS_TYPE_BOOLEAN:
      case  SWFDEC_AS_TYPE_INT:
      case  SWFDEC_AS_TYPE_NUMBER:
      case  SWFDEC_AS_TYPE_STRING:
        g_string_append (url,
          swfdec_as_value_to_string(&(SWFDEC_NP_SCRIPTING(scripting)->player->context),argv[i]));
        break;
      case  SWFDEC_AS_TYPE_OBJECT:
        {
          SwfdecAsObject *pObj=swfdec_as_value_to_object	(&(SWFDEC_NP_SCRIPTING(scripting)->player->context),
						 argv[i]);
					struct obj_each_prop_data each_data={scripting,url};	 
					
					g_string_append (url,"{");
					
          swfdec_as_object_foreach(pObj,obj_each_prop,&each_data);
          g_string_append (url,"}");
        }
        break;
      case  SWFDEC_AS_TYPE_MOVIE:
        break;
      case  SWFDEC_AS_TYPE_UNDEFINED:
      default:
        break;
    }
    if(i+1<argc)
      g_string_append (url,",");
  }
  g_string_append (url,");");
  g_string_append (url,"\n");

  if (externalfd != -1 && exterretfd != -1)
  {
	int maxfd = exterretfd > eventfd ? exterretfd : eventfd;
	int nfds = 0;
	fd_set readset;
	struct timeval tv;
	// send call
	write(externalfd, url->str, strlen(url->str));
	// wait call return
	tv.tv_sec = 5;
	tv.tv_usec = 0;
	FD_ZERO(&readset);
    FD_SET(exterretfd, &readset);
    if (eventfd != -1)
      FD_SET(eventfd, &readset);
    nfds = select(maxfd + 1, &readset, NULL, NULL, &tv);
    if (nfds == -1)
    {
    }
    else if (FD_ISSET(exterretfd, &readset))
    {
      char ret_buf[64];
      int ret_size=read(exterretfd,ret_buf,64);
	  printf("ret_size = %d\n", ret_size);
	  iret = 1;
    }
    else if (FD_ISSET(eventfd, &readset))
    {
	  iret = 1;
	}
  }
  
  g_string_free(url,1);
  
  /*Fixme: Marshal Return*/
  SWFDEC_AS_VALUE_SET_BOOLEAN (ret, iret);
  return 1;
}

static void each_mark_callback(gpointer key,gpointer value,gpointer user_data)
{
  struct CallbackData * pData=(struct CallbackData *)value;
  if(!value)
    return;
  
  swfdec_as_object_mark(pData->obj);
  swfdec_as_object_mark(pData->fun);
}

void    swfdec_np_scripting_mark(SwfdecPlayerScripting *scripting)
{
  GHashTable *		scripting_callbacks=SWFDEC_NP_SCRIPTING(scripting)->scripting_callbacks;
  printf("----------swfdec_np_scripting_mark\n");
  g_hash_table_foreach(scripting_callbacks,each_mark_callback,0);
}

gboolean    swfdec_np_scripting_set_np(SwfdecNPScripting *scripting, SwfdecPlayer *player)
{
  scripting->player=player;
  
  printf("----swfdec_np_scripting_set_np player %p\n",player);
  return 1;
}

gboolean    swfdec_np_scripting_invoke(SwfdecNPScripting *scripting, void *data)
{
  SwfdecAsValue ret;
  SwfdecAsValue * pValues=0;
  SwfdecAsContext *context=0;
  FlashEvent *event = (FlashEvent *)data;
  FlashEventCallItem * item = NULL;
  int i, argCount = 0;
  char *name;

  if (event == NULL || event->type != EVENTTYPE_CALL
	  || event->call.count < 1)
	return 0;
  
  item = event->call.item;
  argCount = event->call.count - 1;

  if (item->type != 3)
	return 0;
  name = item->data;

  struct CallbackData * pData=
    (struct CallbackData *)g_hash_table_lookup(SWFDEC_NP_SCRIPTING(scripting)->scripting_callbacks,name);
  printf("--------swfdec_np_scripting_has_invoke enter\n");
  if(!pData || !SWFDEC_IS_AS_FUNCTION(pData->fun->relay))
    return 0;
  /*Fixme: Marshal Args*/
  if(argCount>0)
    pValues=(SwfdecAsValue *)g_malloc0(sizeof(SwfdecAsValue)*argCount);
  for(i=0;i<argCount;i++)
	{
		item = (FlashEventCallItem *)(item->data + item->len); 
		if(item->type==0)
		{
			printf("-----JS Var Int %d\n",*(int *)item->data);
			pValues[i]=swfdec_as_value_from_integer(
			  &(SWFDEC_NP_SCRIPTING(scripting)->player->context),*(int *)item->data);
		}
		else if(item->type==1)
		{
			printf("-----JS Var Boolean %d\n",item->data[0]);
			SWFDEC_AS_VALUE_SET_BOOLEAN(&pValues[i],item->data[0]);
		}
		else if(item->type==2)
		{
			printf("-----JS Var Double %f\n",*(double *)item->data);
			pValues[i]=swfdec_as_value_from_number(
			  &(SWFDEC_NP_SCRIPTING(scripting)->player->context),*(double *)item->data);
		}
		else if(item->type==3)
		{
			printf("-----JS Var String %s, len %d\n",item->data,strlen(item->data));
			SWFDEC_AS_VALUE_SET_STRING(&pValues[i],
			  swfdec_as_context_get_string(&SWFDEC_NP_SCRIPTING(scripting)->player->context,
			    item->data));
		}
	}
  
  context = swfdec_gc_object_get_context (pData->fun->relay);
  
  printf("----Context %p , Context Global %p\n",context,context->global);
  
  printf("--------swfdec_np_scripting_has_invoke call,fun %p,obj %p,relay %p\n",pData->fun,pData->obj,pData->fun->relay);
  swfdec_player_call(SWFDEC_NP_SCRIPTING(scripting)->player, pData->obj,pData->fun,argCount,pValues, &ret);
  printf("ret, type = %d\n", SWFDEC_AS_VALUE_GET_TYPE(ret));
  
  if(pValues)
    g_free(pValues);
  /*Fixme: Marshal Return*/
  if (evretfd != -1)
  	write(evretfd,"ret",3);
  return 1;
}

static void
swfdec_np_scripting_class_init (SwfdecNPScriptingClass *klass)
{
  klass->script_class.addCallback=swfdec_np_scripting_addCallback;
  klass->script_class.call=swfdec_np_scripting_call;
  klass->script_class.mark=swfdec_np_scripting_mark;
}

static void destroy_func(gpointer data)
{
  g_free(data);
}

static void
swfdec_np_scripting_init (SwfdecNPScripting *np_scripting)
{
  np_scripting->scripting_callbacks = g_hash_table_new_full  (g_str_hash, g_str_equal,destroy_func,destroy_func);
  np_scripting->player=0;
}

