/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file. */


#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#ifdef _WIN32
#include "win/pthread.h"
#include <windows.h>
//static inline void sleep(unsigned ms) { Sleep(ms*1000); }

#else
#include <pthread.h>
#endif

static int initflag;
#ifndef __PNACL
#define PNACL_message printf
#else
void PNACL_message(const char* format, ...);
#endif

void OS_init();
void *OS_filestr(long *allocsizep,char *fname);
char *clonestr(char *);

void CHROMEAPP_MAIN(void *arg);

void *CHROMEAPP_NAME(void *arg)
{
    if ( arg == 0 )
#ifdef __linux__
        arg = 0;
#else
        arg = 0;
#endif
    while ( initflag == 0 )
        usleep(1000000);
    PNACL_message("%s start.(%s)\n",CHROMEAPP_STR,(char *)arg);
    CHROMEAPP_MAIN(arg);
    return(0);
}

#ifdef __PNACL
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/param.h>
#include <sys/select.h>
#include <sys/stat.h>
#include "includes/ppapi/c/ppb.h"
#include "includes/ppapi/c/ppb_var.h"
#include "includes/ppapi/c/ppb_instance.h"
#include "includes/ppapi/c/ppb_messaging.h"
#include "includes/ppapi/c/ppb_var_array.h"
#include "includes/ppapi/c/ppb_var_dictionary.h"
#include "includes/ppapi/c/pp_errors.h"
#include "includes/ppapi/c/ppp_messaging.h"
#include "includes/ppapi/c/ppp_instance.h"
typedef int (*PSMainFunc_t)(int argc, char *argv[]);

#if defined(WIN32)
#define va_copy(d, s) ((d) = (s))
#endif

#ifndef __PNACL
/*int32_t PSGetInstanceId()
{
    return(4);
}*/
#endif

typedef int (*HandleFunc)(struct PP_Var params,struct PP_Var* out_var,const char** error);
typedef struct { const char* name; HandleFunc function; } FuncNameMapping;
static PP_Instance g_instance = 0;
static PPB_GetInterface g_get_browser_interface = NULL;
static PPB_Messaging* g_ppb_messaging = NULL;
PPB_Var* g_ppb_var = NULL;
PPB_VarArray* g_ppb_var_array = NULL;
PPB_VarDictionary* g_ppb_var_dictionary = NULL;
int CHROMEAPP_HANDLER(struct PP_Var params,struct PP_Var *output,const char **out_error);
static FuncNameMapping g_function_map[] = { { CHROMEAPP_STR, CHROMEAPP_HANDLER }, { NULL, NULL } };
struct PP_Var CStrToVar(const char* str) { return g_ppb_var->VarFromUtf8(str, (int32_t)strlen(str)); }

char *VprintfToNewString(const char* format, va_list args)
{
    va_list args_copy; int length; char *buffer; int result;
    va_copy(args_copy, args);
    length = vsnprintf(NULL, 0, format, args);
    buffer = (char*)malloc(length + 1); // +1 for NULL-terminator.
    result = vsnprintf(&buffer[0], length + 1, format, args_copy);
    if ( result != length )
    {
        assert(0);
        return NULL;
    }
    return buffer;
}

char *PrintfToNewString(const char *format, ...)
{
    va_list args; char *result;
    va_start(args, format);
    result = VprintfToNewString(format, args);
    va_end(args);
    return result;
}

struct PP_Var VprintfToVar(const char* format, va_list args)
{
    struct PP_Var var; char *string = VprintfToNewString(format, args);
    var = g_ppb_var->VarFromUtf8(string, (int32_t)strlen(string));
    free(string);
    return var;
}

static const char *VarToCStr(struct PP_Var var)
{
    uint32_t length; char *new_str; const char *str = g_ppb_var->VarToUtf8(var, &length);
    if ( str == NULL )
        return NULL;
    new_str = (char*)malloc(length + 1);
    memcpy(new_str, str, length); // str is NOT NULL-terminated. Copy using memcpy.
    new_str[length] = 0;
    return new_str;
}

struct PP_Var GetDictVar(struct PP_Var dict, const char* key)
{
    struct PP_Var key_var = CStrToVar(key);
    struct PP_Var value = g_ppb_var_dictionary->Get(dict, key_var);
    g_ppb_var->Release(key_var);
    return value;
}

