/*
 * Copyright (c) 2017-2019 TOYOTA MOTOR CORPORATION
 *
 * 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 <unistd.h>
#include <fcntl.h>
#include <net/if.h>
#include <string.h>
#include <sys/ioctl.h>
#include <alloca.h>

#include <linux/input.h>
#include <linux/joystick.h>

#include "wheel-service.h"
#include "js_raw.h"
#include "js_signal_event.h"

#define JSNAMELEN 128

static int *axis;
static char *button;
static timer_t tid;
static struct sigaction oldact;

int js_signal_read(int fd)
{
	ssize_t size;
	struct js_event jsEvent;
	int rc = 0;

	size = read(fd, &jsEvent, sizeof(struct js_event));
	if(size != 0)
	{
		switch (jsEvent.type) //& ~JS_EVENT_INIT don't deal with init state.
		{
			case JS_EVENT_BUTTON | JS_EVENT_INIT:
				DBG_NOTICE("This is JS_EVENT_BUTTON init state [%d] : %d.", jsEvent.number, button[jsEvent.number]);
				break;
			case JS_EVENT_AXIS | JS_EVENT_INIT:
				DBG_NOTICE("This is JS_EVENT_AXIS init state [%d] : %d.", jsEvent.number, axis[jsEvent.number]);
				break;
			case JS_EVENT_BUTTON:
				button[jsEvent.number] = (char)jsEvent.value;
				DBG_NOTICE("JS_EVENT_BUTTON [%d] : %d.", jsEvent.number, button[jsEvent.number]);
				newButtonValue(jsEvent.number, jsEvent.value);
				break;
			case JS_EVENT_AXIS:
				axis[jsEvent.number] = jsEvent.value;
				DBG_NOTICE("JS_EVENT_AXIS [%d] : %d.", jsEvent.number, axis[jsEvent.number]);
				newAxisValue(jsEvent.number, jsEvent.value);
				break;
			default:
				break;
		}

		rc = 0;
	}
	else
	{
		rc = -1;
	}

	return rc;
}

int on_event(sd_event_source *s, int fd, uint32_t revents, void *userdata)
{
	if ((revents & EPOLLIN) != 0)
	{
		//NOTICEMSG("on_event!\n");
		int rc = 0;

		rc = js_signal_read(fd);
		if(rc == -1)
		{
			DBG_ERROR("JS Frame Read failed");

			return -1;
		}
	}
	if ((revents & (EPOLLERR|EPOLLRDHUP|EPOLLHUP)) != 0)
	{
		/* T.B.D
		 * if error or hungup */
		DBG_ERROR("Error or Hunup: rvent=%08x", revents);
	}

	return 0;
}

int js_open(const char *devname)
{
	unsigned char numAxes = 0;
	unsigned char numButtons = 0;
	int version = 0;
	int fd;
	char name[JSNAMELEN] = "Unknown";
	char steering_name[JSNAMELEN] = "Logitech G29 Driving Force Racing Wheel";

	struct js_corr cal[6];
	int i, j;
	unsigned int calData[36] =
	{
		1, 0, 8191, 8192, 65542, 65534,
		1, 0, 127, 128, 4227201, 4194176,
		1, 0, 127, 128, 4227201, 4194176,
		1, 0, 127, 128, 4227201, 4194176,
		1, 0, 0, 0, 536854528, 536854528,
		1, 0, 0, 0, 536854528, 536854528
	};

	if ((fd = open(devname, O_RDONLY)) < 0)
	{
		return -1;
	}

	ioctl(fd, JSIOCGVERSION, &version);
	ioctl(fd, JSIOCGAXES, &numAxes);
	ioctl(fd, JSIOCGBUTTONS, &numButtons);
	ioctl(fd, JSIOCGNAME(JSNAMELEN), name);

	if (strcmp(name, steering_name) == 0)
	{
		setJsType(JS_TYPE_STEERING);
		DBG_NOTICE("JS_TYPE_STEERING type!");
	}
	else
	{
		setJsType(JS_TYPE_GAME_CTL);
		DBG_NOTICE("JS_TYPE_GAME_CTL type!");
	}

	for (i = 0; i < 6; i++)
	{
		int k = 0;
		cal[i].type = (__u16)calData[(i*6)+k];
		k++;
		cal[i].prec = (__s16)calData[(i*6)+k];
		k++;

		for(j = 0; j < 4; j++)
		{
			cal[i].coef[j] = (__s32)calData[(i*6)+k];
			k++;
		}
	}

	if (ioctl(fd, JSIOCSCORR, &cal) < 0)
	{
		return -1;
	}

	axis = (int *)calloc(numAxes, sizeof(int));
	button = (char *)calloc(numButtons, sizeof(char));
#if 0
	gis = g_unix_input_stream_new(fd, TRUE);
	if(gis == NULL)
	{
		DBG_ERROR("g_unix_input_stream_new() failed!");
	}
	else
	{
		NOTICEMSG("g_unix_input_stream_new() succeed!");
	}
	g_input_stream_read_async(gis, &jsEvent, sizeof(struct js_event), G_PRIORITY_DEFAULT, NULL, &readCallback, NULL);
#endif

	return fd;
}

void js_close(int js)
{
	if (js < 0)
	{
		return;
	}

	close(js);
}

int init_timer()
{
    struct sigaction act;
    struct itimerspec itval;

    memset(&act, 0, sizeof(struct sigaction));
    memset(&oldact, 0, sizeof(struct sigaction));

    act.sa_handler = updateTimerHandler;
    act.sa_flags = SA_RESTART;
    if(sigaction(SIGALRM, &act, &oldact) < 0) {
        DBG_ERROR("sigaction failed.");
        return -1;
    }

    itval.it_value.tv_sec = 0;
    itval.it_value.tv_nsec = 200000000;
    itval.it_interval.tv_sec = 0;
    itval.it_interval.tv_nsec = 200000000;

    if(timer_create(CLOCK_REALTIME, NULL, &tid) < 0) {
        DBG_ERROR("timer_create failed.");
        return -1;
    }

    if(timer_settime(tid, 0, &itval, NULL) < 0) {
        DBG_ERROR("timer_settime failed.");
        return -1;
    }

    return 0;
}

void deinit_timer()
{
    timer_delete(tid);
    sigaction(SIGALRM, &oldact, NULL);
}