/*
 *   Copyright © 2008-2010 dragchan <zgchan317@gmail.com>
 *   This file is part of FbTerm.
 *
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU General Public License
 *   as published by the Free Software Foundation; either version 2
 *   of the License, or (at your option) any later version.
 *
 *   This program 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 General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "imapi.h"

#define OFFSET(TYPE, MEMBER) ((size_t)(&(((TYPE *)0)->MEMBER)))
#define MSG(a) ((Message *)(a))

static int imfd = -1;
static ImCallbacks cbs;
static char pending_msg_buf[10240];
static unsigned pending_msg_buf_len = 0;
static int im_active = 0;

static void wait_message(MessageType type);

void register_im_callbacks(ImCallbacks callbacks)
{
	cbs = callbacks;
}

int get_im_socket()
{
	static char init = 0;
	if (!init) {
		init = 1;

		char *val = getenv("FBTERM_IM_SOCKET");
		if (val) {
			char *tail;
			int fd = strtol(val, &tail, 0);
			if (!*tail) imfd = fd;
		}
	}

	return imfd;
}

void connect_fbterm(char raw)
{
	get_im_socket();
	if (imfd == -1) return;

	Message msg;
	msg.type = Connect;
	msg.len = sizeof(msg);
	msg.raw = (raw ? 1 : 0);
	int ret = write(imfd, (char *)&msg, sizeof(msg));
}

void put_im_text(const char *text, unsigned len)
{
	if (imfd == -1 || !im_active || !text || !len || (OFFSET(Message, texts) + len > UINT16_MAX)) return;

	char buf[OFFSET(Message, texts) + len];

	MSG(buf)->type = PutText;
	MSG(buf)->len = sizeof(buf);
	memcpy(MSG(buf)->texts, text, len);

	int ret = write(imfd, buf, MSG(buf)->len);
}

void set_im_window(unsigned id, Rectangle rect)
{
	if (imfd == -1 || !im_active || id >= NR_IM_WINS) return;

	Message msg;
	msg.type = SetWin;
	msg.len = sizeof(msg);
	msg.win.winid = id;
	msg.win.rect = rect;

	int ret = write(imfd, (char *)&msg, sizeof(msg));
	wait_message(AckWin);
}

void fill_rect(Rectangle rect, unsigned char color)
{
	Message msg;
	msg.type = FillRect;
	msg.len = sizeof(msg);

	msg.fillRect.rect = rect;
	msg.fillRect.color = color;

	int ret = write(imfd, (char *)&msg, sizeof(msg));
}

void draw_text(unsigned x, unsigned y, unsigned char fc, unsigned char bc, const char *text, unsigned len)
{
	if (!text || !len) return;

	char buf[OFFSET(Message, drawText.texts) + len];

	MSG(buf)->type = DrawText;
	MSG(buf)->len = sizeof(buf);

	MSG(buf)->drawText.x = x;
	MSG(buf)->drawText.y = y;
	MSG(buf)->drawText.fc = fc;
	MSG(buf)->drawText.bc = bc;
	memcpy(MSG(buf)->drawText.texts, text, len);

	int ret = write(imfd, buf, MSG(buf)->len);
}

static int process_message(Message *msg)
{
	int exit = 0;

	switch (msg->type) {
	case Disconnect:
		close(imfd);
		imfd = -1;
		exit = 1;
		break;

	case FbTermInfo:
		if (cbs.fbterm_info) {
			cbs.fbterm_info(&msg->info);
		}
		break;

	case Active:
		im_active = 1;
		if (cbs.active) {
			cbs.active();
		}
		break;

	case Deactive:
		if (cbs.deactive) {
			cbs.deactive();
		}
		im_active = 0;
		break;

	case ShowUI:
		if (im_active && cbs.show_ui) {
			cbs.show_ui(msg->winid);
		}
		break;

	case HideUI: {
		if (im_active && cbs.hide_ui) {
			cbs.hide_ui();
		}

		Message msg;
		msg.type = AckHideUI;
		msg.len = sizeof(msg);
		int ret = write(imfd, (char *)&msg, sizeof(msg));
		break;
		}

	case SendKey:
		if (im_active && cbs.send_key) {
				cbs.send_key(msg->keys, msg->len - OFFSET(Message, keys));
		}
		break;

	case CursorPosition:
		if (im_active && cbs.cursor_position) {
			cbs.cursor_position(msg->cursor.x, msg->cursor.y);
		}
		break;

	case TermMode:
		if (im_active && cbs.term_mode) {
			cbs.term_mode(msg->term.crWithLf, msg->term.applicKeypad, msg->term.cursorEscO);
		}
		break;

	default:
		break;
	}

	return exit;
}

static int process_messages(char *buf, int len)
{
	char *cur = buf, *end = cur + len;
	int exit = 0;

	for (; cur < end && MSG(cur)->len <= (end - cur); cur += MSG(cur)->len) {
		exit |= process_message(MSG(cur));
	}

	return exit;
}

static void wait_message(MessageType type)
{
	int ack = 0;
	while (!ack) {
		char *cur = pending_msg_buf + pending_msg_buf_len;
		int len = read(imfd, cur, sizeof(pending_msg_buf) - pending_msg_buf_len);

		if (len == -1 && (errno == EAGAIN || errno == EINTR)) continue;
		else if (len <= 0) {
			close(imfd);
			imfd = -1;
			return;
		}

		pending_msg_buf_len += len;

		char *end = cur + len;
		for (; cur < end && MSG(cur)->len <= (end - cur); cur += MSG(cur)->len) {
			if (MSG(cur)->type == type) {
				memcpy(cur, cur + MSG(cur)->len, end - cur - MSG(cur)->len);
				pending_msg_buf_len -= MSG(cur)->len;

				ack = 1;
				break;
			}
		}
	}

	if (pending_msg_buf_len) {
		Message msg;
		msg.type = Ping;
		msg.len = sizeof(msg);
		int ret = write(imfd, (char *)&msg, sizeof(msg));
	}
}

int check_im_message()
{
	if (imfd == -1) return 0;

	char buf[sizeof(pending_msg_buf)];
	int len, exit = 0;

	if (pending_msg_buf_len) {
		len = pending_msg_buf_len;
		pending_msg_buf_len = 0;

		memcpy(buf, pending_msg_buf, len);
		exit |= process_messages(buf, len);
	}

	len = read(imfd, buf, sizeof(buf));

	if (len == -1 && (errno == EAGAIN || errno == EINTR)) return 1;
	else if (len <= 0) {
		close(imfd);
		imfd = -1;
		return 0;
	}

	exit |= process_messages(buf, len);

	return !exit;
}