void PNACL_message(const char* format, ...)
{
    va_list args;
    va_start(args, format);
#ifdef __PNACL
    struct PP_Var var;
    var = VprintfToVar(format, args);
    g_ppb_messaging->PostMessage(g_instance, var);
    g_ppb_var->Release(var);
#else
    printf(format,args);
#endif
    va_end(args);
}
void PostMessage(const char* format, ...)
{
    va_list args;
    va_start(args, format);
#ifdef __PNACL
    struct PP_Var var;
    var = VprintfToVar(format, args);
    g_ppb_messaging->PostMessage(g_instance, var);
    g_ppb_var->Release(var);
#else
    printf(format,args);
#endif
    va_end(args);
}

/**
 * Given a message from JavaScript, parse it for functions and parameters.
 *
 * The format of the message is:
 * {
 *  "cmd": <function name>,
 *  "args": [<arg0>, <arg1>, ...]
 * }
 *
 * @param[in] message The message to parse.
 * @param[out] out_function The function name.
 * @param[out] out_params A PP_Var array.
 * @return 0 if successful, otherwise 1.
 */
static int ParseMessage(struct PP_Var message,const char **out_function,struct PP_Var *out_params)
{
    char *jsonstr=0;
    if ( message.type != PP_VARTYPE_DICTIONARY )
    {
        PNACL_message("illegal message.%d != %d\n",message.type,PP_VARTYPE_DICTIONARY);
        return(1);
    }
    struct PP_Var cmd_value = GetDictVar(message, "cmd");
    *out_function = VarToCStr(cmd_value);
    g_ppb_var->Release(cmd_value);
    *out_params = GetDictVar(message, "args");
    jsonstr = (char *)VarToCStr(*out_params);
    PNACL_message("Parse.(%s) cmd.(%s) cmdtype.%d out.%d\n",*out_function,jsonstr!=0?jsonstr:"null",cmd_value.type,out_params->type);
    if ( jsonstr != 0 )
    {
        PNACL_message("Parse.(%s) cmd.(%s)\n",*out_function,jsonstr);
        if ( cmd_value.type != PP_VARTYPE_STRING )
            return(1);
        if ( out_params->type != PP_VARTYPE_ARRAY )
            return(1);
        
        return(0);
    } else return(1);
}

static HandleFunc GetFunctionByName(const char* function_name)
{
    FuncNameMapping* map_iter = g_function_map;
    for (; map_iter->name; ++map_iter)
    {
        if (strcmp(map_iter->name, function_name) == 0)
            return map_iter->function;
    }
    return NULL;
}

/**
 * Handle as message from JavaScript on the worker thread.
 *
 * @param[in] message The message to parse and handle.
 */
static void HandleMessage(struct PP_Var message)
{
    const char *function_name,*error; struct PP_Var params,result_var;
    if ( ParseMessage(message, &function_name, &params) != 0 )
    {
        PNACL_message("Error: Unable to parse message");
        return;
    }
    HandleFunc function = GetFunctionByName(function_name);
    if ( function == 0 )
    {
        PNACL_message("Error: Unknown function \"%s\"", function_name); // Function name wasn't found.
        return;
    }
    // Function name was found, call it.
    int result = (*function)(params, &result_var, &error);
    if ( result != 0 )
    {
        if ( error != NULL )
        {
            PNACL_message("Error: \"%s\" failed: %s.", function_name, error);
            free((void*)error);
        }
        else PNACL_message("Error: \"%s\" failed.", function_name);
        return;
    }
    // Function returned an output dictionary. Send it to JavaScript.
    g_ppb_messaging->PostMessage(g_instance, result_var);
    g_ppb_var->Release(result_var);
}

#define MAX_QUEUE_SIZE 256

// A mutex that guards |g_queue|.
static pthread_mutex_t g_queue_mutex;
// A condition variable that is signalled when |g_queue| is not empty.
static pthread_cond_t g_queue_not_empty_cond;

/** A circular queue of messages from JavaScript to be handled.
 *
 * If g_queue_start < g_queue_end:
 *   all elements in the range [g_queue_start, g_queue_end) are valid.
 * If g_queue_start > g_queue_end:
 *   all elements in the ranges [0, g_queue_end) and
 *   [g_queue_start, MAX_QUEUE_SIZE) are valid.
 * If g_queue_start == g_queue_end, and g_queue_size > 0:
 *   all elements in the g_queue are valid.
 * If g_queue_start == g_queue_end, and g_queue_size == 0:
 *   No elements are valid. */
static struct PP_Var g_queue[MAX_QUEUE_SIZE];
static int g_queue_start = 0; // The index of the head of the queue
static int g_queue_end = 0; // The index of the tail of the queue, non-inclusive.
static int g_queue_size = 0;
// NOTE: this function assumes g_queue_mutex lock is held. @return non-zero if the queue is empty
static int IsQueueEmpty() { return g_queue_size == 0; }
// NOTE: this function assumes g_queue_mutex lock is held. @return non-zero if the queue is full
static int IsQueueFull() { return g_queue_size == MAX_QUEUE_SIZE; }

