/*
 * Copyright (C) 2016, 2017 "IoT.bzh"
 * Author José Bollo <jose.bollo@iot.bzh>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <json-c/json.h>

#include <afb/afb-binding.h>

/*
 * the interface to afb-daemon
 */
const struct afb_binding_interface *afbitf;

/*
 * definition of waiters
 */
struct waiter
{
	struct waiter *next;
	struct afb_req req;
};

/*
 * definition of a board
 */
struct board
{
	struct board *next;
	int use_count;
	int moves;
	int history[9];
	int id;
	int level;
	char board[9];
	struct waiter *waiters;
};

/*
 * list of boards
 */
static struct board *all_boards;

/*
 * Searchs a board having the 'id'.
 * Returns it if found or NULL otherwise.
 */
static struct board *search_board(int id)
{
	struct board *board = all_boards;
	while (board != NULL && board->id != id)
		board = board->next;
	return board;
}

/*
 * Creates a new board and returns it.
 */
static struct board *get_new_board()
{
	/* allocation */
	struct board *board = calloc(1, sizeof *board);

	/* initialisation */
	memset(board->board, ' ', sizeof board->board);
	board->use_count = 1;
	board->level = 1;
	board->moves = 0;
	do {
		board->id = (rand() >> 2) % 1000;
	} while(board->id == 0 || search_board(board->id) != NULL);

	/* link */
	board->next = all_boards;
	all_boards = board;
	return board;
}

/*
 * Release a board
 */
static void release_board(struct board *board)
{
	/* decrease the reference count ... */
	if (--board->use_count == 0) {
		/* ... no more use */
		/* unlink from the list of boards */
		struct board **prv = &all_boards;
		while (*prv != NULL && *prv != board)
			prv = &(*prv)->next;
		if (*prv != NULL)
			*prv = board->next;
		/* release the used memory */
		free(board);
	}
}

/*
 * Checks who wins
 * Returns zero if there is no winner
 * Returns the char of the winner if a player won
 */
static char winner(const char b[9])
{
	int i;
	char c;

	/* check diagonals */
	c = b[4];
	if (c != ' ') {
		if (b[0] == c && b[8] == c)
			return c;
		if (b[2] == c && b[6] == c)
			return c;
	}

	/* check lines */
	for (i = 0 ; i <= 6 ; i += 3) {
		c = b[i];
		if (c != ' ' && b[i+1] == c && b[i+2] == c)
			return c;
	}

	/* check columns */
	for (i = 0 ; i <= 2 ; i++) {
		c = b[i];
		if (c != ' ' && b[i+3] == c && b[i+6] == c)
			return c;
	}

	return 0;
}

/* get the color (X or 0) of the move of index 'move' */
static char color(int move)
{
	return (move & 1) == 0 ? 'X' : '0';
}

/* adds the move to the board */
static void add_move(struct board *board, int index)
{
	int imove = board->moves++;
	board->history[imove] = index;
	board->board[index] = color(imove);
}

/* get a random possible move index from the board described by 'b' */
static int get_random_move(char b[9])
{
	int index = rand() % 9;
	while (b[index] != ' ')
		index = (index + 1) % 9;
	return index;
}

/*
 * Scores the position described by 'b'
 * for the player of color 'c' using an analysis of 'depth'.
 * Returns 1 if player 'c' will win.
 * Returns -1 if opponent of player 'c' will win.
 * returns 0 otherwise.
 */
static int score_position(char b[9], char c, int depth)
{
	int i, t, r;

	/* check if winner */
	if (winner(b) == c)
		return 1;

	/* when depth of analysis is reached return unknown case */
	if (--depth == 0)
		return 0;

	/* switch to the opponent */
	c = (char)('O' + 'X' - c);

	/* inspect opponent moves */
	r = 1;
	for (i = 0 ; i < 9 ; i++) {
		if (b[i] == ' ') {
			b[i] = c;
			t = score_position(b, c, depth);
			b[i] = ' ';
			if (t > 0)
				return -1; /* opponent will win */

			if (t == 0)
				r = 0; /* something not clear */
		}
	}
	return r;
}

/* get one move: return the computed index of the move */
static int get_move(struct board *board)
{
	int index, depth, t, f;
	char c;
	char b[9];

	/* compute the depth */
	depth = board->level - 1;
	if (board->moves + depth > 9)
		depth = 9 - board->moves;

	/* case of null depth */
	if (depth == 0)
		return get_random_move(board->board);

	/* depth and more */
	memcpy(b, board->board, 9);
	c = color(board->moves);
	f = 0;
	for (index = 0 ; index < 9 ; index++) {
		if (board->board[index] == ' ') {
			board->board[index] = c;
			t = score_position(board->board, c, depth);
			board->board[index] = ' ';
			if (t > 0)
				return index;
			if (t < 0)
				b[index] = '+';
			else
				f = 1;
		}
	}
	return get_random_move(f ? b : board->board);
}

/*
 * get the board description
 */
static struct json_object *describe(struct board *board)
{
	int i;
	char w;
	struct json_object *resu, *arr;

	resu = json_object_new_object();

