/*
 * Copyright (C) 2003 Robert Kooima
 * Copyright (C) 2011 Daniel Friesel
 *
 * NEVERBALL 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.
 */

#include <SDL.h>
#include <SDL_thread.h>
#include <math.h>
#include <stdio.h>

#include "config.h"

/*---------------------------------------------------------------------------*/

#define _ENABLE_TILT
#include <bluetooth/bluetooth.h>
#include <cwiid.h>

struct balance_cal balance_cal;

/*
 * This data structure tracks button changes, counting transitions so that
 * none are missed if the event handling thread falls significantly behind
 * the device IO thread.
 */

#define BUTTON_NC 0
#define BUTTON_DN 1
#define BUTTON_UP 2

struct button_state
{
    unsigned char curr;
    unsigned char last;
    unsigned char upc;
    unsigned char dnc;
};


/* not having and setting a callback causes problems with SDL's mutex
 * ("Mesg pipe is full")
 */
static void cwiid_callback(cwiid_wiimote_t *wiimote, int mesg_count,
	union cwiid_mesg mesg[], struct timespec *ts)
{
}

static double weight(unsigned short reading, unsigned short cal[3])
{
	if (reading < cal[1])
		return ((double)reading - cal[0]) / (cal[1] - cal[0]) * 17.0;

	return (((double)reading - cal[1]) / (cal[2] - cal[1]) * 17.0) + 17.0;
}

static void set_button(struct button_state *B, int s)
{
    if ((B->curr == 0) != (s == 0))
    {
        if (B->curr)
        {
            B->upc++;
            B->curr = 0;
        }
        else
        {
            B->dnc++;
            B->curr = 1;
        }
    }
}

static int get_button(struct button_state *B)
{
    int ch = BUTTON_NC;

    if      (B->last == 1 && B->upc > 0)
    {
        B->upc--;
        B->last = 0;
        ch = BUTTON_UP;
    }
    else if (B->last == 0 && B->dnc > 0)
    {
        B->dnc--;
        B->last = 1;
        ch = BUTTON_DN;
    }

    return ch;
}

/*---------------------------------------------------------------------------*/

struct tilt_state
{
    int   status;
    float x;
    float z;
    struct button_state A;
    struct button_state B;
    struct button_state plus;
    struct button_state minus;
    struct button_state home;
    struct button_state L;
    struct button_state R;
    struct button_state U;
    struct button_state D;
};

static struct tilt_state state;
static SDL_mutex        *mutex  = NULL;
static SDL_Thread       *thread = NULL;

#define FILTER 8

static int tilt_func(void *data)
{
    cwiid_wiimote_t *wiimote = NULL;

    struct cwiid_state wiistate;

    double wlt, wrt, wlb, wrb, bal_x, bal_y;

    if ((wiimote = cwiid_open(BDADDR_ANY, 0)) == NULL)
		fprintf(stderr, "Unable to connect to bboard\n");
	else
	{
		int running = 1;

		cwiid_set_led(wiimote, 1);
		cwiid_get_balance_cal(wiimote, &balance_cal);
		cwiid_set_mesg_callback(wiimote, cwiid_callback);
		cwiid_enable(wiimote, CWIID_FLAG_MESG_IFC);
		cwiid_set_rpt_mode(wiimote, CWIID_RPT_STATUS | CWIID_RPT_EXT);

            SDL_mutexP(mutex);
            state.status = running;
            SDL_mutexV(mutex);

            while (mutex && running)
            {

                SDL_mutexP(mutex);
                {
                    running = state.status;

                    set_button(&state.A,     0);
                    set_button(&state.B,     0);
                    set_button(&state.plus,  0);
                    set_button(&state.minus, 0);
                    set_button(&state.home,  0);
                    set_button(&state.L,     0);
                    set_button(&state.R,     0);
                    set_button(&state.U,     0);
                    set_button(&state.D,     0);

				cwiid_get_state(wiimote, &wiistate);

				wlt = weight(wiistate.ext.balance.left_top, balance_cal.left_top);
				wrt = weight(wiistate.ext.balance.right_top, balance_cal.right_top);
				wlb = weight(wiistate.ext.balance.left_bottom, balance_cal.left_bottom);
				wrb = weight(wiistate.ext.balance.right_bottom, balance_cal.right_bottom);

				bal_x = (wrt + wrb) / (wlt + wlb);
				if (bal_x > 1)
					bal_x = ((wlt + wlb) / (wrt + wrb) * (-1.0)) + 1.0;
				else
					bal_x -= 1;

				bal_y = (wlt + wrt) / (wlb + wrb);
				if (bal_y > 1)
					bal_y = ((wlb + wrb) / (wlt + wrt) * (-1.0)) + 1.0;
				else
					bal_y -= 1;

                    if ((bal_y >= -1) && (bal_y <= 1))
                    {
					bal_y *= 12;
                        state.x = (state.x * (FILTER - 1) +
                                   bal_y) / FILTER;
                    }
                    if ((bal_x >= -1) && (bal_x <= 1))
                    {
					bal_x *= 12;
                        state.z = (state.z * (FILTER - 1) +
                                   bal_x) / FILTER;
                    }
                }
                SDL_mutexV(mutex);
            }

    }
    return 0;
}