void InitializeMessageQueue()
{
    pthread_mutex_init(&g_queue_mutex, NULL);
    pthread_cond_init(&g_queue_not_empty_cond, NULL);
}

int EnqueueMessage(struct PP_Var message)
{
    pthread_mutex_lock(&g_queue_mutex);
    // We shouldn't block the main thread waiting for the queue to not be full, so just drop the message.
    if ( IsQueueFull() != 0)
    {
        PNACL_message("EnqueueMessage: full Q, drop message\n");
        pthread_mutex_unlock(&g_queue_mutex);
        return(0);
    }
    g_queue[g_queue_end] = message;
    g_queue_end = (g_queue_end + 1) % MAX_QUEUE_SIZE;
    g_queue_size++;
    pthread_cond_signal(&g_queue_not_empty_cond);
    pthread_mutex_unlock(&g_queue_mutex);
    return 1;
}

struct PP_Var DequeueMessage()
{
    struct PP_Var message;
    pthread_mutex_lock(&g_queue_mutex);
    while ( IsQueueEmpty() != 0 )
        pthread_cond_wait(&g_queue_not_empty_cond, &g_queue_mutex);
    message = g_queue[g_queue_start];
    g_queue_start = (g_queue_start + 1) % MAX_QUEUE_SIZE;
    g_queue_size--;
    pthread_mutex_unlock(&g_queue_mutex);
    return(message);
}

/**
 * A worker thread that handles messages from JavaScript.
 * @param[in] user_data Unused.
 * @return unused.
 */
void *HandleMessageThread(void *user_data)
{
    while ( 1 )
    {
        struct PP_Var message = DequeueMessage();
        HandleMessage(message);
        g_ppb_var->Release(message);
    }
}

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#undef mount
#undef umount

static PP_Bool Instance_DidCreate(PP_Instance instance,uint32_t argc,const char* argn[],const char* argv[])
{
    int nacl_io_init_ppapi(PP_Instance instance, PPB_GetInterface get_interface);
    static pthread_t g_handle_message_thread;
    static pthread_t chromeapp_thread;
    long allocsize;
    g_instance = instance;
    // By default, nacl_io mounts / to pass through to the original NaCl
    // filesystem (which doesn't do much). Let's remount it to a memfs
    // filesystem.
    OS_init();
    InitializeMessageQueue();
    pthread_create(&g_handle_message_thread, NULL, &HandleMessageThread, NULL);
    pthread_create(&chromeapp_thread,NULL,&CHROMEAPP_NAME,OS_filestr(&allocsize,CHROMEAPP_CONF));
    nacl_io_init_ppapi(instance,g_get_browser_interface);
    umount("/");
    mount("", "/memfs", "memfs", 0, "");
    mount("",                                       /* source */
          "/",                            /* target */
          "html5fs",                                /* filesystemtype */
          0,                                        /* mountflags */
          "type=PERSISTENT,expected_size=10000000000"); /* data */
    mount("",       /* source. Use relative URL */
          "/http",  /* target */
          "httpfs", /* filesystemtype */
          0,        /* mountflags */
          "");      /* data */
    PNACL_message("finished DidCreate %s\n",CHROMEAPP_STR);
    initflag = 1;
    return PP_TRUE;
}

static void Instance_DidDestroy(PP_Instance instance) { }

static void Instance_DidChangeView(PP_Instance instance,PP_Resource view_resource) { }

static void Instance_DidChangeFocus(PP_Instance instance, PP_Bool has_focus) { }

static PP_Bool Instance_HandleDocumentLoad(PP_Instance instance,PP_Resource url_loader)
{
    // NaCl modules do not need to handle the document load function.
    return PP_FALSE;
}