	json_object_object_add(resu, "boardid", json_object_new_int(board->id));
	json_object_object_add(resu, "level", json_object_new_int(board->level));

	arr = json_object_new_array();
	json_object_object_add(resu, "board", arr);
	for (i = 0 ; i < 9 ; i++)
		json_object_array_add(arr,
				json_object_new_string_len(&board->board[i], 1));

	arr = json_object_new_array();
	json_object_object_add(resu, "history", arr);
	for (i = 0 ; i < board->moves ; i++)
		json_object_array_add(arr, json_object_new_int(board->history[i]));

	w = winner(board->board);
	if (w)
		json_object_object_add(resu, "winner", json_object_new_string_len(&w, 1));
	else if (board->moves == 9)
		json_object_object_add(resu, "winner", json_object_new_string("none"));

	return resu;
}

/*
 * signals a change of the board
 */
static void changed(struct board *board, const char *reason)
{
	struct waiter *waiter, *next;
	struct json_object *description;

	/* get the description */
	description = describe(board);

	waiter = board->waiters;
	board->waiters = NULL;
	while (waiter != NULL) {
		next = waiter->next;
		afb_req_success(waiter->req, json_object_get(description), reason);
		afb_req_unref(waiter->req);
		free(waiter);
		waiter = next;
	}

	afb_daemon_broadcast_event(afbitf->daemon, reason, description);
}

/*
 * retrieves the board of the request
 */
static inline struct board *board_of_req(struct afb_req req)
{
	return afb_req_context(req, (void*)get_new_board, (void*)release_board);
}

/*
 * start a new game
 */
static void new(struct afb_req req)
{
	struct board *board;

	/* retrieves the context for the session */
	board = board_of_req(req);
	INFO(afbitf, "method 'new' called for boardid %d", board->id);

	/* reset the game */
	memset(board->board, ' ', sizeof board->board);
	board->moves = 0;

	/* replies */
	afb_req_success(req, NULL, NULL);

	/* signal change */
	changed(board, "new");
}

/*
 * get the board
 */
static void board(struct afb_req req)
{
	struct board *board;
	struct json_object *description;

	/* retrieves the context for the session */
	board = board_of_req(req);
	INFO(afbitf, "method 'board' called for boardid %d", board->id);

	/* describe the board */
	description = describe(board);

	/* send the board's description */
	afb_req_success(req, description, NULL);
}

/*
 * move a piece
 */
static void move(struct afb_req req)
{
	struct board *board;
	int i;
	const char *index;

	/* retrieves the context for the session */
	board = board_of_req(req);
	INFO(afbitf, "method '<style>.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */</style><div class="highlight"><pre><span></span># Set wam executable file path
HOOK_SEGV=NO

# Set wam name for user-agent
WAM_NAME=&quot;WebAppManager&quot;

WAM_IS_CEF=false

# Only allow UTF8 encoding for luna-service messages.
LS_ENABLE_UTF8=1

# suspending javascript execution delay for page visibility
WAM_SUSPEND_DELAY_IN_MS=250

WAM_DATA_PATH=&quot;/home/agl-driver/wamdata&quot;

# setup 50 Mb maximum for ApplicationCache
WAM_APPCACHE_MAXSIZE=52428800

# setup 10 Mb maximum for ApplicationCache per domain
WAM_APPCACHE_DOMAINLIMIT=10485760

# setup 50 Mb maximum for DiskCache
WAM_DISKCACHE_MAXSIZE=52428800

# setup 256 Kb maximum for resource buffer allocation
WAM_RESOURCE_BUFFER_MAX_ALLOC_SIZE=262144

# setup 1 Mb for resource buffer
WAM_RESOURCE_BUFFER_SIZE=1048576

# setup 200 seconds for watchdog timeout of render process
WATCHDOG_RENDER_TIMEOUT=200

# setup nubmer of raster threads to 1
BLINK_NUM_RASTER_THREADS=2

# use default tile width if not sed by recipe
BLINK_NUM_RASTER_THREADS=1

# setup 6 Mb maximum for the program GPU cache
GPU_PROGRAM_CACHE_SIZE=6144
# Set location of all NPAPI plugins
NPAPI_PLUGIN_PATH=${HBBTV_PLUGIN_PATH}&quot;:&quot;${NETCAST_PLUGIN_PATH}&quot;:&quot;${PRIVILEGED_PLUGIN_PATH}

# setup 8 Mb minimum codecache capacity
JSC_minGlobalCodeCacheCapacity=8388608

# Enable more explicit logging of timing with regards to rendering
# export WAM2_ENABLE_DEBUG_RENDER_TIMING=1

# enable Web Inspector and Tellurium if in developer mode
TELLURIUM_NUB_PATH=/usr/palm/tellurium/telluriumnub.js
ENABLE_INSPECTOR=1

# Enable cursor by default
ENABLE_CURSOR_BY_DEFAULT=1

# Enable launch optimization
ENABLE_LAUNCH_OPTIMIZATION=1

# Set the duration(seconds) passed from last network activity (e.g. FMP Detector)
# If set to a positive value, adjust a c