void tilt_init(void)
{
    memset(&state, 0, sizeof (struct tilt_state));

    mutex  = SDL_CreateMutex();
    thread = SDL_CreateThread(tilt_func, NULL);
}

void tilt_free(void)
{
    int b = 0;

    if (mutex)
    {
        /* Get/set the status of the tilt sensor thread. */

        SDL_mutexP(mutex);
        b = state.status;
        state.status = 0;
        SDL_mutexV(mutex);

        /* Kill the thread and destroy the mutex. */

        SDL_KillThread(thread);
        SDL_DestroyMutex(mutex);

        mutex  = NULL;
        thread = NULL;
    }
}

int tilt_get_button(int *b, int *s)
{
    int ch = BUTTON_NC;

    if (mutex)
    {
        SDL_mutexP(mutex);
        {
            if      ((ch = get_button(&state.A)))
            {
                *b = config_get_d(CONFIG_JOYSTICK_BUTTON_A);
                *s = (ch == BUTTON_DN);
            }
            else if ((ch = get_button(&state.B)))
            {
                *b = config_get_d(CONFIG_JOYSTICK_BUTTON_B);
                *s = (ch == BUTTON_DN);
            }
            else if ((ch = get_button(&state.plus)))
            {
                *b = config_get_d(CONFIG_JOYSTICK_BUTTON_R);
                *s = (ch == BUTTON_DN);
            }
            else if ((ch = get_button(&state.minus)))
            {
                *b = config_get_d(CONFIG_JOYSTICK_BUTTON_L);
                *s = (ch == BUTTON_DN);
            }
            else if ((ch = get_button(&state.home)))
            {
                *b = config_get_d(CONFIG_JOYSTICK_BUTTON_EXIT);
                *s = (ch == BUTTON_DN);
            }
            else if ((ch = get_button(&state.L)))
            {
                *b = config_get_d(CONFIG_JOYSTICK_DPAD_L);
                *s = (ch == BUTTON_DN);
            }
            else if ((ch = get_button(&state.R)))
            {
                *b = config_get_d(CONFIG_JOYSTICK_DPAD_R);
                *s = (ch == BUTTON_DN);
            }
            else if ((ch = get_button(&state.U)))
            {
                *b = config_get_d(CONFIG_JOYSTICK_DPAD_U);
                *s = (ch == BUTTON_DN);
            }
            else if ((ch = get_button(&state.D)))
            {
                *b = config_get_d(CONFIG_JOYSTICK_DPAD_D);
                *s = (ch == BUTTON_DN);
            }
        }
        SDL_mutexV(mutex);
    }
    return ch;
}

float tilt_get_x(void)
{
    float x = 0.0f;

    if (mutex)
    {
        SDL_mutexP(mutex);
        x = state.x;
        SDL_mutexV(mutex);
    }

    return x;
}

float tilt_get_z(void)
{
    float z = 0.0f;

    if (mutex)
    {
        SDL_mutexP(mutex);
        z = state.z;
        SDL_mutexV(mutex);
    }

    return z;
}

int tilt_stat(void)
{
    int b = 0;

    if (mutex)
    {
        SDL_mutexP(mutex);
        b = state.status;
        SDL_mutexV(mutex);
    }
    return b;
}

/*---------------------------------------------------------------------------*/