static void Messaging_HandleMessage(PP_Instance instance,struct PP_Var message)
{
    if ( message.type != PP_VARTYPE_DICTIONARY ) // Special case for jspipe input handling
    {
        PNACL_message("Got unexpected message type: %d\n", message.type);
        return;
    }
    struct PP_Var pipe_var = CStrToVar("pipe");
    struct PP_Var pipe_name = g_ppb_var_dictionary->Get(message, pipe_var);
    g_ppb_var->Release(pipe_var);
    if ( pipe_name.type == PP_VARTYPE_STRING ) // Special case for jspipe input handling
    {
        char file_name[PATH_MAX];
        snprintf(file_name, PATH_MAX, "/dev/%s", VarToCStr(pipe_name));
        int fd = open(file_name, O_RDONLY);
        g_ppb_var->Release(pipe_name);
        if ( fd < 0 )
        {
            PNACL_message("Warning: opening %s failed.", file_name);
            goto done;
        }
        //if ( ioctl(fd, NACL_IOC_HANDLEMESSAGE, &message) != 0 )
        //    PNACL_message("Error: ioctl on %s failed: %s", file_name, strerror(errno));
        close(fd);
        goto done;
    }
    g_ppb_var->AddRef(message);
    if ( !EnqueueMessage(message) )
    {
        g_ppb_var->Release(message);
        PNACL_message("Warning: dropped message because the queue was full.");
    }
done:
    g_ppb_var->Release(pipe_name);
}

#define GET_INTERFACE(var, type, name)            \
var = (type*)(get_browser(name));               \
if (!var) {                                     \
printf("Unable to get interface " name "\n"); \
return PP_ERROR_FAILED;                       \
}

//PP_EXPORT
int32_t PPP_InitializeModule(PP_Module a_module_id,PPB_GetInterface get_browser)
{
    g_get_browser_interface = get_browser;
    GET_INTERFACE(g_ppb_messaging, PPB_Messaging, PPB_MESSAGING_INTERFACE);
    GET_INTERFACE(g_ppb_var, PPB_Var, PPB_VAR_INTERFACE);
    GET_INTERFACE(g_ppb_var_array, PPB_VarArray, PPB_VAR_ARRAY_INTERFACE);
    GET_INTERFACE(g_ppb_var_dictionary, PPB_VarDictionary, PPB_VAR_DICTIONARY_INTERFACE);
    return PP_OK;
}

//PP_EXPORT
const void *PPP_GetInterface(const char* interface_name)
{
    if ( strcmp(interface_name,PPP_INSTANCE_INTERFACE) == 0 )
    {
        static PPP_Instance instance_interface =
        {
            &Instance_DidCreate,
            &Instance_DidDestroy,
            &Instance_DidChangeView,
            &Instance_DidChangeFocus,
            &Instance_HandleDocumentLoad,
        };
        return &instance_interface;
    }
    else if ( strcmp(interface_name, PPP_MESSAGING_INTERFACE) == 0 )
    {
        static PPP_Messaging messaging_interface = { &Messaging_HandleMessage };
        return &messaging_interface;
    }
    return NULL;
}

//PP_EXPORT
void PPP_ShutdownModule() { }

#define CHECK_PARAM_COUNT(name, expected)                                   \
if (GetNumParams(params) != expected) {                                   \
*out_error = PrintfToNewString(#name " takes " #expected " parameters." \
" Got %d", GetNumParams(params));        \
return 1;                                                               \
}

#define PARAM_STRING(index, var)                                    \
char* var;                                                        \
uint32_t var##_len;                                               \
if (GetParamString(params, index, &var, &var##_len, out_error)) { \
return 1;                                                       \
}

#define CREATE_RESPONSE(name) CreateResponse(output, name, out_error)
#define RESPONSE_STRING(var) AppendResponseString(output, var, out_error)
#define RESPONSE_INT(var) AppendResponseInt(output, var, out_error)
#define MAX_PARAMS 4
static char* g_ParamStrings[MAX_PARAMS];
/**
 * Get the number of parameters.
 * @param[in] params The parameter array.
 * @return uint32_t The number of parameters in the array.
 */
static uint32_t GetNumParams(struct PP_Var params) {
    return g_ppb_var_array->GetLength(params);
}


/**
 * Create a response PP_Var to send back to JavaScript.
 * @param[out] response_var The response PP_Var.
 * @param[in] cmd The name of the function that is being executed.
 * @param[out] out_error An error message, if this call failed.
 */
static void CreateResponse(struct PP_Var* response_var,
                           const char* cmd,
                           const char** out_error) {
    PP_Bool result;
    
    struct PP_Var dict_var = g_ppb_var_dictionary->Create();
    struct PP_Var cmd_key = CStrToVar("cmd");
    struct PP_Var cmd_value = CStrToVar(cmd);
    
    result = g_ppb_var_dictionary->Set(dict_var, cmd_key, cmd_value);
    g_ppb_var->Release(cmd_key);
    g_ppb_var->Release(cmd_value);
    
    if (!result) {
        g_ppb_var->Release(dict_var);
        *out_error =
        PrintfToNewString("Unable to set \"cmd\" key in result dictionary");
        return;
    }
    
    struct PP_Var args_key = CStrToVar("args");
    struct PP_Var args_value = g_ppb_var_array->Create();
    result = g_ppb_var_dictionary->Set(dict_var, args_key, args_value);
    g_ppb_var->Release(args_key);
    g_ppb_var->Release(args_value);
    
    if (!result) {
        g_ppb_var->Release(dict_var);
        *out_error =
        PrintfToNewString("Unable to set \"args\" key in result dictionary");
        return;
    }
    
    *response_var = dict_var;
}
/**
 * Append a PP_Var to the response dictionary.
 * @param[in,out] response_var The response PP_var.
 * @param[in] value The value to add to the response args.
 * @param[out] out_error An error message, if this call failed.
 */
static void AppendResponseVar(struct PP_Var* response_var,
                              struct PP_Var value,
                              const char** out_error) {
    struct PP_Var args_value = GetDictVar(*response_var, "args");
    uint32_t args_length = g_ppb_var_array->GetLength(args_value);
    PP_Bool result = g_ppb_var_array->Set(args_value, args_length, value);
    if (!result) {
        // Release the dictionary that was there before.
        g_ppb_var->Release(*response_var);
        
        // Return an error message instead.
        *response_var = PP_MakeUndefined();
        *out_error = PrintfToNewString("Unable to append value to result");
        return;
    }
}

/**
 * Append a string to the response dictionary.
 * @param[in,out] response_var The response PP_var.
 * @param[in] value The value to add to the response args.
 * @param[out] out_error An error message, if this call failed.
 */
static void AppendResponseString(struct PP_Var* response_var,
                                 const char* value,
                                 const char** out_error) {
    struct PP_Var value_var = CStrToVar(value);
    AppendResponseVar(response_var, value_var, out_error);
    g_ppb_var->Release(value_var);
}

/**
 * Get a parameter at |index| as a string.
 * @param[in] params The parameter array.
 * @param[in] index The index in |params| to get.
 * @param[out] out_string The output string.
 * @param[out] out_string_len The length of the output string.
 * @param[out] out_error An error message, if this operation failed.
 * @return int 0 if successful, otherwise 1.
 */
static int GetParamString(struct PP_Var params,
                          uint32_t index,
                          char** out_string,
                          uint32_t* out_string_len,
                          const char** out_error) {
    if (index >= MAX_PARAMS) {
        *out_error = PrintfToNewString("Param index %u >= MAX_PARAMS (%d)",
                                       index, MAX_PARAMS);
        return 1;
    }
    
    struct PP_Var value = g_ppb_var_array->Get(params, index);
    if (value.type != PP_VARTYPE_STRING) {
        *out_error =
        PrintfToNewString("Expected param at index %d to be a string not.%d", index,value.type);
        return 1;
    }
    
    uint32_t length;
    const char* var_str = g_ppb_var->VarToUtf8(value, &length);
    
    char* string = (char*)malloc(length + 1);
    memcpy(string, var_str, length);
    string[length] = 0;
    
    /* Put the allocated string in g_ParamStrings. This keeps us from leaking
     * each parameter string, without having to do manual cleanup in every
     * Handle* function below.
     */
    free(g_ParamStrings[index]);
    g_ParamStrings[index] = string;
    
    
    *out_string = string;
    *out_string_len = length;
    return 0;
}

int CHROMEAPP_HANDLER(struct PP_Var params,struct PP_Var *output,const char **out_error)
{
    char *CHROMEAPP_JSON(char *);
    char *retstr;
    PNACL_message("inside Handle_%s\n",CHROMEAPP_STR);
    CHECK_PARAM_COUNT(CHROMEAPP_STR, 1);
    PARAM_STRING(0,jsonstr);
    if ( jsonstr == 0 )
        retstr = clonestr("{\"error\":\"illegal null jsonstr received\"}");
    else if ( (retstr= CHROMEAPP_JSON(jsonstr)) == 0 )
        retstr = clonestr("{\"error\":\"null return\"}");
    CREATE_RESPONSE(CHROMEAPP_STR);
    RESPONSE_STRING(retstr);
    return 0;
}

int example_main()
{
    PNACL_message("Started %s example main.\n",CHROMEAPP_STR);
    while ( 1 )
    {
        sleep(777);
    }
    return 0;
}

PSMainFunc_t PSUserMainGet()
{
    return(example_main);
}

#else
int main(int argc, const char * argv[])
{
    char *jsonstr;
    if ( argc < 2 )
        jsonstr = 0;
    else jsonstr = (char *)argv[1];
    initflag = 1;
    OS_init();
    printf("%s main\n",CHROMEAPP_STR);
    CHROMEAPP_NAME(jsonstr);
    return 0;
}
#endif